diff --git a/cms/djangoapps/api/v1/serializers/course_runs.py b/cms/djangoapps/api/v1/serializers/course_runs.py index 10737564ef..90de622bac 100644 --- a/cms/djangoapps/api/v1/serializers/course_runs.py +++ b/cms/djangoapps/api/v1/serializers/course_runs.py @@ -8,6 +8,7 @@ import six from django.contrib.auth import get_user_model from django.db import transaction from django.utils.translation import ugettext_lazy as _ +from opaque_keys import InvalidKeyError from rest_framework import serializers from rest_framework.fields import empty @@ -168,29 +169,40 @@ class CourseRunCreateSerializer(CourseRunSerializer): class CourseRunRerunSerializer(CourseRunSerializerCommonFieldsMixin, CourseRunTeamSerializerMixin, serializers.Serializer): title = serializers.CharField(source='display_name', required=False) + number = serializers.CharField(source='id.course', required=False) run = serializers.CharField(source='id.run') - def validate_run(self, value): + def validate(self, attrs): course_run_key = self.instance.id + _id = attrs.get('id') + number = _id.get('course', course_run_key.course) + run = _id['run'] store = modulestore() - with store.default_store('split'): - new_course_run_key = store.make_course_key(course_run_key.org, course_run_key.course, value) + try: + with store.default_store('split'): + new_course_run_key = store.make_course_key(course_run_key.org, number, run) + except InvalidKeyError: + raise serializers.ValidationError( + u'Invalid key supplied. Ensure there are no special characters in the Course Number.' + ) if store.has_course(new_course_run_key, ignore_case=True): - raise serializers.ValidationError(u'Course run {key} already exists'.format(key=new_course_run_key)) - return value + raise serializers.ValidationError( + {'run': u'Course run {key} already exists'.format(key=new_course_run_key)} + ) + return attrs def update(self, instance, validated_data): course_run_key = instance.id _id = validated_data.pop('id') + number = _id.get('course', course_run_key.course) + run = _id['run'] team = validated_data.pop('team', []) user = self.context['request'].user fields = { 'display_name': instance.display_name } fields.update(validated_data) - new_course_run_key = rerun_course( - user, course_run_key, course_run_key.org, course_run_key.course, _id['run'], fields, False - ) + new_course_run_key = rerun_course(user, course_run_key, course_run_key.org, number, run, fields, False) course_run = get_course_and_check_access(new_course_run_key, user) self.update_team(course_run, team) diff --git a/cms/djangoapps/api/v1/tests/test_views/test_course_runs.py b/cms/djangoapps/api/v1/tests/test_views/test_course_runs.py index 8ce1b997d1..cdc1d3819d 100644 --- a/cms/djangoapps/api/v1/tests/test_views/test_course_runs.py +++ b/cms/djangoapps/api/v1/tests/test_views/test_course_runs.py @@ -321,18 +321,18 @@ class CourseRunViewSetTests(ModuleStoreTestCase): contentstore().find(content_key) @ddt.data( - ('instructor_paced', False), - ('self_paced', True), + ('instructor_paced', False, 'NotOriginalNumber1x'), + ('self_paced', True, None), ) @ddt.unpack - def test_rerun(self, pacing_type, expected_self_paced_value): - course_run = ToyCourseFactory() + def test_rerun(self, pacing_type, expected_self_paced_value, number): + original_course_run = ToyCourseFactory() start = datetime.datetime.now(pytz.UTC).replace(microsecond=0) end = start + datetime.timedelta(days=30) user = UserFactory() role = 'instructor' run = '3T2017' - url = reverse('api:v1:course_run-rerun', kwargs={'pk': str(course_run.id)}) + url = reverse('api:v1:course_run-rerun', kwargs={'pk': str(original_course_run.id)}) data = { 'run': run, 'schedule': { @@ -347,13 +347,25 @@ class CourseRunViewSetTests(ModuleStoreTestCase): ], 'pacing_type': pacing_type, } + # If number is supplied, this should become the course number used in the course run key + # If not, it should default to the original course run number that the rerun is based on. + if number: + data.update({'number': number}) response = self.client.post(url, data, format='json') assert response.status_code == 201 course_run_key = CourseKey.from_string(response.data['id']) course_run = modulestore().get_course(course_run_key) + assert course_run.id.run == run assert course_run.self_paced is expected_self_paced_value + + if number: + assert course_run.id.course == number + assert course_run.id.course != original_course_run.id.course + else: + assert course_run.id.course == original_course_run.id.course + self.assert_course_run_schedule(course_run, start, end) self.assert_access_role(course_run, user, role) self.assert_course_access_role_count(course_run, 1) @@ -367,3 +379,16 @@ class CourseRunViewSetTests(ModuleStoreTestCase): response = self.client.post(url, data, format='json') assert response.status_code == 400 assert response.data == {'run': [u'Course run {key} already exists'.format(key=course_run.id)]} + + def test_rerun_invalid_number(self): + course_run = ToyCourseFactory() + url = reverse('api:v1:course_run-rerun', kwargs={'pk': str(course_run.id)}) + data = { + 'run': '2T2019', + 'number': '!@#$%^&*()', + } + response = self.client.post(url, data, format='json') + assert response.status_code == 400 + assert response.data == {'non_field_errors': [ + u'Invalid key supplied. Ensure there are no special characters in the Course Number.' + ]}