-# Copyright
+# Copyright (C) 2012 W. Trevor King <wking@tremily.us>
+#
+# This file is part of pygrader.
+#
+# pygrader is free software: you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# pygrader is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# pygrader. If not, see <http://www.gnu.org/licenses/>.
import io as _io
from jinja2 import Template
from . import LOG as _LOG
-from .email import construct_email as _construct_email
+from .email import construct_text_email as _construct_text_email
from .email import send_emails as _send_emails
from .storage import set_notified as _set_notified
from .tabulate import tabulate as _tabulate
#{{ grade.comment|wordwrap }}
STUDENT_TEMPLATE = Template("""
-{{ grades[0].student.alias() }},
+{{ target }},
Grades:
{%- for grade in grades %}
{%- endfor %}
Comments:
-{%- for grade in grades -%}
-{% if grade.comment %}
+{%- for grade in grades -%}{% if grade.comment %}
{{ grade.assignment.name }}
{{ grade.comment }}
-{%- endif %}
-{% endfor %}
+{%- endif %}{% endfor %}
+
Yours,
{{ author.alias() }}
""".strip())
""".strip())
-
-
class NotifiedCallback (object):
"""A callback for marking notifications with `_send_emails`
"""
return ''.join(ret)
def assignment_email(basedir, author, course, assignment, student=None,
- cc=None, smtp=None, use_color=False, debug_target=None,
- dry_run=False):
+ cc=None, smtp=None, debug_target=None, dry_run=False):
"""Send each student an email with their grade on `assignment`
"""
_send_emails(
emails=_assignment_email(
basedir=basedir, author=author, course=course,
assignment=assignment, student=student, cc=cc),
- smtp=smtp, use_color=use_color,
- debug_target=debug_target, dry_run=dry_run)
+ smtp=smtp, debug_target=debug_target, dry_run=dry_run)
def _assignment_email(basedir, author, course, assignment, student=None,
cc=None):
Yours,
Jack
"""
- return _construct_email(
+ return _construct_text_email(
author=author, targets=[grade.student], cc=cc,
subject='Your {} grade'.format(grade.assignment.name),
text=ASSIGNMENT_TEMPLATE.render(author=author, grade=grade))
def student_email(basedir, author, course, student=None, cc=None, old=False,
- smtp=None, use_color=False, debug_target=None,
- dry_run=False):
+ smtp=None, debug_target=None, dry_run=False):
"""Send each student an email with their grade to date
"""
_send_emails(
emails=_student_email(
basedir=basedir, author=author, course=course, student=student,
cc=cc, old=old),
- smtp=smtp, use_color=use_color, debug_target=debug_target,
- dry_run=dry_run)
+ smtp=smtp, debug_target=debug_target, dry_run=dry_run)
-def _student_email(basedir, author, course, student=None, cc=None, old=False):
+def _student_email(basedir, author, course, student=None, targets=None, cc=None, old=False):
"""Iterate through composed student `Message`\s
"""
if student:
grades = [g for g in grades if not g.notified]
if not grades:
continue
- yield (construct_student_email(author=author, grades=grades, cc=cc),
+ yield (construct_student_email(
+ author=author, course=course, grades=grades, targets=targets,
+ cc=cc),
NotifiedCallback(basedir=basedir, grades=grades))
-def construct_student_email(author, grades, cc=None):
+def construct_student_email(author, course, grades, targets=None, cc=None):
"""Construct a `Message` notfiying a student of `grade`
>>> from pygrader.model.person import Person
>>> from pygrader.model.assignment import Assignment
+ >>> from pygrader.model.course import Course
>>> from pygrader.model.grade import Grade
+ >>> course = Course(name='Physics 101')
>>> author = Person(name='Jack', emails=['a@b.net'])
>>> student = Person(name='Jill', emails=['c@d.net'])
>>> grades = []
... student=student, assignment=assignment,
... points=int(points/2.0))
... grades.append(grade)
- >>> msg = construct_student_email(author=author, grades=grades)
- >>> print(msg.as_string())
- ... # doctest: +REPORT_UDIFF, +ELLIPSIS, +NORMALIZE_WHITESPACE
+ >>> msg = construct_student_email(
+ ... author=author, course=course, grades=grades)
+ >>> print(msg.as_string().replace('\\t', ' '))
+ ... # doctest: +REPORT_UDIFF, +ELLIPSIS
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
From: Jack <a@b.net>
Reply-to: Jack <a@b.net>
To: Jill <c@d.net>
- Subject: Your grade
+ Subject: Physics 101 grades
<BLANKLINE>
Jill,
<BLANKLINE>
Grades:
- * Exam 1:\t5 out of 10 available points.
- * Homework 1:\t1 out of 3 available points.
+ * Exam 1: 5 out of 10 available points.
+ * Homework 1: 1 out of 3 available points.
<BLANKLINE>
Comments:
<BLANKLINE>
- <BLANKLINE>
Yours,
Jack
>>> grades[0].comment = ('Bla bla bla. '*20).strip()
>>> grades[1].comment = ('Hello world')
- >>> msg = construct_student_email(author=author, grades=grades)
- >>> print(msg.as_string())
- ... # doctest: +REPORT_UDIFF, +ELLIPSIS, +NORMALIZE_WHITESPACE
+ >>> msg = construct_student_email(
+ ... author=author, course=course, grades=grades)
+ >>> print(msg.as_string().replace('\\t', ' '))
+ ... # doctest: +REPORT_UDIFF, +ELLIPSIS
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
From: Jack <a@b.net>
Reply-to: Jack <a@b.net>
To: Jill <c@d.net>
- Subject: Your grade
+ Subject: Physics 101 grades
<BLANKLINE>
Jill,
<BLANKLINE>
Grades:
- * Exam 1:\t5 out of 10 available points.
- * Homework 1:\t1 out of 3 available points.
+ * Exam 1: 5 out of 10 available points.
+ * Homework 1: 1 out of 3 available points.
<BLANKLINE>
Comments:
<BLANKLINE>
<BLANKLINE>
Hello world
<BLANKLINE>
+ Homework 1
+ <BLANKLINE>
+ Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla.
+ <BLANKLINE>
+ Yours,
+ Jack
+
+ >>> grades[0].comment = 'Work harder!'
+ >>> grades[1].comment = None
+ >>> msg = construct_student_email(
+ ... author=author, course=course, grades=grades)
+ >>> print(msg.as_string().replace('\\t', ' '))
+ ... # doctest: +REPORT_UDIFF, +ELLIPSIS
+ Content-Type: text/plain; charset="us-ascii"
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Disposition: inline
+ Date: ...
+ From: Jack <a@b.net>
+ Reply-to: Jack <a@b.net>
+ To: Jill <c@d.net>
+ Subject: Physics 101 grades
+ <BLANKLINE>
+ Jill,
+ <BLANKLINE>
+ Grades:
+ * Exam 1: 5 out of 10 available points.
+ * Homework 1: 1 out of 3 available points.
<BLANKLINE>
+ Comments:
<BLANKLINE>
Homework 1
<BLANKLINE>
- Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla
- bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla
- bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla. Bla bla bla.
- Bla bla bla. Bla bla bla. Bla bla bla.
+ Work harder!
<BLANKLINE>
+ Yours,
+ Jack
+
+ You can also send the student grades to alternative targets:
+
+ >>> prof = Person(name='H.D.', emails=['hd@wall.net'])
+ >>> msg = construct_student_email(
+ ... author=author, course=course, grades=grades, targets=[prof])
+ >>> print(msg.as_string().replace('\\t', ' '))
+ ... # doctest: +REPORT_UDIFF, +ELLIPSIS
+ Content-Type: text/plain; charset="us-ascii"
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Disposition: inline
+ Date: ...
+ From: Jack <a@b.net>
+ Reply-to: Jack <a@b.net>
+ To: "H.D." <hd@wall.net>
+ Subject: Physics 101 grades for Jill
+ <BLANKLINE>
+ H.D.,
+ <BLANKLINE>
+ Grades:
+ * Exam 1: 5 out of 10 available points.
+ * Homework 1: 1 out of 3 available points.
+ <BLANKLINE>
+ Comments:
+ <BLANKLINE>
+ Homework 1
+ <BLANKLINE>
+ Work harder!
<BLANKLINE>
Yours,
Jack
"""
students = set(g.student for g in grades)
assert len(students) == 1, students
- return _construct_email(
- author=author, targets=[grades[0].student], cc=cc,
- subject='Your grade',
- text=STUDENT_TEMPLATE.render(author=author, grades=sorted(grades)))
+ student = students.pop()
+ subject = '{} grades'.format(course.name)
+ if not targets:
+ targets = [student]
+ else:
+ subject += ' for {}'.format(student.name)
+ target = join_with_and([t.alias() for t in targets])
+ return _construct_text_email(
+ author=author, targets=targets, cc=cc, subject=subject,
+ text=STUDENT_TEMPLATE.render(
+ author=author, target=target, grades=sorted(grades)))
def course_email(basedir, author, course, targets, assignment=None,
- student=None, cc=None, smtp=None, use_color=False,
- debug_target=None, dry_run=False):
+ student=None, cc=None, smtp=None, debug_target=None,
+ dry_run=False):
"""Send the professor an email with all student grades to date
"""
_send_emails(
emails=_course_email(
basedir=basedir, author=author, course=course, targets=targets,
assignment=assignment, student=student, cc=cc),
- smtp=smtp, use_color=use_color, debug_target=debug_target,
- dry_run=dry_run)
+ smtp=smtp, debug_target=debug_target, dry_run=dry_run)
def _course_email(basedir, author, course, targets, assignment=None,
student=None, cc=None):
... assignments=assignments, people=[student], grades=grades)
>>> msg = construct_course_email(
... author=author, course=course, targets=[prof])
- >>> print(msg.as_string())
- ... # doctest: +REPORT_UDIFF, +ELLIPSIS, +NORMALIZE_WHITESPACE
+ >>> print(msg.as_string().replace('\\t', ' '))
+ ... # doctest: +REPORT_UDIFF, +ELLIPSIS
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
<BLANKLINE>
Here are the (tab delimited) course grades to date:
<BLANKLINE>
- Student\tExam 1\tHomework 1\tTotal
- Jill\t5\t1\t0.416...
+ Student Exam 1 Homework 1 Total
+ Jill 5 1 0.416...
--
- Mean\t5.00\t1.00\t0.416...
- Std. Dev.\t0.00\t0.00\t0.0
+ Mean 5.00 1.00 0.416...
+ Std. Dev. 0.00 0.00 0.0
<BLANKLINE>
The available points (and weights) for each assignment are:
- * Exam 1:\t10\t0.5
- * Homework 1:\t3\t0.5
+ * Exam 1: 10 0.5
+ * Homework 1: 3 0.5
<BLANKLINE>
Yours,
Jack
"""
target = join_with_and([t.alias() for t in targets])
table = _io.StringIO()
- _tabulate(course=course, statistics=True, stream=table)
- return _construct_email(
+ _tabulate(course=course, statistics=True, stream=table, use_color=False)
+ return _construct_text_email(
author=author, targets=targets, cc=cc,
subject='Course grades',
text=COURSE_TEMPLATE.render(