pgp: don't assume protocol/micalg ordering in doctest output.
[pygrader.git] / pygrader / handler / get.py
index e354d2ad1d9700971ebf2e8edabab15458ad861e..626e0e0bb11ccebea35dc57b0e49d22d18289a96 100644 (file)
@@ -1,4 +1,18 @@
-# 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/>.
 
 """Handle information requests
 
@@ -16,23 +30,18 @@ import pgp_mime as _pgp_mime
 from .. import LOG as _LOG
 from ..email import construct_text_email as _construct_text_email
 from ..email import construct_email as _construct_email
+from ..extract_mime import message_time as _message_time
 from ..storage import assignment_path as _assignment_path
 from ..tabulate import tabulate as _tabulate
 from ..template import _student_email as _student_email
-from . import InvalidMessage as _InvalidMessage
-from . import InvalidSubjectMessage as _InvalidSubjectMessage
+from . import get_subject_assignment as _get_subject_assignment
+from . import get_subject_student as _get_subject_student
+from . import InvalidStudentSubject as _InvalidStudentSubject
+from . import InvalidAssignmentSubject as _InvalidAssignmentSubject
 from . import Response as _Response
 from . import UnsignedMessage as _UnsignedMessage
 
 
-class InvalidStudent (_InvalidSubjectMessage):
-    def __init__(self, students=None, **kwargs):
-        if 'error' not in kwargs:
-            kwargs['error'] = 'Subject matches multiple students'
-        super(InvalidStudent, self).__init__(kwargs)
-        self.students = students
-
-
 def run(basedir, course, message, person, subject,
         trust_email_infrastructure=False, dry_run=False, **kwargs):
     """
@@ -126,7 +135,7 @@ def run(basedir, course, message, person, subject,
     ...     person=person, subject='[get]', max_late=0)
     ... # doctest: +ELLIPSIS, +REPORT_UDIFF
     respond with:
-    Content-Type: multipart/signed; protocol="application/pgp-signature"; micalg="pgp-sha1"; boundary="===============...=="
+    Content-Type: multipart/signed; ...protocol="application/pgp-signature"; ...boundary="===============...=="
     MIME-Version: 1.0
     Content-Disposition: inline
     Date: ...
@@ -181,7 +190,7 @@ def run(basedir, course, message, person, subject,
     ...     person=person, subject='[get]', max_late=0)
     ... # doctest: +ELLIPSIS, +REPORT_UDIFF
     respond with:
-    Content-Type: multipart/signed; protocol="application/pgp-signature"; micalg="pgp-sha1"; boundary="===============...=="
+    Content-Type: multipart/signed; ...protocol="application/pgp-signature"; ...boundary="===============...=="
     MIME-Version: 1.0
     Content-Disposition: inline
     Date: ...
@@ -224,7 +233,7 @@ def run(basedir, course, message, person, subject,
     ...     max_late=0)
     ... # doctest: +ELLIPSIS, +REPORT_UDIFF
     respond with:
-    Content-Type: multipart/signed; protocol="application/pgp-signature"; micalg="pgp-sha1"; boundary="===============...=="
+    Content-Type: multipart/signed; ...protocol="application/pgp-signature"; ...boundary="===============...=="
     MIME-Version: 1.0
     Content-Disposition: inline
     Date: ...
@@ -296,7 +305,7 @@ def run(basedir, course, message, person, subject,
     ...     max_late=0)
     ... # doctest: +ELLIPSIS, +REPORT_UDIFF
     respond with:
-    Content-Type: multipart/signed; protocol="application/pgp-signature"; micalg="pgp-sha1"; boundary="===============...=="
+    Content-Type: multipart/signed; ...protocol="application/pgp-signature"; ...boundary="===============...=="
     MIME-Version: 1.0
     Content-Disposition: inline
     Date: ...
@@ -364,7 +373,7 @@ def run(basedir, course, message, person, subject,
             hasattr(message, 'authenticated') and message.authenticated)
     if not authenticated:
         raise _UnsignedMessage()
-    if 'assistants' in person.groups or 'professors' in person.groups:
+    if person.is_admin():
         email = _get_admin_email(
             basedir=basedir, course=course, person=person, subject=subject)
     elif 'students' in person.groups:
@@ -419,9 +428,10 @@ def _get_student_submission_email(
     for assignment in assignments:
         grade = course.grade(student=student, assignment=assignment)
         if grade is not None:
-            message.attach(_pgp_mime.encodedMIMEText(
-                    '{} grade: {}\n\n{}\n'.format(
-                        assignment.name, grade.points, grade.comment)))
+            text = '{} grade: {}\n'.format(assignment.name, grade.points)
+            if grade.comment:
+                text += '\n{}\n'.format(grade.comment)
+            message.attach(_pgp_mime.encodedMIMEText(text))
         assignment_path = _assignment_path(basedir, assignment, student)
         mpath = _os_path.join(assignment_path, 'mail')
         try:
@@ -429,17 +439,27 @@ def _get_student_submission_email(
         except _mailbox.NoSuchMailboxError as e:
             pass
         else:
-            for msg in mbox:
+            messages = []
+            for key,msg in mbox.items():
+                subpath = mbox._lookup(key)
+                if subpath.endswith('.gitignore'):
+                    _LOG.debug('skipping non-message {}'.format(subpath))
+                    continue
+                messages.append(msg)
+            messages.sort(key=_message_time)
+            for msg in messages:
                 message.attach(_MIMEMessage(msg))
     return _construct_email(
         author=course.robot, targets=[person], subject=subject,
         message=message)
 
 def _get_admin_email(basedir, course, person, subject):
-    lsubject = subject.lower()
-    students = [p for p in course.find_people()
-                if p.name.lower() in lsubject]
-    if len(students) == 0:
+    try:
+        student = _get_subject_student(course, subject)
+    except _InvalidStudentSubject as error:
+        if error.students:  # several students
+            raise
+        # no students
         _LOG.debug('construct course grades email for {}'.format(person))
         stream = _io.StringIO()
         _tabulate(
@@ -449,17 +469,20 @@ def _get_admin_email(basedir, course, person, subject):
             author=course.robot, targets=[person],
             subject='All grades for {}'.format(course.name),
             text=text)
-    elif len(students) == 1:
-        student = students[0]
-        assignments = [a for a in course.assignments
-                       if a.name.lower() in lsubject]
-        if len(assignments) == 0:
-            email = _get_student_email(
-                basedir=basedir, course=course, person=person, student=student)
-        else:
+    else:  # a single student
+        try:
+            assignment = _get_subject_assignment(course, subject)
+        except _InvalidAssignmentSubject as error:
+            if error.assignments:  # several assignments
+                email = _get_student_submission_email(
+                    basedir=basedir, course=course, person=person,
+                    student=student, assignments=error.assignments)
+            else: # no assignments
+                email = _get_student_email(
+                    basedir=basedir, course=course, person=person,
+                    student=student)
+        else:  # a single assignment
             email = _get_student_submission_email(
                 basedir=basedir, course=course, person=person, student=student,
-                assignments=assignments)
-    else:
-        raise InvalidStudent(students=students)
+                assignments=[assignment])
     return email