|
2 | 2 | Public interface for the submissions app. |
3 | 3 |
|
4 | 4 | """ |
5 | | - |
6 | 5 | import itertools |
7 | 6 | import logging |
8 | 7 | import operator |
|
15 | 14 |
|
16 | 15 | # SubmissionError imported so that code importing this api has access |
17 | 16 | from submissions.errors import ( # pylint: disable=unused-import |
| 17 | + ExternalGraderQueueEmptyError, |
18 | 18 | SubmissionError, |
19 | 19 | SubmissionInternalError, |
20 | 20 | SubmissionNotFoundError, |
21 | 21 | SubmissionRequestError |
22 | 22 | ) |
23 | 23 | from submissions.models import ( |
24 | 24 | DELETED, |
| 25 | + ExternalGraderDetail, |
25 | 26 | Score, |
26 | 27 | ScoreAnnotation, |
27 | 28 | ScoreSummary, |
|
48 | 49 | TOP_SUBMISSIONS_CACHE_TIMEOUT = 300 |
49 | 50 |
|
50 | 51 |
|
51 | | -def create_submission(student_item_dict, answer, submitted_at=None, attempt_number=None, team_submission=None): |
52 | | - """Creates a submission for assessment. |
| 52 | +# pylint: disable=unused-argument |
| 53 | +def create_external_grader_detail(student_item_dict, |
| 54 | + answer, |
| 55 | + queue_name: str, |
| 56 | + grader_file_name="", |
| 57 | + points_possible=1, |
| 58 | + **external_grader_additional_data |
| 59 | + ): |
| 60 | + """ |
| 61 | + Creates a submission and an associated ExternalGraderDetail record. |
| 62 | +
|
| 63 | + Args: |
| 64 | + student_item_dict (dict): The student_item this submission is associated with. |
| 65 | + This is used to determine which course, student, and location the submission belongs to. |
| 66 | +
|
| 67 | + answer (JSON-serializable): The answer given by the student to be assessed. |
| 68 | +
|
| 69 | + queue_name (str): The name of the queue for the external grader. |
| 70 | +
|
| 71 | + grader_file_name (str, optional): The name of the grader file. Defaults to "". |
| 72 | +
|
| 73 | + points_possible (int, optional): The maximum possible points for this submission. Defaults to 1. |
| 74 | +
|
| 75 | + external_grader_additional_data: Additional keyword arguments that may be used for the external grader. |
| 76 | +
|
| 77 | + Returns: |
| 78 | + ExternalGraderDetail: The created external grader detail record that references the submission. |
| 79 | +
|
| 80 | + Raises: |
| 81 | + ExternalGraderQueueEmptyError: If queue_name is empty. |
| 82 | + SubmissionInternalError: If there's an error creating the submission or external grader detail. |
| 83 | + """ |
| 84 | + |
| 85 | + submission = create_submission(student_item_dict, answer) |
| 86 | + submission_uuid = submission.get('uuid') |
| 87 | + |
| 88 | + if not queue_name: |
| 89 | + raise ExternalGraderQueueEmptyError("The parameter queue_name can not be empty.") |
| 90 | + |
| 91 | + try: |
| 92 | + instance = ExternalGraderDetail.create_from_uuid( |
| 93 | + submission_uuid=submission_uuid, |
| 94 | + queue_name=queue_name, |
| 95 | + grader_file_name=grader_file_name, |
| 96 | + points_possible=points_possible, |
| 97 | + |
| 98 | + ) |
| 99 | + return instance |
| 100 | + |
| 101 | + except DatabaseError as error: |
| 102 | + error_message = ( |
| 103 | + f"An error occurred while creating external grader for submission {submission_uuid}" |
| 104 | + ) |
| 105 | + logger.exception(error_message) |
| 106 | + raise SubmissionInternalError(error_message) from error |
| 107 | + |
| 108 | + |
| 109 | +def create_submission( |
| 110 | + student_item_dict, |
| 111 | + answer, |
| 112 | + submitted_at=None, |
| 113 | + attempt_number=None, |
| 114 | + team_submission=None, |
| 115 | +): |
| 116 | + """ |
| 117 | + Creates a submission for assessment. |
53 | 118 |
|
54 | 119 | Generic means by which to submit an answer for assessment. |
55 | 120 |
|
56 | 121 | Args: |
57 | | - student_item_dict (dict): The student_item this |
58 | | - submission is associated with. This is used to determine which |
59 | | - course, student, and location this submission belongs to. |
| 122 | + student_item_dict (dict): The student_item this submission is associated with. |
| 123 | + This is used to determine which course, student, and location this |
| 124 | + submission belongs to. |
60 | 125 |
|
61 | 126 | answer (JSON-serializable): The answer given by the student to be assessed. |
62 | 127 |
|
63 | | - submitted_at (datetime): The date in which this submission was submitted. |
| 128 | + submitted_at (datetime, optional): The date on which this submission was submitted. |
64 | 129 | If not specified, defaults to the current date. |
65 | 130 |
|
66 | | - attempt_number (int): A student may be able to submit multiple attempts |
| 131 | + attempt_number (int, optional): A student may be able to submit multiple attempts |
67 | 132 | per question. This allows the designated attempt to be overridden. |
68 | 133 | If the attempt is not specified, it will take the most recent |
69 | 134 | submission, as specified by the submitted_at time, and use its |
70 | 135 | attempt_number plus one. |
71 | 136 |
|
| 137 | + team_submission (TeamSubmission, optional): The team submission this individual |
| 138 | + submission is associated with, if any. |
| 139 | +
|
72 | 140 | Returns: |
73 | | - dict: A representation of the created Submission. The submission |
74 | | - contains five attributes: student_item, attempt_number, submitted_at, |
75 | | - created_at, and answer. 'student_item' is the ID of the related student |
76 | | - item for the submission. 'attempt_number' is the attempt this submission |
77 | | - represents for this question. 'submitted_at' represents the time this |
78 | | - submission was submitted, which can be configured, versus the |
79 | | - 'created_at' date, which is when the submission is first created. |
| 141 | + dict: A representation of the created Submission. The submission contains |
| 142 | + five attributes: student_item, attempt_number, submitted_at, created_at, |
| 143 | + and answer. |
| 144 | +
|
| 145 | + The returned dictionary includes: |
| 146 | + - student_item: ID of the related student item for the submission |
| 147 | + - attempt_number: Attempt this submission represents for this question |
| 148 | + - submitted_at: Time this submission was submitted |
| 149 | + - created_at: Time the submission was first created |
| 150 | + - answer: The submitted answer |
80 | 151 |
|
81 | 152 | Raises: |
82 | 153 | SubmissionRequestError: Raised when there are validation errors for the |
83 | | - student item or submission. This can be caused by the student item |
84 | | - missing required values, the submission being too long, the |
85 | | - attempt_number is negative, or the given submitted_at time is invalid. |
86 | | - SubmissionInternalError: Raised when submission access causes an |
87 | | - internal error. |
| 154 | + student item or submission. This can occur due to: |
| 155 | + - Student item missing required values |
| 156 | + - Submission being too long |
| 157 | + - Attempt number is negative |
| 158 | + - Submitted time is invalid |
| 159 | +
|
| 160 | + SubmissionInternalError: Raised when submission access causes an internal error. |
88 | 161 |
|
89 | 162 | Examples: |
90 | | - >>> student_item_dict = dict( |
91 | | - >>> student_id="Tim", |
92 | | - >>> item_id="item_1", |
93 | | - >>> course_id="course_1", |
94 | | - >>> item_type="type_one" |
95 | | - >>> ) |
96 | | - >>> create_submission(student_item_dict, "The answer is 42.", datetime.utcnow, 1) |
| 163 | + >>> student_item_dict = { |
| 164 | + ... "student_id": "Tim", |
| 165 | + ... "item_id": "item_1", |
| 166 | + ... "course_id": "course_1", |
| 167 | + ... "item_type": "type_one" |
| 168 | + ... } |
| 169 | + >>> create_submission(student_item_dict, "The answer is 42.", datetime.utcnow(), 1) |
97 | 170 | { |
98 | 171 | 'student_item': 2, |
99 | 172 | 'attempt_number': 1, |
100 | | - 'submitted_at': datetime.datetime(2014, 1, 29, 17, 14, 52, 649284 tzinfo=<UTC>), |
| 173 | + 'submitted_at': datetime.datetime(2014, 1, 29, 17, 14, 52, 649284, tzinfo=<UTC>), |
101 | 174 | 'created_at': datetime.datetime(2014, 1, 29, 17, 14, 52, 668850, tzinfo=<UTC>), |
102 | | - 'answer': u'The answer is 42.' |
| 175 | + 'answer': 'The answer is 42.' |
103 | 176 | } |
104 | | -
|
105 | 177 | """ |
106 | 178 | student_item_model = _get_or_create_student_item(student_item_dict) |
107 | 179 | if attempt_number is None: |
108 | | - first_submission = None |
109 | 180 | attempt_number = 1 |
110 | 181 | try: |
111 | 182 | first_submission = Submission.objects.filter(student_item=student_item_model).first() |
@@ -134,6 +205,7 @@ def create_submission(student_item_dict, answer, submitted_at=None, attempt_numb |
134 | 205 | submission_serializer = SubmissionSerializer(data=model_kwargs) |
135 | 206 | if not submission_serializer.is_valid(): |
136 | 207 | raise SubmissionRequestError(field_errors=submission_serializer.errors) |
| 208 | + |
137 | 209 | submission_serializer.save() |
138 | 210 |
|
139 | 211 | sub_data = submission_serializer.data |
|
0 commit comments