Skip to content

Commit ff1c901

Browse files
willgeartykkbrum
andauthored
Add module that allows students to get completion certificate (#3477)
* Add student certificate module * Fix certificate indentation and table * Change error to redirect * Escape program name in certificate * One more escaped program name * Add tag to specify which students can get certificates Co-authored-by: Katherine <[email protected]>
1 parent 94231ed commit ff1c901

File tree

9 files changed

+165
-20
lines changed

9 files changed

+165
-20
lines changed

esp/esp/program/forms.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def __init__(self, *args, **kwargs):
8888
('Do students have to apply to individual classes?', [x.id for x in ProgramModule.objects.filter(admin_title__in=['Application Review for Admin', 'Admin Admissions Dashboard'])]),
8989
('If yes, can teachers admit them (as opposed to just admins)?', [x.id for x in ProgramModule.objects.filter(admin_title__in=['Teacher Admissions Dashboard', 'Application Reviews for Teachers', 'Application Review for Admin', 'Admin Admissions Dashboard'])]),
9090
('Will you have moderators or assistants for individual class sections?', [x.id for x in ProgramModule.objects.filter(admin_title='Moderator Signup')]),
91+
('Do you want students to be able to download a completion certificate after the program ends?', [x.id for x in ProgramModule.objects.filter(admin_title='Student Certificate Module')]),
9192
])
9293
# Include additional or new modules that haven't been added to the list
9394
for x in ProgramModule.objects.filter(choosable=0):

esp/esp/program/modules/handlers/programprintables.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,12 +1019,10 @@ def get_msg_vars(self, user, key):
10191019
return u''
10201020

10211021
@staticmethod
1022-
def get_student_classlist(program, student):
1022+
def get_student_classlist(program, student, verbs = ['Enrolled'], valid_only = True):
10231023
# get list of valid classes
1024-
classes = [ cls for cls in student.getEnrolledSections()]
1025-
classes = [ cls for cls in classes
1026-
if cls.parent_program == program
1027-
and cls.isAccepted() ]
1024+
classes = [ cls for cls in student.getSections(program = program, verbs = verbs, valid_only = valid_only)]
1025+
classes = [ cls for cls in classes if cls.isAccepted() ]
10281026
classes.sort()
10291027
return classes
10301028

@@ -1053,7 +1051,7 @@ def get_teacher_classlist(program, teacher, teaching = True, moderating = False)
10531051
return scheditems
10541052

10551053
@staticmethod
1056-
def getTranscript(program, student, format='text'):
1054+
def getTranscript(program, student, format='text', verbs = ['Enrolled'], valid_only = True):
10571055
from django.template import Template
10581056
from esp.middleware.threadlocalrequest import AutoRequestContext as Context
10591057

@@ -1070,7 +1068,7 @@ def getTranscript(program, student, format='text'):
10701068

10711069
t = get_template(template_filename)
10721070

1073-
context = {'classlist': ProgramPrintables.get_student_classlist(program, student)}
1071+
context = {'classlist': ProgramPrintables.get_student_classlist(program, student, verbs = verbs, valid_only = valid_only)}
10741072

10751073
return t.render(context)
10761074

@@ -1713,9 +1711,15 @@ def certificate(self, request, tl, one, two, module, extra, prog):
17131711
else:
17141712
file_type = 'pdf'
17151713

1714+
attended = Tag.getProgramTag('student_certificate', prog) == 'class_attendance'
1715+
if attended:
1716+
verbs = ['Attended']
1717+
else:
1718+
verbs = ['Enrolled']
1719+
17161720
context = {'user': user, 'prog': prog,
1717-
'schedule': ProgramPrintables.getTranscript(prog, user, 'latex'),
1718-
'descriptions': ProgramPrintables.getTranscript(prog, user, 'latex_desc')}
1721+
'schedule': ProgramPrintables.getTranscript(prog, user, 'latex', verbs, valid_only = (not attended)),
1722+
'descriptions': ProgramPrintables.getTranscript(prog, user, 'latex_desc', verbs, valid_only = (not attended))}
17191723

17201724
return render_to_latex(self.baseDir()+'completion_certificate.tex', context, file_type)
17211725

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
2+
__author__ = "Individual contributors (see AUTHORS file)"
3+
__date__ = "$DATE$"
4+
__rev__ = "$REV$"
5+
__license__ = "AGPL v.3"
6+
__copyright__ = """
7+
This file is part of the ESP Web Site
8+
Copyright (c) 2012 by the individual contributors
9+
(see AUTHORS file)
10+
The ESP Web Site is free software; you can redistribute it and/or
11+
modify it under the terms of the GNU Affero General Public License
12+
as published by the Free Software Foundation; either version 3
13+
of the License, or (at your option) any later version.
14+
This program is distributed in the hope that it will be useful,
15+
but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
GNU Affero General Public License for more details.
18+
You should have received a copy of the GNU Affero General Public
19+
License along with this program; if not, write to the Free Software
20+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21+
Contact information:
22+
MIT Educational Studies Program
23+
84 Massachusetts Ave W20-467, Cambridge, MA 02139
24+
Phone: 617-253-4882
25+
26+
Learning Unlimited, Inc.
27+
527 Franklin St, Cambridge, MA 02139
28+
Phone: 617-379-0178
29+
30+
"""
31+
32+
from esp.middleware.threadlocalrequest import get_current_request
33+
from esp.program.modules.base import ProgramModuleObj, main_call, needs_student, meets_cap
34+
from esp.program.modules.handlers.programprintables import ProgramPrintables
35+
from esp.program.models import StudentRegistration
36+
from esp.tagdict.models import Tag
37+
from esp.users.models import Record
38+
from esp.utils.latex import render_to_latex
39+
40+
from datetime import datetime
41+
42+
class StudentCertModule(ProgramModuleObj):
43+
doc = """Allows students to download a completion certificate
44+
for the program once the program has ended."""
45+
46+
@classmethod
47+
def module_properties(cls):
48+
return {
49+
"link_title": "Print Completion Certificate",
50+
"admin_title": "Student Certificate Module",
51+
"module_type": "learn",
52+
"required": False,
53+
"seq": 999999,
54+
"choosable": 0,
55+
}
56+
57+
@main_call
58+
@needs_student
59+
@meets_cap
60+
def certificate(self, request, tl, one, two, module, extra, prog):
61+
if not self.isStep():
62+
return self.goToCore(tl)
63+
64+
user = request.user
65+
66+
if extra:
67+
file_type = extra.strip()
68+
else:
69+
file_type = 'pdf'
70+
71+
attended = Tag.getProgramTag('student_certificate', prog) == 'class_attendance'
72+
if attended:
73+
verbs = ['Attended']
74+
else:
75+
verbs = ['Enrolled']
76+
77+
context = {'user': user, 'prog': prog,
78+
'schedule': ProgramPrintables.getTranscript(prog, user, 'latex', verbs, valid_only = (not attended)),
79+
'descriptions': ProgramPrintables.getTranscript(prog, user, 'latex_desc', verbs, valid_only = (not attended))}
80+
81+
return render_to_latex('program/modules/programprintables/completion_certificate.tex', context, file_type)
82+
83+
def isStep(self):
84+
if hasattr(self, 'user'):
85+
user = self.user
86+
else:
87+
request = get_current_request()
88+
user = request.user
89+
cert_tag = Tag.getProgramTag('student_certificate', self.program)
90+
slots = self.program.getTimeSlots()
91+
if slots and datetime.now() > max(slots).end:
92+
if cert_tag == 'all':
93+
return True
94+
elif cert_tag == 'program_attendance':
95+
return Record.user_completed(user, "attended", self.program)
96+
elif cert_tag == 'class_attendance':
97+
return StudentRegistration.objects.filter(user = user, section__parent_class__parent_program=self.program, relationship__name = 'Attended').exists()
98+
return False
99+
100+
class Meta:
101+
proxy = True
102+
app_label = 'modules'
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.11.29 on 2022-01-18 20:41
3+
from __future__ import unicode_literals
4+
5+
from django.db import migrations
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('modules', '0037_medliab_split'),
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name='StudentCertModule',
17+
fields=[
18+
],
19+
options={
20+
'proxy': True,
21+
'indexes': [],
22+
},
23+
bases=('modules.programmoduleobj',),
24+
),
25+
]

esp/esp/tagdict/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,18 @@
10521052
'category': 'teach',
10531053
'is_setting': True,
10541054
},
1055+
'student_certificate': {
1056+
'is_boolean': False,
1057+
'help_text': 'Which students will be offered a completion certificate via the student certificate module \
1058+
following the end of the program? If based on class attendance, the certificate will be filtered to \
1059+
only attended classes (as will the program printable version).',
1060+
'default': 'program_attendance',
1061+
'category': 'learn',
1062+
'is_setting': True,
1063+
'field': forms.ChoiceField(choices=[('all', 'All students'),
1064+
('program_attendance', 'Only students who attended the program'),
1065+
('class_attendance', 'Only students who attended at least one class')]),
1066+
},
10551067
}
10561068

10571069
# Dictionary of categories that tags fall into (for grouping on the tag settings page)

esp/templates/outlines/letter_base.tex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
\usepackage{subfigure}
1010
\usepackage{boxedminipage}
1111
\usepackage{wallpaper}
12+
\usepackage{tabularx}
1213

1314
\ULCornerWallPaper{1}{% templatetag openbrace %}{{ MEDIA_ROOT }}latex_media/letterhead.pdf}
1415
\pagestyle{empty}

esp/templates/program/modules/programprintables/completion_certificate.tex

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,32 @@
44
{% block maincontent %}
55
{{ block.super }}
66

7-
{\bf {\underline {\Large Certificate of Completion \hspace{2.5in} \normalsize {{ prog.niceName }} {{ date_range }} }}} \\
7+
\noindent {\bf {\underline {\Large Certificate of Completion \hspace{2.5in} \normalsize {{ prog.niceName|texescape }} {{ date_range }} }}} \\
88

99
\vspace{0.3in}
1010

11-
This is to certify that \underline{ {{ user.first_name }} {{ user.last_name }} } attended {{ prog.niceName|texescape }} and completed the following classes: \\
11+
\noindent This is to certify that \underline{% templatetag openbrace %}{{ user.first_name }} {{ user.last_name }}} attended {{ prog.niceName|texescape }} and completed the following classes:
1212

13-
\hspace{1in} {{ schedule }}
13+
\vspace{0.25in} {{ schedule }}
1414

1515
\vspace{0.25in}
1616

1717
{% autoescape off %}
18-
{{ prog.program_type }} courses are taught by volunteers, primarily {{ settings.INSTITUTION_NAME|texescape }} students.
18+
\noindent {{ prog.program_type }} courses are taught by volunteers, primarily {{ settings.INSTITUTION_NAME|texescape }} students.
1919
The content and difficulty of the subjects varies significantly, from non-academic to college-level.
2020
Our program is not accredited to issue credit for any class, however, we leave it to school administrators to decide whether {{ settings.ORGANIZATION_SHORT_NAME|texescape }} courses can be counted as substitutes for school classes.
2121
You may view the teachers' course descriptions on the attached pages.
22-
If you would like to talk to an administrator or teacher about the content of a particular course, please email us at {\tt {{ settings.DEFAULT_EMAIL_ADDRESSES.default|texescape }} }. \\
22+
If you would like to talk to an administrator or teacher about the content of a particular course, please email us at {\tt {{ settings.DEFAULT_EMAIL_ADDRESSES.default|texescape }}}. \\
2323

24-
\hspace{3in}\begin{minipage}{4in}{{ prog.niceName }} Directors \\
24+
\hspace{3in}\begin{minipage}{4in}{{ prog.niceName|texescape }} Directors \\
2525
{{ settings.INSTITUTION_NAME|texescape }} {{ settings.ORGANIZATION_SHORT_NAME|texescape }} \end{minipage}
2626

2727
{% endautoescape %}
2828
{% endblock %}
2929

3030
{% block additionalcontent %}
3131
\clearpage
32-
\noindent {\bf \underline{ {{ prog.niceName }} courses taken by {{ user.first_name }} {{ user.last_name }} } } \\
32+
\noindent {\bf \underline{% templatetag openbrace %}{{ prog.niceName|texescape }} courses taken by {{ user.first_name }} {{ user.last_name }}}} \\
3333
\hspace{0.5in}
3434
{{ descriptions }}
3535
{% endblock %}

esp/templates/program/modules/programprintables/courses_inline.tex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
{% autoescape off %}
33

44
{% for cls in classlist %}
5-
\noindent \textbf{ {{cls.emailcode}}: {{ cls.title|texescape }} } - {{cls.parent_class.getTeacherNames|join:", "|texescape }} ~\\
5+
\noindent \textbf{% templatetag openbrace %}{{cls.emailcode}}: {{ cls.title|texescape }}} - {{cls.parent_class.getTeacherNames|join:", "|texescape }} ~\\
66
77
\noindent {{ cls.parent_class.class_info|texescape }} ~\\
88
9-
\noindent \textit{Open to students in grades {{ cls.parent_class.grade_min }} through {{ cls.parent_class.grade_max }} } ~\\
9+
\noindent \textit{Open to students in grades {{ cls.parent_class.grade_min }} through {{ cls.parent_class.grade_max }}} ~\\
1010
\textit{Maximum size: {{cls.parent_class.class_size_max }} } ~\\
1111
\textit{Meeting times: {{ cls.friendly_times|join:"\\" }} } ~\\
1212
\rule{7in}{0.25pt} ~\\
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{% load latex %}
22
{% autoescape off %}
3-
\begin{tabular}{ll}
3+
\noindent \begin{tabularx}{\textwidth}{p{.6\textwidth}|X}
44
{\bf Course} & {\bf Teacher[s]} \\ \hline
55
{% for cls in classlist %}{{ cls.title|texescape }} & {{cls.parent_class.getTeacherNames|join:", "|texescape }}\\
66
{% endfor %}
7-
\end{tabular}
7+
\end{tabularx}
88
{% endautoescape %}

0 commit comments

Comments
 (0)