Commit c724f90f by Stephen Sanchez

Merge branch 'master' into sanchez/debug_submissions_page

parents 6c634b23 f88acf51
...@@ -77,14 +77,38 @@ def create_submission(student_item_dict, answer, submitted_at=None, ...@@ -77,14 +77,38 @@ def create_submission(student_item_dict, answer, submitted_at=None,
attempt_number plus one. attempt_number plus one.
Returns: Returns:
dict: A representation of the created Submission. dict: A representation of the created Submission. The submission
contains five attributes: student_item, attempt_number, submitted_at,
created_at, and answer. 'student_item' is the ID of the related student
item for the submission. 'attempt_number' is the attempt this submission
represents for this question. 'submitted_at' represents the time this
submission was submitted, which can be configured, versus the
'created_at' date, which is when the submission is first created.
Raises: Raises:
SubmissionRequestError: Raised when information regarding the student SubmissionRequestError: Raised when there are validation errors for the
item fails validation. student item or submission. This can be caused by the student item
missing required values, the submission being too long, the
attempt_number is negative, or the given submitted_at time is invalid.
SubmissionInternalError: Raised when submission access causes an SubmissionInternalError: Raised when submission access causes an
internal error. internal error.
Examples:
>>> student_item_dict = dict(
>>> student_id="Tim",
>>> item_id="item_1",
>>> course_id="course_1",
>>> item_type="type_one"
>>> )
>>> create_submission(student_item_dict, "The answer is 42.", datetime.utcnow, 1)
{
'student_item': 2,
'attempt_number': 1,
'submitted_at': datetime.datetime(2014, 1, 29, 17, 14, 52, 649284 tzinfo=<UTC>),
'created_at': datetime.datetime(2014, 1, 29, 17, 14, 52, 668850, tzinfo=<UTC>),
'answer': u'The answer is 42.'
}
""" """
student_item_model = _get_or_create_student_item(student_item_dict) student_item_model = _get_or_create_student_item(student_item_dict)
if attempt_number is None: if attempt_number is None:
...@@ -101,7 +125,8 @@ def create_submission(student_item_dict, answer, submitted_at=None, ...@@ -101,7 +125,8 @@ def create_submission(student_item_dict, answer, submitted_at=None,
try: try:
answer = force_unicode(answer) answer = force_unicode(answer)
except UnicodeDecodeError: except UnicodeDecodeError:
raise SubmissionRequestError(u"Submission answer could not be properly decoded to unicode.") raise SubmissionRequestError(
u"Submission answer could not be properly decoded to unicode.")
model_kwargs = { model_kwargs = {
"student_item": student_item_model, "student_item": student_item_model,
...@@ -112,13 +137,16 @@ def create_submission(student_item_dict, answer, submitted_at=None, ...@@ -112,13 +137,16 @@ def create_submission(student_item_dict, answer, submitted_at=None,
model_kwargs["submitted_at"] = submitted_at model_kwargs["submitted_at"] = submitted_at
try: try:
# Serializer validation requires the student item primary key, rather
# than the student item model itself. Create a copy of the submission
# kwargs and replace the student item model with it's primary key.
validation_data = model_kwargs.copy() validation_data = model_kwargs.copy()
validation_data["student_item"] = student_item_model.pk validation_data["student_item"] = student_item_model.pk
submission_serializer = SubmissionSerializer(data=validation_data) submission_serializer = SubmissionSerializer(data=validation_data)
submission_serializer.is_valid() if not submission_serializer.is_valid():
if submission_serializer.errors:
raise SubmissionRequestError(submission_serializer.errors) raise SubmissionRequestError(submission_serializer.errors)
submission = Submission.objects.create(**model_kwargs) submission_serializer.save()
return submission_serializer.data
except DatabaseError: except DatabaseError:
error_message = u"An error occurred while creating submission {} for student item: {}".format( error_message = u"An error occurred while creating submission {} for student item: {}".format(
model_kwargs, model_kwargs,
...@@ -127,8 +155,6 @@ def create_submission(student_item_dict, answer, submitted_at=None, ...@@ -127,8 +155,6 @@ def create_submission(student_item_dict, answer, submitted_at=None,
logger.exception(error_message) logger.exception(error_message)
raise SubmissionInternalError(error_message) raise SubmissionInternalError(error_message)
return SubmissionSerializer(submission).data
def get_submissions(student_item_dict, limit=None): def get_submissions(student_item_dict, limit=None):
"""Retrieves the submissions for the specified student item, """Retrieves the submissions for the specified student item,
...@@ -138,15 +164,20 @@ def get_submissions(student_item_dict, limit=None): ...@@ -138,15 +164,20 @@ def get_submissions(student_item_dict, limit=None):
thrown if no submission is found relative to this location. thrown if no submission is found relative to this location.
Args: Args:
student_item_dict (dict): The location of the problem student_item_dict (dict): The location of the problem this submission is
this submission is associated with, as defined by a course, student, associated with, as defined by a course, student, and item.
and item.
limit (int): Optional parameter for limiting the returned number of limit (int): Optional parameter for limiting the returned number of
submissions associated with this student item. If not specified, all submissions associated with this student item. If not specified, all
associated submissions are returned. associated submissions are returned.
Returns: Returns:
List dict: A list of dicts for the associated student item. List dict: A list of dicts for the associated student item. The submission
contains five attributes: student_item, attempt_number, submitted_at,
created_at, and answer. 'student_item' is the ID of the related student
item for the submission. 'attempt_number' is the attempt this submission
represents for this question. 'submitted_at' represents the time this
submission was submitted, which can be configured, versus the
'created_at' date, which is when the submission is first created.
Raises: Raises:
SubmissionRequestError: Raised when the associated student item fails SubmissionRequestError: Raised when the associated student item fails
...@@ -154,10 +185,27 @@ def get_submissions(student_item_dict, limit=None): ...@@ -154,10 +185,27 @@ def get_submissions(student_item_dict, limit=None):
SubmissionNotFoundError: Raised when a submission cannot be found for SubmissionNotFoundError: Raised when a submission cannot be found for
the associated student item. the associated student item.
Examples:
>>> student_item_dict = dict(
>>> student_id="Tim",
>>> item_id="item_1",
>>> course_id="course_1",
>>> item_type="type_one"
>>> )
>>> get_submissions(student_item_dict, 3)
[{
'student_item': 2,
'attempt_number': 1,
'submitted_at': datetime.datetime(2014, 1, 29, 23, 14, 52, 649284, tzinfo=<UTC>),
'created_at': datetime.datetime(2014, 1, 29, 17, 14, 52, 668850, tzinfo=<UTC>),
'answer': u'The answer is 42.'
}]
""" """
student_item_model = _get_or_create_student_item(student_item_dict) student_item_model = _get_or_create_student_item(student_item_dict)
try: try:
submission_models = Submission.objects.filter(student_item=student_item_model) submission_models = Submission.objects.filter(
student_item=student_item_model)
except DatabaseError: except DatabaseError:
error_message = ( error_message = (
u"Error getting submission request for student item {}" u"Error getting submission request for student item {}"
...@@ -169,7 +217,8 @@ def get_submissions(student_item_dict, limit=None): ...@@ -169,7 +217,8 @@ def get_submissions(student_item_dict, limit=None):
if limit: if limit:
submission_models = submission_models[:limit] submission_models = submission_models[:limit]
return [SubmissionSerializer(submission).data for submission in submission_models] return [SubmissionSerializer(submission).data for submission in
submission_models]
def get_score(student_item): def get_score(student_item):
...@@ -216,16 +265,14 @@ def _get_or_create_student_item(student_item_dict): ...@@ -216,16 +265,14 @@ def _get_or_create_student_item(student_item_dict):
""" """
try: try:
try: try:
student_item_model = StudentItem.objects.get(**student_item_dict) return StudentItem.objects.get(**student_item_dict)
except StudentItem.DoesNotExist: except StudentItem.DoesNotExist:
student_item_serializer = StudentItemSerializer(data=student_item_dict) student_item_serializer = StudentItemSerializer(data=student_item_dict)
student_item_serializer.is_valid() if not student_item_serializer.is_valid():
if student_item_serializer.errors:
raise SubmissionRequestError(student_item_serializer.errors) raise SubmissionRequestError(student_item_serializer.errors)
student_item_model = StudentItem.objects.create(**student_item_dict) return student_item_serializer.save()
except DatabaseError: except DatabaseError:
error_message = u"An error occurred creating student item: {}".format( error_message = u"An error occurred creating student item: {}".format(
student_item_dict) student_item_dict)
logger.exception(error_message) logger.exception(error_message)
raise SubmissionInternalError(error_message) raise SubmissionInternalError(error_message)
return student_item_model
...@@ -7,7 +7,8 @@ from nose.tools import raises ...@@ -7,7 +7,8 @@ from nose.tools import raises
from mock import patch from mock import patch
from submissions.api import create_submission, get_submissions, SubmissionRequestError, SubmissionInternalError from submissions.api import create_submission, get_submissions, SubmissionRequestError, SubmissionInternalError
from submissions.models import Submission, StudentItem from submissions.models import Submission
from submissions.serializers import StudentItemSerializer
STUDENT_ITEM = dict( STUDENT_ITEM = dict(
student_id="Tim", student_id="Tim",
...@@ -73,10 +74,10 @@ class TestApi(TestCase): ...@@ -73,10 +74,10 @@ class TestApi(TestCase):
mock_filter.side_effect = DatabaseError("Bad things happened") mock_filter.side_effect = DatabaseError("Bad things happened")
create_submission(STUDENT_ITEM, ANSWER_ONE) create_submission(STUDENT_ITEM, ANSWER_ONE)
@patch.object(StudentItem.objects, 'create') @patch.object(StudentItemSerializer, 'save')
@raises(SubmissionInternalError) @raises(SubmissionInternalError)
def test_create_student_item_validation(self, mock_create): def test_create_student_item_validation(self, mock_save):
mock_create.side_effect = DatabaseError("Bad things happened") mock_save.side_effect = DatabaseError("Bad things happened")
create_submission(STUDENT_ITEM, ANSWER_ONE) create_submission(STUDENT_ITEM, ANSWER_ONE)
def test_unicode_enforcement(self): def test_unicode_enforcement(self):
......
# Grab everything in base requirements # Grab everything in base requirements
-r base.txt -r base.txt
ddt==0.4.0
django-nose==1.2 django-nose==1.2
mock==1.0.1 mock==1.0.1
nose==1.3.0 nose==1.3.0
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment