Skip to content

Commit

Permalink
feat: add new endpoint for cloning course (#520)
Browse files Browse the repository at this point in the history
  • Loading branch information
pkulkark committed Feb 23, 2023
1 parent a45c466 commit 9d9e3eb
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 0 deletions.
33 changes: 33 additions & 0 deletions cms/djangoapps/api/v1/serializers/course_runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.db import transaction
from django.utils.translation import gettext_lazy as _
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from rest_framework import serializers
from rest_framework.fields import empty

Expand Down Expand Up @@ -203,3 +204,35 @@ def update(self, instance, validated_data):
course_run = get_course_and_check_access(new_course_run_key, user)
self.update_team(course_run, team)
return course_run


class CourseCloneSerializer(serializers.Serializer): # lint-amnesty, pylint: disable=abstract-method, missing-class-docstring
source_course_id = serializers.CharField()
destination_course_id = serializers.CharField()

def validate(self, attrs):
source_course_id = attrs.get('source_course_id')
destination_course_id = attrs.get('destination_course_id')
store = modulestore()
source_key = CourseKey.from_string(source_course_id)
dest_key = CourseKey.from_string(destination_course_id)

# Check if the source course exists
if not store.has_course(source_key):
raise serializers.ValidationError('Source course does not exist.')

# Check if the destination course already exists
if store.has_course(dest_key):
raise serializers.ValidationError('Destination course already exists.')
return attrs

def create(self, validated_data):
source_course_id = validated_data.get('source_course_id')
destination_course_id = validated_data.get('destination_course_id')
user_id = self.context['request'].user.id
store = modulestore()
source_key = CourseKey.from_string(source_course_id)
dest_key = CourseKey.from_string(destination_course_id)
with store.default_store('split'):
new_course = store.clone_course(source_key, dest_key, user_id)
return new_course
51 changes: 51 additions & 0 deletions cms/djangoapps/api/v1/tests/test_views/test_course_runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,3 +402,54 @@ def test_rerun_invalid_number(self):
assert response.data == {'non_field_errors': [
'Invalid key supplied. Ensure there are no special characters in the Course Number.'
]}

def test_clone_course(self):
course = CourseFactory()
url = reverse('api:v1:course_run-clone')
data = {
'source_course_id': str(course.id),
'destination_course_id': 'course-v1:destination+course+id',
}
response = self.client.post(url, data, format='json')
assert response.status_code == 201
self.assertEqual(response.data, {"message": "Course cloned successfully."})

def test_clone_course_with_missing_source_id(self):
url = reverse('api:v1:course_run-clone')
data = {
'destination_course_id': 'course-v1:destination+course+id',
}
response = self.client.post(url, data, format='json')
assert response.status_code == 400
self.assertEqual(response.data, {'source_course_id': ['This field is required.']})

def test_clone_course_with_missing_dest_id(self):
url = reverse('api:v1:course_run-clone')
data = {
'source_course_id': 'course-v1:source+course+id',
}
response = self.client.post(url, data, format='json')
assert response.status_code == 400
self.assertEqual(response.data, {'destination_course_id': ['This field is required.']})

def test_clone_course_with_nonexistent_source_course(self):
url = reverse('api:v1:course_run-clone')
data = {
'source_course_id': 'course-v1:nonexistent+source+course_id',
'destination_course_id': 'course-v1:destination+course+id',
}
response = self.client.post(url, data, format='json')
assert response.status_code == 400
assert str(response.data.get('non_field_errors')[0]) == 'Source course does not exist.'

def test_clone_course_with_existing_dest_course(self):
url = reverse('api:v1:course_run-clone')
course = CourseFactory()
existing_dest_course = CourseFactory()
data = {
'source_course_id': str(course.id),
'destination_course_id': str(existing_dest_course.id),
}
response = self.client.post(url, data, format='json')
assert response.status_code == 400
assert str(response.data.get('non_field_errors')[0]) == 'Destination course already exists.'
8 changes: 8 additions & 0 deletions cms/djangoapps/api/v1/views/course_runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from cms.djangoapps.contentstore.views.course import _accessible_courses_iter, get_course_and_check_access

from ..serializers.course_runs import (
CourseCloneSerializer,
CourseRunCreateSerializer,
CourseRunImageSerializer,
CourseRunRerunSerializer,
Expand Down Expand Up @@ -93,3 +94,10 @@ def rerun(self, request, *args, **kwargs): # lint-amnesty, pylint: disable=miss
new_course_run = serializer.save()
serializer = self.get_serializer(new_course_run)
return Response(serializer.data, status=status.HTTP_201_CREATED)

@action(detail=False, methods=['post'])
def clone(self, request, *args, **kwargs): # lint-amnesty, pylint: disable=missing-function-docstring, unused-argument
serializer = CourseCloneSerializer(data=request.data, context=self.get_serializer_context())
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({"message": "Course cloned successfully."}, status=status.HTTP_201_CREATED)

0 comments on commit 9d9e3eb

Please sign in to comment.