3 """Assignment submission handler
5 Allow students to submit assignments via email (if
6 ``Assignment.submittable`` is set).
9 from email.utils import formatdate as _formatdate
10 import mailbox as _mailbox
12 import os.path as _os_path
14 import pgp_mime as _pgp_mime
16 from .. import LOG as _LOG
17 from ..color import GOOD_DEBUG as _GOOD_DEBUG
18 from ..extract_mime import extract_mime as _extract_mime
19 from ..extract_mime import message_time as _message_time
20 from ..storage import assignment_path as _assignment_path
21 from ..storage import set_late as _set_late
22 from . import get_subject_assignment as _get_subject_assignment
23 from . import InvalidMessage as _InvalidMessage
24 from . import Response as _Response
27 class InvalidSubmission (_InvalidMessage):
28 def __init__(self, assignment=None, **kwargs):
29 if 'error' not in kwargs:
30 kwargs['error'] = 'invalid submission'
31 super(InvalidSubmission, self).__init__(**kwargs)
32 self.assignment = assignment
35 def run(basedir, course, message, person, subject, max_late=0, dry_run=None,
38 >>> from pgp_mime.email import encodedMIMEText
39 >>> from ..test.course import StubCourse
40 >>> from . import Response
41 >>> course = StubCourse()
43 ... course.course.find_people(email='bb@greyhavens.net'))[0]
44 >>> message = encodedMIMEText('The answer is 42.')
45 >>> message['Message-ID'] = '<123.456@home.net>'
46 >>> message['Received'] = (
47 ... 'from smtp.home.net (smtp.home.net [123.456.123.456]) '
48 ... 'by smtp.mail.uu.edu (Postfix) with ESMTP id 5BA225C83EF '
49 ... 'for <wking@tremily.us>; Sun, 09 Oct 2011 11:50:46 -0400 (EDT)')
50 >>> subject = '[submit] assignment 1'
52 ... run(basedir=course.basedir, course=course.course, message=message,
53 ... person=person, subject=subject, max_late=0)
54 ... except Response as e:
55 ... print('respond with:')
56 ... print(e.message.as_string())
57 ... # doctest: +ELLIPSIS, +REPORT_UDIFF
59 Content-Type: text/plain; charset="us-ascii"
61 Content-Transfer-Encoding: 7bit
62 Content-Disposition: inline
63 Subject: Received Assignment 1 submission
65 We received your submission for Assignment 1 on ....
69 time = _message_time(message=message)
70 assignment = _get_assignment(course=course, subject=subject)
71 assignment_path = _assignment_path(basedir, assignment, person)
72 _save_local_message_copy(
73 msg=message, person=person, assignment_path=assignment_path,
75 _extract_mime(message=message, output=assignment_path, dry_run=dry_run)
77 basedir=basedir, assignment=assignment, person=person, time=time,
78 max_late=max_late, dry_run=dry_run)
80 time_str = 'on {}'.format(_formatdate(time))
82 time_str = 'at an unknown time'
83 message = _pgp_mime.encodedMIMEText((
84 'We received your submission for {} {}.'
86 assignment.name, time_str))
87 message['Subject'] = 'Received {} submission'.format(assignment.name)
88 raise _Response(message=message)
90 def _get_assignment(course, subject):
91 assignment = _get_subject_assignment(course, subject)
92 if not assignment.submittable:
93 raise InvalidSubmission(assignment=assignment)
96 def _save_local_message_copy(msg, person, assignment_path, dry_run=False):
98 _os.makedirs(assignment_path)
101 mpath = _os_path.join(assignment_path, 'mail')
103 mbox = _mailbox.Maildir(mpath, factory=None, create=not dry_run)
104 except _mailbox.NoSuchMailboxError as e:
105 _LOG.warn('could not open mailbox at {}'.format(mpath))
110 for other_msg in mbox:
111 if other_msg['Message-ID'] == msg['Message-ID']:
115 _LOG.log(_GOOD_DEBUG, 'saving email from {} to {}'.format(
116 person, assignment_path))
117 if mbox is not None and not dry_run:
118 mdmsg = _mailbox.MaildirMessage(msg)
123 _LOG.log(_GOOD_DEBUG, 'already found {} in {}'.format(
124 msg['Message-ID'], mpath))
126 def _check_late(basedir, assignment, person, time, max_late=0, dry_run=False):
127 if time > assignment.due + max_late:
128 dt = time - assignment.due
129 _LOG.warning('{} {} late by {} seconds ({} hours)'.format(
130 person.name, assignment.name, dt, dt/3600.))
132 _set_late(basedir=basedir, assignment=assignment, person=person)