EDUCATOR-4189 | Account for undefined values in commerce API Course objects.
This commit is contained in:
committed by
Alex Dusenbery
parent
8ecf6d68b5
commit
f35970bb39
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user