From d6c7d8d383dd54ca601c9a7d37a0ac217f7ca18f Mon Sep 17 00:00:00 2001 From: Will Daly Date: Fri, 18 Sep 2015 10:40:53 -0700 Subject: [PATCH] TNL-3316: Error on Insights performance report Preserve DRF v2 behavior of defaulting to None for missing keys in the grading policy dictionary. --- .../course_structure_api/v0/tests.py | 83 +++++++++++++++---- .../course_structures/api/v0/serializers.py | 15 ++++ 2 files changed, 82 insertions(+), 16 deletions(-) diff --git a/lms/djangoapps/course_structure_api/v0/tests.py b/lms/djangoapps/course_structure_api/v0/tests.py index c74f76f601..08224e521a 100644 --- a/lms/djangoapps/course_structure_api/v0/tests.py +++ b/lms/djangoapps/course_structure_api/v0/tests.py @@ -36,6 +36,23 @@ class CourseViewTestsMixin(object): """ view = None + raw_grader = [ + { + "min_count": 24, + "weight": 0.2, + "type": "Homework", + "drop_count": 0, + "short_label": "HW" + }, + { + "min_count": 4, + "weight": 0.8, + "type": "Exam", + "drop_count": 0, + "short_label": "Exam" + } + ] + def setUp(self): super(CourseViewTestsMixin, self).setUp() self.create_user_and_access_token() @@ -51,22 +68,7 @@ class CourseViewTestsMixin(object): @classmethod def create_course_data(cls): cls.invalid_course_id = 'foo/bar/baz' - cls.course = CourseFactory.create(display_name='An Introduction to API Testing', raw_grader=[ - { - "min_count": 24, - "weight": 0.2, - "type": "Homework", - "drop_count": 0, - "short_label": "HW" - }, - { - "min_count": 4, - "weight": 0.8, - "type": "Exam", - "drop_count": 0, - "short_label": "Exam" - } - ]) + cls.course = CourseFactory.create(display_name='An Introduction to API Testing', raw_grader=cls.raw_grader) cls.course_id = unicode(cls.course.id) with cls.store.bulk_operations(cls.course.id, emit_signals=False): cls.sequential = ItemFactory.create( @@ -408,6 +410,55 @@ class CourseGradingPolicyTests(CourseDetailTestMixin, CourseViewTestsMixin, Shar self.assertListEqual(response.data, expected) +class CourseGradingPolicyMissingFieldsTests(CourseDetailTestMixin, CourseViewTestsMixin, SharedModuleStoreTestCase): + view = 'course_structure_api:v0:grading_policy' + + # Update the raw grader to have missing keys + raw_grader = [ + { + "min_count": 24, + "weight": 0.2, + "type": "Homework", + "drop_count": 0, + "short_label": "HW" + }, + { + # Deleted "min_count" key + "weight": 0.8, + "type": "Exam", + "drop_count": 0, + "short_label": "Exam" + } + ] + + @classmethod + def setUpClass(cls): + super(CourseGradingPolicyMissingFieldsTests, cls).setUpClass() + cls.create_course_data() + + def test_get(self): + """ + The view should return grading policy for a course. + """ + response = super(CourseGradingPolicyMissingFieldsTests, self).test_get() + + expected = [ + { + "count": 24, + "weight": 0.2, + "assignment_type": "Homework", + "dropped": 0 + }, + { + "count": None, + "weight": 0.8, + "assignment_type": "Exam", + "dropped": 0 + } + ] + self.assertListEqual(response.data, expected) + + ##################################################################################### # # The following Mixins/Classes collectively test the CourseBlocksAndNavigation view. diff --git a/openedx/core/djangoapps/content/course_structures/api/v0/serializers.py b/openedx/core/djangoapps/content/course_structures/api/v0/serializers.py index 189c9dc8ea..881be80437 100644 --- a/openedx/core/djangoapps/content/course_structures/api/v0/serializers.py +++ b/openedx/core/djangoapps/content/course_structures/api/v0/serializers.py @@ -1,6 +1,8 @@ """ API Serializers """ +from collections import defaultdict + from rest_framework import serializers @@ -11,6 +13,19 @@ class GradingPolicySerializer(serializers.Serializer): dropped = serializers.IntegerField(source='drop_count') weight = serializers.FloatField() + def to_representation(self, obj): + """ + Return a representation of the grading policy. + """ + # Backwards compatibility with the behavior of DRF v2. + # When the grader dictionary was missing keys, DRF v2 would default to None; + # DRF v3 unhelpfully raises an exception. + return dict( + super(GradingPolicySerializer, self).to_representation( + defaultdict(lambda: None, obj) + ) + ) + # pylint: disable=invalid-name class BlockSerializer(serializers.Serializer):