EDUCATOR-4189 | Account for undefined values in commerce API Course objects.

This commit is contained in:
Alex Dusenbery
2019-03-13 17:20:43 -04:00
committed by Alex Dusenbery
parent 8ecf6d68b5
commit f35970bb39
3 changed files with 69 additions and 10 deletions

View File

@@ -12,6 +12,8 @@ from openedx.core.djangoapps.content.course_overviews.models import CourseOvervi
log = logging.getLogger(__name__)
UNDEFINED = object()
class Course(object):
""" Pseudo-course model used to group CourseMode objects. """
@@ -19,10 +21,12 @@ class Course(object):
modes = None
_deleted_modes = None
def __init__(self, id, modes, verification_deadline=None): # pylint: disable=redefined-builtin
def __init__(self, id, modes, **kwargs): # pylint: disable=redefined-builtin
self.id = CourseKey.from_string(unicode(id)) # pylint: disable=invalid-name
self.modes = list(modes)
self.verification_deadline = verification_deadline
self.verification_deadline = UNDEFINED
if 'verification_deadline' in kwargs:
self.verification_deadline = kwargs['verification_deadline']
self._deleted_modes = []
@property
@@ -59,8 +63,10 @@ class Course(object):
def save(self, *args, **kwargs): # pylint: disable=unused-argument
""" Save the CourseMode objects to the database. """
# Override the verification deadline for the course (not the individual modes)
VerificationDeadline.set_deadline(self.id, self.verification_deadline, is_explicit=True)
if self.verification_deadline is not UNDEFINED:
# Override the verification deadline for the course (not the individual modes)
# This will delete verification deadlines for the course if self.verification_deadline is null
VerificationDeadline.set_deadline(self.id, self.verification_deadline, is_explicit=True)
for mode in self.modes:
mode.course_id = self.id
@@ -73,7 +79,10 @@ class Course(object):
def update(self, attrs):
""" Update the model with external data (usually passed via API call). """
self.verification_deadline = attrs.get('verification_deadline')
# There are possible downstream effects of settings self.verification_deadline to null,
# so don't assign it a value here unless it is specifically included in attrs.
if 'verification_deadline' in attrs:
self.verification_deadline = attrs.get('verification_deadline')
existing_modes = {mode.mode_slug: mode for mode in self.modes}
merged_modes = set()

View File

@@ -10,7 +10,7 @@ from rest_framework import serializers
from course_modes.models import CourseMode
from xmodule.modulestore.django import modulestore
from .models import Course
from .models import Course, UNDEFINED
class CourseModeSerializer(serializers.ModelSerializer):
@@ -56,11 +56,22 @@ def validate_course_id(course_id):
)
class PossiblyUndefinedDateTimeField(serializers.DateTimeField):
"""
We need a DateTime serializer that can deal with the non-JSON-serializable
UNDEFINED object.
"""
def to_representation(self, value):
if value is UNDEFINED:
return None
return super(PossiblyUndefinedDateTimeField, self).to_representation(value)
class CourseSerializer(serializers.Serializer):
""" Course serializer. """
id = serializers.CharField(validators=[validate_course_id]) # pylint: disable=invalid-name
name = serializers.CharField(read_only=True)
verification_deadline = serializers.DateTimeField(format=None, allow_null=True, required=False)
verification_deadline = PossiblyUndefinedDateTimeField(format=None, allow_null=True, required=False)
modes = CourseModeSerializer(many=True)
def validate(self, attrs):
@@ -87,11 +98,23 @@ class CourseSerializer(serializers.Serializer):
return attrs
def create(self, validated_data):
"""Create course modes for a course. """
"""
Create course modes for a course.
arguments:
validated_data: The result of self.validate() - a dictionary containing 'id', 'modes', and optionally
a 'verification_deadline` key.
returns:
A ``commerce.api.v1.models.Course`` object.
"""
kwargs = {}
if 'verification_deadline' in validated_data:
kwargs['verification_deadline'] = validated_data['verification_deadline']
course = Course(
validated_data["id"],
self._new_course_mode_models(validated_data["modes"]),
verification_deadline=validated_data["verification_deadline"]
**kwargs
)
course.save()
return course

View File

@@ -128,8 +128,9 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
@ddt.data('post', 'put')
def test_authorization_required(self, method):
self.user.user_permissions.clear()
""" Verify create/edit operations require appropriate permissions. """
self.user.user_permissions.clear()
response = getattr(self.client, method)(self.path, content_type=JSON_CONTENT_TYPE)
self.assertEqual(response.status_code, 403)
@@ -236,6 +237,32 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
self.assertEqual(response.status_code, 200)
self.assertIsNone(VerificationDeadline.deadline_for_course(self.course.id))
def test_update_verification_deadline_left_alone(self):
"""
When the course's verification deadline is set and an update request doesn't
include it, we should take no action on it.
"""
verification_deadline = datetime(year=1915, month=5, day=7, tzinfo=pytz.utc)
response, __ = self._get_update_response_and_expected_data(None, verification_deadline)
self.assertEqual(VerificationDeadline.deadline_for_course(self.course.id), verification_deadline)
verified_mode = CourseMode(
mode_slug=u'verified',
min_price=200,
currency=u'USD',
sku=u'ABC123',
bulk_sku=u'BULK-ABC123',
expiration_datetime=None
)
updated_data = self._serialize_course(self.course, [verified_mode], None)
# don't include the verification_deadline key in the PUT request
updated_data.pop('verification_deadline', None)
response = self.client.put(self.path, json.dumps(updated_data), content_type=JSON_CONTENT_TYPE)
self.assertEqual(response.status_code, 200)
self.assertEqual(VerificationDeadline.deadline_for_course(self.course.id), verification_deadline)
def test_remove_upgrade_deadline(self):
"""
Verify that course mode upgrade deadlines can be removed through the API.