Commit 47d2fe23 by Will Daly

Merge pull request #718 from edx/will/upgrade-django-rest-framework

Upgrade djangorestframework to 3.2.3
parents 782bce7a aa908378
......@@ -7,6 +7,7 @@ import logging
from django.core.cache import cache
from rest_framework import serializers
from rest_framework.fields import IntegerField, DateTimeField
from openassessment.assessment.models import (
Assessment, AssessmentPart, Criterion, CriterionOption, Rubric,
)
......@@ -22,75 +23,41 @@ class InvalidRubric(Exception):
self.errors = deepcopy(errors)
class NestedModelSerializer(serializers.ModelSerializer):
"""Model Serializer that supports deserialization with arbitrary nesting.
The Django REST Framework does not currently support deserialization more
than one level deep (so a parent and children). We want to be able to
create a :class:`Rubric` → :class:`Criterion` → :class:`CriterionOption`
hierarchy.
Much of the base logic already "just works" and serialization of arbritrary
depth is supported. So we just override the save_object method to
recursively link foreign key relations instead of doing it one level deep.
We don't touch many-to-many relationships because we don't need to for our
purposes, so those still only work one level deep.
"""
def recursively_link_related(self, obj, **kwargs):
if getattr(obj, '_related_data', None):
for accessor_name, related in obj._related_data.items():
setattr(obj, accessor_name, related)
for related_obj in related:
self.recursively_link_related(related_obj, **kwargs)
del(obj._related_data)
def save_object(self, obj, **kwargs):
obj.save(**kwargs)
# The code for many-to-many relationships is just copy-pasted from the
# Django REST Framework ModelSerializer
if getattr(obj, '_m2m_data', None):
for accessor_name, object_list in obj._m2m_data.items():
setattr(obj, accessor_name, object_list)
del(obj._m2m_data)
# This is our only real change from ModelSerializer
self.recursively_link_related(obj, **kwargs)
class CriterionOptionSerializer(serializers.ModelSerializer):
"""Serializer for :class:`CriterionOption`"""
# Django Rest Framework v3 no longer requires `PositiveIntegerField`s
# to be positive by default, so we need to explicitly set the `min_value`
# on the serializer field.
points = IntegerField(min_value=0)
class CriterionOptionSerializer(NestedModelSerializer):
"""Serializer for :class:`CriterionOption`"""
class Meta:
model = CriterionOption
fields = ('order_num', 'points', 'name', 'label', 'explanation')
class CriterionSerializer(NestedModelSerializer):
class CriterionSerializer(serializers.ModelSerializer):
"""Serializer for :class:`Criterion`"""
options = CriterionOptionSerializer(required=True, many=True)
points_possible = serializers.Field(source='points_possible')
class Meta:
model = Criterion
fields = ('order_num', 'name', 'label', 'prompt', 'options', 'points_possible')
class RubricSerializer(NestedModelSerializer):
class RubricSerializer(serializers.ModelSerializer):
"""Serializer for :class:`Rubric`."""
criteria = CriterionSerializer(required=True, many=True)
points_possible = serializers.Field(source='points_possible')
class Meta:
model = Rubric
fields = ('id', 'content_hash', 'structure_hash', 'criteria', 'points_possible')
def validate_criteria(self, attrs, source):
def validate_criteria(self, value):
"""Make sure we have at least one Criterion in the Rubric."""
criteria = attrs[source]
if not criteria:
if not value:
raise serializers.ValidationError("Must have at least one criterion")
return attrs
return value
@classmethod
def serialized_from_cache(cls, rubric, local_cache=None):
......@@ -135,6 +102,33 @@ class RubricSerializer(NestedModelSerializer):
return rubric_dict
def create(self, validated_data):
"""
Create the rubric model, including its nested models.
Args:
validated_data (dict): Dictionary of validated data for the rubric,
including nested Criterion and CriterionOption data.
Returns:
Rubric
"""
criteria_data = validated_data.pop("criteria")
rubric = Rubric.objects.create(**validated_data)
# Create each nested criterion in the rubric, linking it to the rubric
for criterion_dict in criteria_data:
options_data = criterion_dict.pop("options")
criterion = Criterion.objects.create(rubric=rubric, **criterion_dict)
# Create each option in the criterion, linking it to the criterion
CriterionOption.objects.bulk_create(
CriterionOption(criterion=criterion, **option_dict)
for option_dict in options_data
)
return rubric
class AssessmentPartSerializer(serializers.ModelSerializer):
"""Serializer for :class:`AssessmentPart`."""
......@@ -147,6 +141,13 @@ class AssessmentPartSerializer(serializers.ModelSerializer):
class AssessmentSerializer(serializers.ModelSerializer):
"""Simplified serializer for :class:`Assessment` that's lighter on the DB."""
# Django Rest Framework v3 uses the Django setting `DATETIME_FORMAT`
# when serializing datetimes. This differs from v2, which always
# returned a datetime. To preserve the old behavior, we explicitly
# set `format` to None.
# http://www.django-rest-framework.org/api-guide/fields/#datetimefield
scored_at = DateTimeField(format=None, required=False)
class Meta:
model = Assessment
fields = (
......
......@@ -152,13 +152,14 @@ STEP_REQUIREMENTS = {
}
}
@ddt
class TestPeerApi(CacheResetTest):
"""
Tests for the peer assessment API functions.
"""
CREATE_ASSESSMENT_NUM_QUERIES = 58
CREATE_ASSESSMENT_NUM_QUERIES = 53
def test_create_assessment_points(self):
self._create_student_and_submission("Tim", "Tim's answer")
......
......@@ -32,8 +32,8 @@ class TrainingExampleSerializerTest(CacheResetTest):
},
{
"order_num": 2,
"name": "єχ¢єℓℓєηт",
"explanation": "乇メc乇レレ乇刀イ フo乃!",
"name": u"єχ¢єℓℓєηт",
"explanation": u"乇メc乇レレ乇刀イ フo乃!",
"points": 2,
},
]
......@@ -89,7 +89,6 @@ class TrainingExampleSerializerTest(CacheResetTest):
},
]
def test_duplicate_training_example(self):
# Deserialize some examples for a rubric
deserialize_training_examples(self.EXAMPLES[0:2], self.RUBRIC)
......
......@@ -780,7 +780,7 @@
"uuid": "387d840a-d0ae-11e3-bb0e-14109fd8dc43",
"student_item": 2,
"created_at": "2014-04-30T21:27:24.181Z",
"raw_answer": "{\"text\": \"E\\u00e3 \\u00e9um quis d\\u00ed\\u00e7o \\u00e9l\\u00e2b\\u00f3r\\u00e3ret. N\\u00e1m in \\u00e7\\u00f4mmune p\\u00f3nder\\u00fam apeirian, te \\u00e2lii m\\u00e1zim \\u00ednt\\u00e9ll\\u00eagat nec, ex \\u00e9leif\\u00e9nd el\\u00f3quenti\\u00e2m usu. His no n\\u00f3vum lu\\u00e7ilius, \\u00e0utem \\u00e3\\u00e9que vix \\u00e0d.\"}",
"answer": "{\"text\": \"E\\u00e3 \\u00e9um quis d\\u00ed\\u00e7o \\u00e9l\\u00e2b\\u00f3r\\u00e3ret. N\\u00e1m in \\u00e7\\u00f4mmune p\\u00f3nder\\u00fam apeirian, te \\u00e2lii m\\u00e1zim \\u00ednt\\u00e9ll\\u00eagat nec, ex \\u00e9leif\\u00e9nd el\\u00f3quenti\\u00e2m usu. His no n\\u00f3vum lu\\u00e7ilius, \\u00e0utem \\u00e3\\u00e9que vix \\u00e0d.\"}",
"attempt_number": 1,
"submitted_at": "2014-04-30T21:27:24.173Z"
}
......@@ -792,7 +792,7 @@
"uuid": "1783758f-d0ae-11e3-b495-14109fd8dc43",
"student_item": 1,
"created_at": "2014-04-30T21:26:28.855Z",
"raw_answer": "{\"text\": \"L\\u00f5r\\u00eam \\u00edpsum d\\u00f4lor sit amet, in d\\u00fao p\\u00f5pul\\u00f3 m\\u00e0nd\\u00e1mus, alienum cons\\u00e9q\\u00faat pers\\u00e9cuti mel \\u00e0n. R\\u00eabum de\\u00e7or\\u00ea \\u00e9\\u00fam an, s\\u00e0ep\\u00e9 p\\u00f5pulo splendid\\u00e9 te p\\u00e9r.\"}",
"answer": "{\"text\": \"L\\u00f5r\\u00eam \\u00edpsum d\\u00f4lor sit amet, in d\\u00fao p\\u00f5pul\\u00f3 m\\u00e0nd\\u00e1mus, alienum cons\\u00e9q\\u00faat pers\\u00e9cuti mel \\u00e0n. R\\u00eabum de\\u00e7or\\u00ea \\u00e9\\u00fam an, s\\u00e0ep\\u00e9 p\\u00f5pulo splendid\\u00e9 te p\\u00e9r.\"}",
"attempt_number": 1,
"submitted_at": "2014-04-30T21:26:28.847Z"
}
......@@ -839,4 +839,4 @@
"latest": 2
}
}
]
\ No newline at end of file
]
......@@ -821,7 +821,7 @@
"uuid": "28cebeca-d0ab-11e3-a6ab-14109fd8dc43",
"student_item": 2,
"created_at": "2014-04-30T21:05:29.380Z",
"raw_answer": "{\"text\": \"Etiam vel neque id nunc lacinia tincidunt.\"}",
"answer": "{\"text\": \"Etiam vel neque id nunc lacinia tincidunt.\"}",
"attempt_number": 1,
"submitted_at": "2014-04-30T21:05:29.372Z"
}
......@@ -833,9 +833,9 @@
"uuid": "cf5190b8-d0aa-11e3-a734-14109fd8dc43",
"student_item": 1,
"created_at": "2014-04-30T21:02:59.241Z",
"raw_answer": "{\"text\": \"Lorem ipsum dolor sit amet\"}",
"answer": "{\"text\": \"Lorem ipsum dolor sit amet\"}",
"attempt_number": 1,
"submitted_at": "2014-04-30T21:02:59.234Z"
}
}
]
\ No newline at end of file
]
......@@ -800,7 +800,7 @@
"uuid": "28cebeca-d0ab-11e3-a6ab-14109fd8dc43",
"student_item": 2,
"created_at": "2014-04-30T21:05:29.380Z",
"raw_answer": "{\"text\": \"Etiam vel neque id nunc lacinia tincidunt.\"}",
"answer": "{\"text\": \"Etiam vel neque id nunc lacinia tincidunt.\"}",
"attempt_number": 1,
"submitted_at": "2014-04-30T21:05:29.372Z"
}
......@@ -812,9 +812,9 @@
"uuid": "cf5190b8-d0aa-11e3-a734-14109fd8dc43",
"student_item": 1,
"created_at": "2014-04-30T21:02:59.241Z",
"raw_answer": "{\"text\": \"Lorem ipsum dolor sit amet\"}",
"answer": "{\"text\": \"Lorem ipsum dolor sit amet\"}",
"attempt_number": 1,
"submitted_at": "2014-04-30T21:02:59.234Z"
}
}
]
\ No newline at end of file
]
......@@ -938,7 +938,7 @@
"uuid": "28cebeca-d0ab-11e3-a6ab-14109fd8dc43",
"student_item": 2,
"created_at": "2014-04-30T21:05:29.380Z",
"raw_answer": "{\"text\": \"Etiam vel neque id nunc lacinia tincidunt.\"}",
"answer": "{\"text\": \"Etiam vel neque id nunc lacinia tincidunt.\"}",
"attempt_number": 1,
"submitted_at": "2014-04-30T21:05:29.372Z"
}
......@@ -950,7 +950,7 @@
"uuid": "cf5190b8-d0aa-11e3-a734-14109fd8dc43",
"student_item": 1,
"created_at": "2014-04-30T21:02:59.241Z",
"raw_answer": "{\"text\": \"Lorem ipsum dolor sit amet\"}",
"answer": "{\"text\": \"Lorem ipsum dolor sit amet\"}",
"attempt_number": 1,
"submitted_at": "2014-04-30T21:02:59.234Z"
}
......@@ -997,4 +997,4 @@
"latest": 2
}
}
]
\ No newline at end of file
]
......@@ -842,7 +842,7 @@
"uuid": "28cebeca-d0ab-11e3-a6ab-14109fd8dc43",
"student_item": 2,
"created_at": "2014-04-30T21:05:29.380Z",
"raw_answer": "{\"text\": \"Etiam vel neque id nunc lacinia tincidunt.\"}",
"answer": "{\"text\": \"Etiam vel neque id nunc lacinia tincidunt.\"}",
"attempt_number": 1,
"submitted_at": "2014-04-30T21:05:29.372Z"
}
......@@ -854,7 +854,7 @@
"uuid": "cf5190b8-d0aa-11e3-a734-14109fd8dc43",
"student_item": 1,
"created_at": "2014-04-30T21:02:59.241Z",
"raw_answer": "{\"text\": \"Lorem ipsum dolor sit amet\"}",
"answer": "{\"text\": \"Lorem ipsum dolor sit amet\"}",
"attempt_number": 1,
"submitted_at": "2014-04-30T21:02:59.234Z"
}
......
......@@ -477,9 +477,9 @@
"uuid": "cf5190b8-d0aa-11e3-a734-14109fd8dc43",
"student_item": 1,
"created_at": "2014-04-30T21:02:59.241Z",
"raw_answer": "{\"text\": \"Lorem ipsum dolor sit amet\"}",
"answer": "{\"text\": \"Lorem ipsum dolor sit amet\"}",
"attempt_number": 1,
"submitted_at": "2014-04-30T21:02:59.234Z"
}
}
]
\ No newline at end of file
]
......@@ -463,7 +463,7 @@
"uuid": "99765973-d1f9-11e3-841a-14109fd8dc43",
"student_item": 2,
"created_at": "2014-05-02T12:59:30.290Z",
"raw_answer": "{\"text\": \"\\u0547\\ufec9\\u0e23\\u0547 \\u0e23\\u0547\\u027c\\u0671\\u0e01\\ufeed\"}",
"answer": "{\"text\": \"\\u0547\\ufec9\\u0e23\\u0547 \\u0e23\\u0547\\u027c\\u0671\\u0e01\\ufeed\"}",
"attempt_number": 1,
"submitted_at": "2014-05-02T12:59:30.283Z"
}
......@@ -475,7 +475,7 @@
"uuid": "8c52cfdc-d1f9-11e3-953c-14109fd8dc43",
"student_item": 1,
"created_at": "2014-05-02T12:59:08.243Z",
"raw_answer": "{\"text\": \"\\u0442\\u0454\\u0455\\u0442 \\u0455\\u0442\\u044f\\u03b9\\u03b7\\ufeed\"}",
"answer": "{\"text\": \"\\u0442\\u0454\\u0455\\u0442 \\u0455\\u0442\\u044f\\u03b9\\u03b7\\ufeed\"}",
"attempt_number": 1,
"submitted_at": "2014-05-02T12:59:08.234Z"
}
......
"""
Serializers are created to ensure models do not have to be accessed outside the
scope of the Tim APIs.
scope of the ORA2 APIs.
"""
from rest_framework import serializers
from openassessment.workflow.models import AssessmentWorkflow, AssessmentWorkflowCancellation
class AssessmentWorkflowSerializer(serializers.ModelSerializer):
score = serializers.Field(source='score')
score = serializers.ReadOnlyField(required=False)
class Meta:
model = AssessmentWorkflow
......@@ -22,20 +22,6 @@ class AssessmentWorkflowSerializer(serializers.ModelSerializer):
'score'
)
# Not implemented yet:
#
# class AssessmentWorkflowHistorySerializer(serializers.ModelSerializer):
# class Meta:
# model = AssessmentWorkflowHistory
# fields = (
# 'workflow',
# 'app',
# 'event_type',
# 'event_data',
# 'description',
# 'created_at'
# )
class AssessmentWorkflowCancellationSerializer(serializers.ModelSerializer):
"""
......
......@@ -182,7 +182,7 @@ def prepare_submission_for_serialization(submission_data):
def create_submission_dict(submission, prompts):
"""
1. Convert from legacy format.
3. Add prompts to submission['answer']['parts'] to simplify iteration in the template.
2. Add prompts to submission['answer']['parts'] to simplify iteration in the template.
Args:
submission (dict): Submission dictionary.
......@@ -191,7 +191,7 @@ def create_submission_dict(submission, prompts):
Returns:
dict
"""
parts = [{ 'prompt': prompt, 'text': ''} for prompt in prompts]
parts = [{'prompt': prompt, 'text': ''} for prompt in prompts]
if 'text' in submission['answer']:
parts[0]['text'] = submission['answer'].pop('text')
......
......@@ -86,7 +86,11 @@ class RubricValidationTest(TestCase):
is_released = data.get('is_released', False)
is_example_based = data.get('is_example_based', False)
success, msg = validate_rubric(
data['rubric'], current_rubric,is_released, is_example_based, STUB_I18N
data['rubric'],
current_rubric,
is_released,
is_example_based,
STUB_I18N
)
self.assertTrue(success)
self.assertEqual(msg, u'')
......
......@@ -6,7 +6,7 @@
git+https://github.com/edx/XBlock.git@9c634481dfc85a17dcb3351ca232d7098a38e10e#egg=XBlock
# edx-submissions
git+https://github.com/edx/edx-submissions.git@9538ee8a971d04dc1cb05e88f6aa0c36b224455c#egg=edx-submissions
git+https://github.com/edx/edx-submissions.git@14aeaa9e30f9a408b34ffaf6d78409dedaad015a#egg=edx-submissions==0.1.0
# Third Party Requirements
boto>=2.32.1,<3.0.0
......@@ -16,8 +16,9 @@ django>=1.4,<1.5
django-celery==3.1.16
django-extensions==1.5.5
django-model-utils==2.3.1
djangorestframework<2.4
djangorestframework>=3.1,<3.2
dogapi==1.2.1
jsonfield==1.0.3
lazy==1.1
loremipsum==1.0.5
python-dateutil==2.1
......
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