Commit 09405a75 by Clinton Blackburn

Merge pull request #9193 from edx/patch/2015-08-04

ECOM Patch
parents 69be9000 69e9ac1a
""" API v1 models. """ """ API v1 models. """
from itertools import groupby from itertools import groupby
import logging
import logging
from django.db import transaction from django.db import transaction
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from course_modes.models import CourseMode from course_modes.models import CourseMode
from verify_student.models import VerificationDeadline
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -17,11 +18,25 @@ class Course(object): ...@@ -17,11 +18,25 @@ class Course(object):
modes = None modes = None
_deleted_modes = None _deleted_modes = None
def __init__(self, id, modes): # pylint: disable=invalid-name,redefined-builtin def __init__(self, id, modes, verification_deadline=None): # pylint: disable=invalid-name,redefined-builtin
self.id = CourseKey.from_string(unicode(id)) # pylint: disable=invalid-name self.id = CourseKey.from_string(unicode(id)) # pylint: disable=invalid-name
self.modes = list(modes) self.modes = list(modes)
self.verification_deadline = verification_deadline
self._deleted_modes = [] self._deleted_modes = []
@property
def name(self):
""" Return course name. """
course_id = CourseKey.from_string(unicode(self.id)) # pylint: disable=invalid-name
try:
return CourseOverview.get_from_id(course_id).display_name
except CourseOverview.DoesNotExist:
# NOTE (CCB): Ideally, the course modes table should only contain data for courses that exist in
# modulestore. If that is not the case, say for local development/testing, carry on without failure.
log.warning('Failed to retrieve CourseOverview for [%s]. Using empty course name.', course_id)
return None
def get_mode_display_name(self, mode): def get_mode_display_name(self, mode):
""" Returns display name for the given mode. """ """ Returns display name for the given mode. """
slug = mode.mode_slug.strip().lower() slug = mode.mode_slug.strip().lower()
...@@ -42,6 +57,10 @@ class Course(object): ...@@ -42,6 +57,10 @@ class Course(object):
@transaction.commit_on_success @transaction.commit_on_success
def save(self, *args, **kwargs): # pylint: disable=unused-argument def save(self, *args, **kwargs): # pylint: disable=unused-argument
""" Save the CourseMode objects to the database. """ """ Save the CourseMode objects to the database. """
# Update the verification deadline for the course (not the individual modes)
VerificationDeadline.set_deadline(self.id, self.verification_deadline)
for mode in self.modes: for mode in self.modes:
mode.course_id = self.id mode.course_id = self.id
mode.mode_display_name = self.get_mode_display_name(mode) mode.mode_display_name = self.get_mode_display_name(mode)
...@@ -53,6 +72,8 @@ class Course(object): ...@@ -53,6 +72,8 @@ class Course(object):
def update(self, attrs): def update(self, attrs):
""" Update the model with external data (usually passed via API call). """ """ Update the model with external data (usually passed via API call). """
self.verification_deadline = attrs.get('verification_deadline')
existing_modes = {mode.mode_slug: mode for mode in self.modes} existing_modes = {mode.mode_slug: mode for mode in self.modes}
merged_modes = set() merged_modes = set()
merged_mode_keys = set() merged_mode_keys = set()
...@@ -87,7 +108,8 @@ class Course(object): ...@@ -87,7 +108,8 @@ class Course(object):
course_modes = CourseMode.objects.filter(course_id=course_id) course_modes = CourseMode.objects.filter(course_id=course_id)
if course_modes: if course_modes:
return cls(unicode(course_id), list(course_modes)) verification_deadline = VerificationDeadline.deadline_for_course(course_id)
return cls(course_id, list(course_modes), verification_deadline=verification_deadline)
return None return None
......
""" API v1 serializers. """ """ API v1 serializers. """
from datetime import datetime
import pytz
from rest_framework import serializers from rest_framework import serializers
from commerce.api.v1.models import Course from commerce.api.v1.models import Course
...@@ -25,11 +28,36 @@ class CourseModeSerializer(serializers.ModelSerializer): ...@@ -25,11 +28,36 @@ class CourseModeSerializer(serializers.ModelSerializer):
class CourseSerializer(serializers.Serializer): class CourseSerializer(serializers.Serializer):
""" Course serializer. """ """ Course serializer. """
id = serializers.CharField() # pylint: disable=invalid-name id = serializers.CharField() # pylint: disable=invalid-name
name = serializers.CharField(read_only=True)
verification_deadline = serializers.DateTimeField(blank=True)
modes = CourseModeSerializer(many=True, allow_add_remove=True) modes = CourseModeSerializer(many=True, allow_add_remove=True)
def validate(self, attrs):
""" Ensure the verification deadline occurs AFTER the course mode enrollment deadlines. """
verification_deadline = attrs.get('verification_deadline', None)
if verification_deadline:
upgrade_deadline = None
# Find the earliest upgrade deadline
for mode in attrs['modes']:
expires = mode.expiration_datetime
if expires:
# If we don't already have an upgrade_deadline value, use datetime.max so that we can actually
# complete the comparison.
upgrade_deadline = min(expires, upgrade_deadline or datetime.max.replace(tzinfo=pytz.utc))
# In cases where upgrade_deadline is None (e.g. the verified professional mode), allow a verification
# deadline to be set anyway.
if upgrade_deadline is not None and verification_deadline < upgrade_deadline:
raise serializers.ValidationError(
'Verification deadline must be after the course mode upgrade deadlines.')
return attrs
def restore_object(self, attrs, instance=None): def restore_object(self, attrs, instance=None):
if instance is None: if instance is None:
return Course(attrs['id'], attrs['modes']) return Course(attrs['id'], attrs['modes'], attrs['verification_deadline'])
instance.update(attrs) instance.update(attrs)
return instance return instance
...@@ -41,3 +41,8 @@ class CourseRetrieveUpdateView(RetrieveUpdateAPIView): ...@@ -41,3 +41,8 @@ class CourseRetrieveUpdateView(RetrieveUpdateAPIView):
return course return course
raise Http404 raise Http404
def pre_save(self, obj):
# There is nothing to pre-save. The default behavior changes the Course.id attribute from
# a CourseKey to a string, which is not desired.
pass
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