self.subject = subject
+class InvalidStudentSubject (InvalidSubjectMessage):
+ def __init__(self, students=None, **kwargs):
+ if 'error' not in kwargs:
+ if students:
+ kwargs['error'] = 'Subject matches multiple students'
+ else:
+ kwargs['error'] = "Subject doesn't match any student"
+ super(InvalidStudentSubject, self).__init__(**kwargs)
+ self.students = students
+
+
+class InvalidAssignmentSubject (InvalidSubjectMessage):
+ def __init__(self, assignments=None, **kwargs):
+ if 'error' not in kwargs:
+ if assignments:
+ kwargs['error'] = 'Subject matches multiple assignments'
+ else:
+ kwargs['error'] = "Subject doesn't match any assignment"
+ super(InvalidAssignmentSubject, self).__init__(kwargs)
+ self.assignments = assignments
+
+
class Response (Exception):
"""Exception to bubble out email responses.
super(Response, self).__init__()
self.message = message
self.complete = complete
+
+
+def get_subject_student(course, subject):
+ lsubject = subject.lower()
+ students = [p for p in course.find_people()
+ if p.name.lower() in lsubject]
+ if len(students) == 1:
+ return students[0]
+ raise InvalidStudentSubject(students=students, subject=subject)
+
+def get_subject_assignment(course, subject):
+ lsubject = subject.lower()
+ assignments = [a for a in course.assignments
+ if a.name.lower() in lsubject]
+ if len(assignments) == 1:
+ return assignments[0]
+ raise InvalidAssignmentSubject(assignments=assignments, subject=subject)
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):
"""
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(
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
from ..extract_mime import message_time as _message_time
from ..storage import assignment_path as _assignment_path
from ..storage import set_late as _set_late
+from . import get_subject_assignment as _get_subject_assignment
from . import InvalidMessage as _InvalidMessage
from . import Response as _Response
-class InvalidAssignment (_InvalidMessage):
- def __init__(self, assignment, **kwargs):
+class InvalidSubmission (_InvalidMessage):
+ def __init__(self, assignment=None, **kwargs):
if 'error' not in kwargs:
- kwargs['error'] = 'Received invalid {} submission'.format(
- assignment.name)
- super(InvalidAssignment, self).__init__(**kwargs)
+ kwargs['error'] = 'invalid submission'
+ super(InvalidSubmission, self).__init__(**kwargs)
self.assignment = assignment
message['Subject'] = 'Received {} submission'.format(assignment.name)
raise _Response(message=message)
-def _match_assignment(assignment, subject):
- return assignment.name.lower() in subject.lower()
-
def _get_assignment(course, subject):
- assignments = [a for a in course.assignments
- if _match_assignment(a, subject)]
- if len(assignments) != 1:
- if len(assignments) == 0:
- response_subject = 'no assignment found in {!r}'.format(subject)
- error = (
- 'does not match any submittable assignment name\n'
- 'for {}.\n').format(course.name)
- else:
- response_subject = 'several assignments found in {!r}'.format(
- subject)
- error = (
- 'matches several submittable assignment names\n'
- 'for {}: * {}\n').format(
- course.name,
- '\n * '.join(a.name for a in assignments))
- submittable_assignments = [
- a for a in course.assignments if a.submittable]
- if not submittable_assignments:
- hint = (
- 'In fact, there are no submittable assignments for\n'
- 'this course!')
- else:
- hint = (
- 'Remember to use the full name for the assignment in the\n'
- 'subject. For example:\n'
- ' {} submission').format(
- submittable_assignments[0].name)
- message = _pgp_mime.encodedMIMEText((
- 'We got an email from you with the following subject:\n'
- ' {!r}\n'
- 'which {}.\n\n'
- '{}\n').format(subject, course.name, hint))
- message['Subject'] = response_subject
- raise _Response(
- message=message, exception=ValueError(response_subject))
- assignment = assignments[0]
-
+ assignment = _get_subject_assignment(course, subject)
if not assignment.submittable:
- raise InvalidAssignment(assignment)
- return assignments[0]
+ raise InvalidSubmission(assignment=assignment)
+ return assignment
def _save_local_message_copy(msg, person, assignment_path, dry_run=False):
try:
from .extract_mime import message_time as _message_time
from .model.person import Person as _Person
+from .handler import InsecureMessage as _InsecureMessage
+from .handler import InvalidAssignmentSubject as _InvalidAssignmentSubject
from .handler import InvalidMessage as _InvalidMessage
+from .handler import InvalidStudentSubject as _InvalidStudentSubject
from .handler import InvalidSubjectMessage as _InvalidSubjectMessage
from .handler import Response as _Response
from .handler import UnsignedMessage as _UnsignedMessage
-from .handler import InsecureMessage as _InsecureMessage
-from .handler.get import InvalidStudent as _InvalidStudent
from .handler.get import run as _handle_get
-from .handler.submission import InvalidAssignment as _InvalidAssignment
from .handler.submission import run as _handle_submission
+from .handler.submission import InvalidSubmission as _InvalidSubmission
_TAG_REGEXP = _re.compile('^.*\[([^]]*)\].*$')
>>> message['Return-Path'] = '<bb@greyhavens.net>'
>>> process(message) # doctest: +REPORT_UDIFF, +ELLIPSIS
respond with:
+ Content-Type: multipart/signed; protocol="application/pgp-signature"; micalg="pgp-sha1"; boundary="===============...=="
+ MIME-Version: 1.0
+ Content-Disposition: inline
+ Date: ...
+ From: Robot101 <phys101@tower.edu>
+ Reply-to: Robot101 <phys101@tower.edu>
+ To: Bilbo Baggins <bb@shire.org>
+ Subject: Received Assignment 1 submission
+ <BLANKLINE>
+ --===============...==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Disposition: inline
Yours,
phys-101 robot
<BLANKLINE>
+ --===============...==
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Description: OpenPGP digital signature
+ Content-Type: application/pgp-signature; name="signature.asc"; charset="us-ascii"
+ <BLANKLINE>
+ -----BEGIN PGP SIGNATURE-----
+ Version: GnuPG v2.0.19 (GNU/Linux)
+ <BLANKLINE>
+ ...
+ -----END PGP SIGNATURE-----
+ <BLANKLINE>
+ --===============...==--
>>> course.print_tree() # doctest: +REPORT_UDIFF, +ELLIPSIS
Bilbo_Baggins
... 'for <wking@tremily.us>; Mon, 09 Oct 2011 11:50:46 -0400 (EDT)')
>>> process(message) # doctest: +REPORT_UDIFF, +ELLIPSIS
respond with:
+ Content-Type: multipart/signed; protocol="application/pgp-signature"; micalg="pgp-sha1"; boundary="===============...=="
+ MIME-Version: 1.0
+ Content-Disposition: inline
+ Date: ...
+ From: Robot101 <phys101@tower.edu>
+ Reply-to: Robot101 <phys101@tower.edu>
+ To: Bilbo Baggins <bb@shire.org>
+ Subject: Received Assignment 1 submission
+ <BLANKLINE>
+ --===============...==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Disposition: inline
Yours,
phys-101 robot
<BLANKLINE>
+ --===============...==
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Description: OpenPGP digital signature
+ Content-Type: application/pgp-signature; name="signature.asc"; charset="us-ascii"
+ <BLANKLINE>
+ -----BEGIN PGP SIGNATURE-----
+ Version: GnuPG v2.0.19 (GNU/Linux)
+ <BLANKLINE>
+ ...
+ -----END PGP SIGNATURE-----
+ <BLANKLINE>
+ --===============...==--
+
>>> course.print_tree() # doctest: +REPORT_UDIFF, +ELLIPSIS
Bilbo_Baggins
Bilbo_Baggins/Assignment_1
>>> message['Message-ID'] = '<hgi.jlk@home.net>'
>>> process(message) # doctest: +REPORT_UDIFF, +ELLIPSIS
respond with:
+ Content-Type: multipart/signed; protocol="application/pgp-signature"; micalg="pgp-sha1"; boundary="===============...=="
+ MIME-Version: 1.0
+ Content-Disposition: inline
+ Date: ...
+ From: Robot101 <phys101@tower.edu>
+ Reply-to: Robot101 <phys101@tower.edu>
+ To: Bilbo Baggins <bb@shire.org>
+ Subject: Received Assignment 1 submission
+ <BLANKLINE>
+ --===============...==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Disposition: inline
Yours,
phys-101 robot
<BLANKLINE>
+ --===============...==
+ MIME-Version: 1.0
+ Content-Transfer-Encoding: 7bit
+ Content-Description: OpenPGP digital signature
+ Content-Type: application/pgp-signature; name="signature.asc"; charset="us-ascii"
+ <BLANKLINE>
+ -----BEGIN PGP SIGNATURE-----
+ Version: GnuPG v2.0.19 (GNU/Linux)
+ <BLANKLINE>
+ ...
+ -----END PGP SIGNATURE-----
+ <BLANKLINE>
+ --===============...==--
Response to a submission on an unsubmittable assignment:
author = error.course.robot
target = getattr(error, 'person', None)
subject = str(error)
- if isinstance(error, InvalidHandlerMessage):
+ if isinstance(error, _InvalidSubmission):
+ subject = 'Received invalid {} submission'.format(
+ error.assignment.name)
+ text = (
+ 'We received your submission for {}, but you are not\n'
+ 'allowed to submit that assignment via email.'
+ ).format(error.assignment.name)
+ elif isinstance(error, InvalidHandlerMessage):
targets = sorted(error.handlers.keys())
if not targets:
hint = (
'or TA.').format(error.course.name)
elif isinstance(error, NoReturnPath):
return
+ elif isinstance(error, _InvalidAssignmentSubject):
+ if error.assignments:
+ hint = (
+ 'but it matches several assignments:\n'
+ ' * {}').format('\n * '.join(
+ a.name for a in error.assignments))
+ else:
+ # prefer a submittable example assignment
+ assignments = [
+ a for a in error.course.assignments if a.submittable]
+ assignments += course.assignments # but fall back to any one
+ hint = (
+ 'Remember to use the full name for the assignment in the\n'
+ 'subject. For example:\n'
+ ' {} submission').format(assignments[0].name)
+ text = (
+ 'We got an email from you with the following subject:\n'
+ ' {!r}\n{}').format(error.subject, hint)
+ elif isinstance(error, _InvalidStudentSubject):
+ text = (
+ 'We got an email from you with the following subject:\n'
+ ' {!r}\n'
+ 'but it matches several students:\n'
+ ' * {}').format(
+ error.subject, '\n * '.join(s.name for s in error.students))
elif isinstance(error, _InvalidSubjectMessage):
text = (
'We received an email message from you with an invalid\n'
text = (
'We received an email message from you without a valid\n'
'PGP signature.')
- elif isinstance(error, _InvalidAssignment):
- text = (
- 'We received your submission for {}, but you are not\n'
- 'allowed to submit that assignment via email.'
- ).format(error.assignment.name)
- elif isinstance(error, _InvalidStudent):
- text = (
- 'We got an email from you with the following subject:\n'
- ' {!r}\n'
- 'but it matches several students:\n'
- ' * {}').format(
- error.subject, '\n * '.join(s.name for s in error.students))
elif isinstance(error, _InvalidMessage):
text = subject
else: