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 ...@@ -7,6 +7,7 @@ import logging
from django.core.cache import cache from django.core.cache import cache
from rest_framework import serializers from rest_framework import serializers
from rest_framework.fields import IntegerField, DateTimeField
from openassessment.assessment.models import ( from openassessment.assessment.models import (
Assessment, AssessmentPart, Criterion, CriterionOption, Rubric, Assessment, AssessmentPart, Criterion, CriterionOption, Rubric,
) )
...@@ -22,75 +23,41 @@ class InvalidRubric(Exception): ...@@ -22,75 +23,41 @@ class InvalidRubric(Exception):
self.errors = deepcopy(errors) self.errors = deepcopy(errors)
class NestedModelSerializer(serializers.ModelSerializer): class CriterionOptionSerializer(serializers.ModelSerializer):
"""Model Serializer that supports deserialization with arbitrary nesting. """Serializer for :class:`CriterionOption`"""
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)
# 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: class Meta:
model = CriterionOption model = CriterionOption
fields = ('order_num', 'points', 'name', 'label', 'explanation') fields = ('order_num', 'points', 'name', 'label', 'explanation')
class CriterionSerializer(NestedModelSerializer): class CriterionSerializer(serializers.ModelSerializer):
"""Serializer for :class:`Criterion`""" """Serializer for :class:`Criterion`"""
options = CriterionOptionSerializer(required=True, many=True) options = CriterionOptionSerializer(required=True, many=True)
points_possible = serializers.Field(source='points_possible')
class Meta: class Meta:
model = Criterion model = Criterion
fields = ('order_num', 'name', 'label', 'prompt', 'options', 'points_possible') fields = ('order_num', 'name', 'label', 'prompt', 'options', 'points_possible')
class RubricSerializer(NestedModelSerializer): class RubricSerializer(serializers.ModelSerializer):
"""Serializer for :class:`Rubric`.""" """Serializer for :class:`Rubric`."""
criteria = CriterionSerializer(required=True, many=True) criteria = CriterionSerializer(required=True, many=True)
points_possible = serializers.Field(source='points_possible')
class Meta: class Meta:
model = Rubric model = Rubric
fields = ('id', 'content_hash', 'structure_hash', 'criteria', 'points_possible') 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.""" """Make sure we have at least one Criterion in the Rubric."""
criteria = attrs[source] if not value:
if not criteria:
raise serializers.ValidationError("Must have at least one criterion") raise serializers.ValidationError("Must have at least one criterion")
return attrs return value
@classmethod @classmethod
def serialized_from_cache(cls, rubric, local_cache=None): def serialized_from_cache(cls, rubric, local_cache=None):
...@@ -135,6 +102,33 @@ class RubricSerializer(NestedModelSerializer): ...@@ -135,6 +102,33 @@ class RubricSerializer(NestedModelSerializer):
return rubric_dict 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): class AssessmentPartSerializer(serializers.ModelSerializer):
"""Serializer for :class:`AssessmentPart`.""" """Serializer for :class:`AssessmentPart`."""
...@@ -147,6 +141,13 @@ class AssessmentPartSerializer(serializers.ModelSerializer): ...@@ -147,6 +141,13 @@ class AssessmentPartSerializer(serializers.ModelSerializer):
class AssessmentSerializer(serializers.ModelSerializer): class AssessmentSerializer(serializers.ModelSerializer):
"""Simplified serializer for :class:`Assessment` that's lighter on the DB.""" """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: class Meta:
model = Assessment model = Assessment
fields = ( fields = (
......
...@@ -152,13 +152,14 @@ STEP_REQUIREMENTS = { ...@@ -152,13 +152,14 @@ STEP_REQUIREMENTS = {
} }
} }
@ddt @ddt
class TestPeerApi(CacheResetTest): class TestPeerApi(CacheResetTest):
""" """
Tests for the peer assessment API functions. Tests for the peer assessment API functions.
""" """
CREATE_ASSESSMENT_NUM_QUERIES = 58 CREATE_ASSESSMENT_NUM_QUERIES = 53
def test_create_assessment_points(self): def test_create_assessment_points(self):
self._create_student_and_submission("Tim", "Tim's answer") self._create_student_and_submission("Tim", "Tim's answer")
......
...@@ -32,8 +32,8 @@ class TrainingExampleSerializerTest(CacheResetTest): ...@@ -32,8 +32,8 @@ class TrainingExampleSerializerTest(CacheResetTest):
}, },
{ {
"order_num": 2, "order_num": 2,
"name": "єχ¢єℓℓєηт", "name": u"єχ¢єℓℓєηт",
"explanation": "乇メc乇レレ乇刀イ フo乃!", "explanation": u"乇メc乇レレ乇刀イ フo乃!",
"points": 2, "points": 2,
}, },
] ]
...@@ -89,7 +89,6 @@ class TrainingExampleSerializerTest(CacheResetTest): ...@@ -89,7 +89,6 @@ class TrainingExampleSerializerTest(CacheResetTest):
}, },
] ]
def test_duplicate_training_example(self): def test_duplicate_training_example(self):
# Deserialize some examples for a rubric # Deserialize some examples for a rubric
deserialize_training_examples(self.EXAMPLES[0:2], self.RUBRIC) deserialize_training_examples(self.EXAMPLES[0:2], self.RUBRIC)
......
...@@ -780,7 +780,7 @@ ...@@ -780,7 +780,7 @@
"uuid": "387d840a-d0ae-11e3-bb0e-14109fd8dc43", "uuid": "387d840a-d0ae-11e3-bb0e-14109fd8dc43",
"student_item": 2, "student_item": 2,
"created_at": "2014-04-30T21:27:24.181Z", "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, "attempt_number": 1,
"submitted_at": "2014-04-30T21:27:24.173Z" "submitted_at": "2014-04-30T21:27:24.173Z"
} }
...@@ -792,7 +792,7 @@ ...@@ -792,7 +792,7 @@
"uuid": "1783758f-d0ae-11e3-b495-14109fd8dc43", "uuid": "1783758f-d0ae-11e3-b495-14109fd8dc43",
"student_item": 1, "student_item": 1,
"created_at": "2014-04-30T21:26:28.855Z", "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, "attempt_number": 1,
"submitted_at": "2014-04-30T21:26:28.847Z" "submitted_at": "2014-04-30T21:26:28.847Z"
} }
...@@ -839,4 +839,4 @@ ...@@ -839,4 +839,4 @@
"latest": 2 "latest": 2
} }
} }
] ]
\ No newline at end of file
...@@ -821,7 +821,7 @@ ...@@ -821,7 +821,7 @@
"uuid": "28cebeca-d0ab-11e3-a6ab-14109fd8dc43", "uuid": "28cebeca-d0ab-11e3-a6ab-14109fd8dc43",
"student_item": 2, "student_item": 2,
"created_at": "2014-04-30T21:05:29.380Z", "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, "attempt_number": 1,
"submitted_at": "2014-04-30T21:05:29.372Z" "submitted_at": "2014-04-30T21:05:29.372Z"
} }
...@@ -833,9 +833,9 @@ ...@@ -833,9 +833,9 @@
"uuid": "cf5190b8-d0aa-11e3-a734-14109fd8dc43", "uuid": "cf5190b8-d0aa-11e3-a734-14109fd8dc43",
"student_item": 1, "student_item": 1,
"created_at": "2014-04-30T21:02:59.241Z", "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, "attempt_number": 1,
"submitted_at": "2014-04-30T21:02:59.234Z" "submitted_at": "2014-04-30T21:02:59.234Z"
} }
} }
] ]
\ No newline at end of file
...@@ -800,7 +800,7 @@ ...@@ -800,7 +800,7 @@
"uuid": "28cebeca-d0ab-11e3-a6ab-14109fd8dc43", "uuid": "28cebeca-d0ab-11e3-a6ab-14109fd8dc43",
"student_item": 2, "student_item": 2,
"created_at": "2014-04-30T21:05:29.380Z", "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, "attempt_number": 1,
"submitted_at": "2014-04-30T21:05:29.372Z" "submitted_at": "2014-04-30T21:05:29.372Z"
} }
...@@ -812,9 +812,9 @@ ...@@ -812,9 +812,9 @@
"uuid": "cf5190b8-d0aa-11e3-a734-14109fd8dc43", "uuid": "cf5190b8-d0aa-11e3-a734-14109fd8dc43",
"student_item": 1, "student_item": 1,
"created_at": "2014-04-30T21:02:59.241Z", "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, "attempt_number": 1,
"submitted_at": "2014-04-30T21:02:59.234Z" "submitted_at": "2014-04-30T21:02:59.234Z"
} }
} }
] ]
\ No newline at end of file
...@@ -938,7 +938,7 @@ ...@@ -938,7 +938,7 @@
"uuid": "28cebeca-d0ab-11e3-a6ab-14109fd8dc43", "uuid": "28cebeca-d0ab-11e3-a6ab-14109fd8dc43",
"student_item": 2, "student_item": 2,
"created_at": "2014-04-30T21:05:29.380Z", "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, "attempt_number": 1,
"submitted_at": "2014-04-30T21:05:29.372Z" "submitted_at": "2014-04-30T21:05:29.372Z"
} }
...@@ -950,7 +950,7 @@ ...@@ -950,7 +950,7 @@
"uuid": "cf5190b8-d0aa-11e3-a734-14109fd8dc43", "uuid": "cf5190b8-d0aa-11e3-a734-14109fd8dc43",
"student_item": 1, "student_item": 1,
"created_at": "2014-04-30T21:02:59.241Z", "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, "attempt_number": 1,
"submitted_at": "2014-04-30T21:02:59.234Z" "submitted_at": "2014-04-30T21:02:59.234Z"
} }
...@@ -997,4 +997,4 @@ ...@@ -997,4 +997,4 @@
"latest": 2 "latest": 2
} }
} }
] ]
\ No newline at end of file
...@@ -842,7 +842,7 @@ ...@@ -842,7 +842,7 @@
"uuid": "28cebeca-d0ab-11e3-a6ab-14109fd8dc43", "uuid": "28cebeca-d0ab-11e3-a6ab-14109fd8dc43",
"student_item": 2, "student_item": 2,
"created_at": "2014-04-30T21:05:29.380Z", "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, "attempt_number": 1,
"submitted_at": "2014-04-30T21:05:29.372Z" "submitted_at": "2014-04-30T21:05:29.372Z"
} }
...@@ -854,7 +854,7 @@ ...@@ -854,7 +854,7 @@
"uuid": "cf5190b8-d0aa-11e3-a734-14109fd8dc43", "uuid": "cf5190b8-d0aa-11e3-a734-14109fd8dc43",
"student_item": 1, "student_item": 1,
"created_at": "2014-04-30T21:02:59.241Z", "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, "attempt_number": 1,
"submitted_at": "2014-04-30T21:02:59.234Z" "submitted_at": "2014-04-30T21:02:59.234Z"
} }
......
...@@ -477,9 +477,9 @@ ...@@ -477,9 +477,9 @@
"uuid": "cf5190b8-d0aa-11e3-a734-14109fd8dc43", "uuid": "cf5190b8-d0aa-11e3-a734-14109fd8dc43",
"student_item": 1, "student_item": 1,
"created_at": "2014-04-30T21:02:59.241Z", "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, "attempt_number": 1,
"submitted_at": "2014-04-30T21:02:59.234Z" "submitted_at": "2014-04-30T21:02:59.234Z"
} }
} }
] ]
\ No newline at end of file
...@@ -463,7 +463,7 @@ ...@@ -463,7 +463,7 @@
"uuid": "99765973-d1f9-11e3-841a-14109fd8dc43", "uuid": "99765973-d1f9-11e3-841a-14109fd8dc43",
"student_item": 2, "student_item": 2,
"created_at": "2014-05-02T12:59:30.290Z", "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, "attempt_number": 1,
"submitted_at": "2014-05-02T12:59:30.283Z" "submitted_at": "2014-05-02T12:59:30.283Z"
} }
...@@ -475,7 +475,7 @@ ...@@ -475,7 +475,7 @@
"uuid": "8c52cfdc-d1f9-11e3-953c-14109fd8dc43", "uuid": "8c52cfdc-d1f9-11e3-953c-14109fd8dc43",
"student_item": 1, "student_item": 1,
"created_at": "2014-05-02T12:59:08.243Z", "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, "attempt_number": 1,
"submitted_at": "2014-05-02T12:59:08.234Z" "submitted_at": "2014-05-02T12:59:08.234Z"
} }
......
""" """
Serializers are created to ensure models do not have to be accessed outside the 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 rest_framework import serializers
from openassessment.workflow.models import AssessmentWorkflow, AssessmentWorkflowCancellation from openassessment.workflow.models import AssessmentWorkflow, AssessmentWorkflowCancellation
class AssessmentWorkflowSerializer(serializers.ModelSerializer): class AssessmentWorkflowSerializer(serializers.ModelSerializer):
score = serializers.Field(source='score') score = serializers.ReadOnlyField(required=False)
class Meta: class Meta:
model = AssessmentWorkflow model = AssessmentWorkflow
...@@ -22,20 +22,6 @@ class AssessmentWorkflowSerializer(serializers.ModelSerializer): ...@@ -22,20 +22,6 @@ class AssessmentWorkflowSerializer(serializers.ModelSerializer):
'score' '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): class AssessmentWorkflowCancellationSerializer(serializers.ModelSerializer):
""" """
......
...@@ -182,7 +182,7 @@ def prepare_submission_for_serialization(submission_data): ...@@ -182,7 +182,7 @@ def prepare_submission_for_serialization(submission_data):
def create_submission_dict(submission, prompts): def create_submission_dict(submission, prompts):
""" """
1. Convert from legacy format. 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: Args:
submission (dict): Submission dictionary. submission (dict): Submission dictionary.
...@@ -191,7 +191,7 @@ def create_submission_dict(submission, prompts): ...@@ -191,7 +191,7 @@ def create_submission_dict(submission, prompts):
Returns: Returns:
dict dict
""" """
parts = [{ 'prompt': prompt, 'text': ''} for prompt in prompts] parts = [{'prompt': prompt, 'text': ''} for prompt in prompts]
if 'text' in submission['answer']: if 'text' in submission['answer']:
parts[0]['text'] = submission['answer'].pop('text') parts[0]['text'] = submission['answer'].pop('text')
......
...@@ -86,7 +86,11 @@ class RubricValidationTest(TestCase): ...@@ -86,7 +86,11 @@ class RubricValidationTest(TestCase):
is_released = data.get('is_released', False) is_released = data.get('is_released', False)
is_example_based = data.get('is_example_based', False) is_example_based = data.get('is_example_based', False)
success, msg = validate_rubric( 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.assertTrue(success)
self.assertEqual(msg, u'') self.assertEqual(msg, u'')
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
git+https://github.com/edx/XBlock.git@9c634481dfc85a17dcb3351ca232d7098a38e10e#egg=XBlock git+https://github.com/edx/XBlock.git@9c634481dfc85a17dcb3351ca232d7098a38e10e#egg=XBlock
# edx-submissions # 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 # Third Party Requirements
boto>=2.32.1,<3.0.0 boto>=2.32.1,<3.0.0
...@@ -16,8 +16,9 @@ django>=1.4,<1.5 ...@@ -16,8 +16,9 @@ django>=1.4,<1.5
django-celery==3.1.16 django-celery==3.1.16
django-extensions==1.5.5 django-extensions==1.5.5
django-model-utils==2.3.1 django-model-utils==2.3.1
djangorestframework<2.4 djangorestframework>=3.1,<3.2
dogapi==1.2.1 dogapi==1.2.1
jsonfield==1.0.3
lazy==1.1 lazy==1.1
loremipsum==1.0.5 loremipsum==1.0.5
python-dateutil==2.1 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