template: remove use_color from template functions.
[pygrader.git] / pygrader / template.py
index 4ec585c88ce4ae31d0fd5501f7a873de189cc0e4..d3f43312c24870640b53cbfc23526e5edf859a35 100644 (file)
@@ -1,11 +1,25 @@
-# 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
@@ -24,7 +38,7 @@ Yours,
 #{{ grade.comment|wordwrap }}
 
 STUDENT_TEMPLATE = Template("""
-{{ grades[0].student.alias() }},
+{{ target }},
 
 Grades:
 {%- for grade in grades %}
@@ -32,14 +46,13 @@ 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())
@@ -60,8 +73,6 @@ Yours,
 """.strip())
 
 
-
-
 class NotifiedCallback (object):
     """A callback for marking notifications with `_send_emails`
     """
@@ -97,16 +108,14 @@ def join_with_and(strings):
     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):
@@ -177,24 +186,22 @@ def construct_assignment_email(author, grade, 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:
@@ -207,15 +214,19 @@ def _student_email(basedir, author, course, student=None, cc=None, old=False):
             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 = []
@@ -225,9 +236,10 @@ def construct_student_email(author, grades, cc=None):
     ...         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
@@ -236,25 +248,25 @@ def construct_student_email(author, grades, cc=None):
     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
@@ -263,13 +275,13 @@ def construct_student_email(author, grades, cc=None):
     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>
@@ -277,37 +289,100 @@ def construct_student_email(author, grades, cc=None):
     <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):
@@ -339,8 +414,8 @@ def construct_course_email(author, course, targets, 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
@@ -355,23 +430,23 @@ def construct_course_email(author, course, targets, cc=None):
     <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(