Skip to content

Commit 7275e17

Browse files
refactor: change REST APIs after refactor models
1 parent 51244d0 commit 7275e17

File tree

4 files changed

+135
-96
lines changed

4 files changed

+135
-96
lines changed

cms/djangoapps/import_from_modulestore/views/v0/serializers.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from rest_framework import serializers
66

77
from cms.djangoapps.import_from_modulestore.validators import validate_composition_level
8+
from cms.djangoapps.import_from_modulestore.models import Import
89

910

1011
class ImportBlocksSerializer(serializers.Serializer):
@@ -16,6 +17,7 @@ class ImportBlocksSerializer(serializers.Serializer):
1617
child=serializers.CharField(),
1718
required=True,
1819
)
20+
target_library = serializers.CharField(required=True)
1921
import_uuid = serializers.CharField(required=True)
2022
composition_level = serializers.CharField(
2123
required=True,
@@ -24,12 +26,26 @@ class ImportBlocksSerializer(serializers.Serializer):
2426
override = serializers.BooleanField(default=False, required=False)
2527

2628

27-
class CourseToLibraryImportSerializer(serializers.Serializer):
29+
class ImportSerializer(serializers.ModelSerializer):
2830
"""
2931
Serializer for the course to library import creation API.
3032
"""
3133

3234
course_ids = serializers.ListField()
33-
status = serializers.CharField(allow_blank=True, required=False)
34-
library_key = serializers.CharField(allow_blank=True, required=False)
35-
uuid = serializers.CharField(allow_blank=True, required=False)
35+
course_id = serializers.CharField(source='source_key', required=False)
36+
37+
class Meta:
38+
model = Import
39+
fields = [
40+
'uuid',
41+
'course_id',
42+
'course_ids',
43+
'status',
44+
]
45+
46+
def to_representation(self, instance):
47+
return {
48+
'uuid': str(instance.uuid),
49+
'course_id': str(instance.source_key),
50+
'status': instance.get_status_display(),
51+
}

cms/djangoapps/import_from_modulestore/views/v0/tests/test_views.py

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,7 @@ def setUp(self):
4646
self.problem = BlockFactory.create(category='problem', parent=self.vertical)
4747

4848
with self.captureOnCommitCallbacks(execute=True):
49-
self.import_event = api.create_import(
50-
user_id=self.admin_user.pk,
51-
learning_package_id=self.library.learning_package_id,
52-
source_key=self.course.id,
53-
)
49+
self.import_event = api.create_import(user_id=self.admin_user.pk, source_key=self.course.id)
5450

5551

5652
class ImportBlocksViewTest(TestCourseToLibraryImportViewsMixin):
@@ -65,6 +61,7 @@ def setUp(self):
6561
self.valid_data = {
6662
'usage_ids': [str(self.chapter.location)],
6763
'import_uuid': self.import_event.uuid,
64+
'target_library': self.library_id,
6865
'composition_level': 'xblock',
6966
'override': False,
7067
}
@@ -111,6 +108,7 @@ def test_successful_import(self, mock_import):
111108
mock_import.assert_called_once_with(
112109
usage_ids=self.valid_data['usage_ids'],
113110
import_uuid=str(self.valid_data['import_uuid']),
111+
target_learning_package_id=self.library.learning_package_id,
114112
user_id=self.admin_user.pk,
115113
composition_level=self.valid_data['composition_level'],
116114
override=self.valid_data['override'],
@@ -125,7 +123,7 @@ class TestCreateCourseToLibraryImportView(TestCourseToLibraryImportViewsMixin):
125123
def setUp(self):
126124
super().setUp()
127125

128-
self.url = reverse('import_from_modulestore:v0:create_import', args=[self.library_id])
126+
self.url = reverse('import_from_modulestore:v0:create_import')
129127
self.valid_data = {
130128
'course_ids': ['course-v1:org+course+run', 'course-v1:org2+course2+run2'],
131129
}
@@ -160,35 +158,19 @@ def test_successful_import(self):
160158
Test successful import returns a success response.
161159
"""
162160
self.client.force_authenticate(user=self.admin_user)
163-
expected_response = {
164-
'result': []
165-
}
161+
expected_response = []
166162

167163
response = self.client.post(self.url, self.valid_data, format='json')
168164

169165
for course_id in self.valid_data['course_ids']:
170-
expected_response['result'].append({
166+
expected_response.append({
171167
'uuid': str(Import.objects.get(source_key=CourseKey.from_string(course_id)).uuid),
172168
'course_id': course_id,
173-
'status': 'Pending',
174-
'library_key': str(self.library_id),
169+
'status': ImportStatus.NOT_STARTED.label,
175170
})
176171
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
177172
self.assertEqual(response.data, expected_response)
178173

179-
def test_non_existent_library(self):
180-
"""
181-
Test that a non-existent library returns a 404 response.
182-
"""
183-
self.client.force_authenticate(user=self.admin_user)
184-
185-
response = self.client.post(
186-
reverse('import_from_modulestore:v0:create_import', args=['lib:org:lib2']),
187-
self.valid_data,
188-
format='json'
189-
)
190-
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
191-
192174

193175
class GetCourseStructureToLibraryImportView(TestCourseToLibraryImportViewsMixin):
194176
"""
@@ -231,7 +213,7 @@ def test_get_course_structure_not_found(self):
231213

232214
response = self.client.get(reverse(
233215
'import_from_modulestore:v0:get_import',
234-
kwargs={'course_to_lib_uuid': '593e93d7-ed64-4147-bb5c-4cfcb1cf80b1'})
216+
kwargs={'import_uuid': '593e93d7-ed64-4147-bb5c-4cfcb1cf80b1'})
235217
)
236218
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
237219

cms/djangoapps/import_from_modulestore/views/v0/urls.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@
1313
app_name = 'v0'
1414
urlpatterns = [
1515
path('import_blocks/', ImportBlocksView.as_view(), name='import_blocks'),
16-
path('create_import/<str:content_library_id>/', CreateCourseToLibraryImportView.as_view(), name='create_import'),
17-
path('get_import/<uuid:course_to_lib_uuid>/', GetCourseStructureToLibraryImportView.as_view(), name='get_import'),
16+
path('create_import/', CreateCourseToLibraryImportView.as_view(), name='create_import'),
17+
path('get_import/<uuid:import_uuid>/', GetCourseStructureToLibraryImportView.as_view(), name='get_import'),
1818
]

cms/djangoapps/import_from_modulestore/views/v0/views.py

Lines changed: 105 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,49 @@
1919
from cms.djangoapps.import_from_modulestore import api
2020
from cms.djangoapps.import_from_modulestore.models import Import
2121
from cms.djangoapps.import_from_modulestore.permissions import IsImportAuthor
22-
from cms.djangoapps.import_from_modulestore.views.v0.serializers import CourseToLibraryImportSerializer
22+
from cms.djangoapps.import_from_modulestore.views.v0.serializers import ImportBlocksSerializer, ImportSerializer
2323
from openedx.core.djangoapps.content_libraries.api import ContentLibrary
2424
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
25-
from .serializers import ImportBlocksSerializer
2625

2726

2827
class ImportBlocksView(APIView):
2928
"""
3029
Import blocks from a course to a library.
30+
31+
**Example Request**
32+
POST /api/import_from_modulestore/v0/import_blocks/
33+
34+
**Example request data**
35+
```
36+
{
37+
"usage_ids": ["block-v1:org+course+run+type@problem+block@12345"],
38+
"target_library": "lib:org:test",
39+
"import_uuid": "78df3b2c-4e5a-4d6b-8c7e-1f2a3b4c5d6e",
40+
"composition_level": "xblock",
41+
"override": false
42+
}
43+
```
44+
45+
**POST Parameters**
46+
- usage_ids (list): A list of usage IDs of the blocks to be imported.
47+
- target_library (str): The library to which the blocks will be imported.
48+
- import_uuid (str): The UUID of the import task.
49+
- composition_level (str): The composition level of the blocks to be imported.
50+
- override (bool): Whether to override existing blocks in the library.
51+
52+
**Responses**
53+
- 200: Import blocks from a course to a library task successfully started.
54+
- 400: Invalid request data.
55+
- 401: Unauthorized.
56+
- 403: Forbidden, request user is not the author of the received import.
57+
- 404: Import not found.
58+
59+
**Example Response**:
60+
```
61+
{
62+
"status": "success"
63+
}
64+
```
3165
"""
3266

3367
serializer_class = ImportBlocksSerializer
@@ -42,54 +76,70 @@ class ImportBlocksView(APIView):
4276
def post(self, request, *args, **kwargs):
4377
"""
4478
Import blocks from a course to a library.
45-
API endpoint: POST /api/import_from_modulestore/v0/import_blocks/
46-
Request:
47-
{
48-
"usage_ids": ["block-v1:org+course+run+type@problem+block@12345"],
49-
"import_uuid": "78df3b2c-4e5a-4d6b-8c7e-1f2a3b4c5d6e",
50-
"composition_level": "xblock",
51-
"override": false
52-
}
53-
Response:
54-
{
55-
"status": "success"
56-
}
5779
"""
58-
data = self.serializer_class(data=request.data)
59-
data.is_valid(raise_exception=True)
80+
serializer = self.serializer_class(data=request.data)
81+
serializer.is_valid(raise_exception=True)
82+
83+
library_key = LibraryLocatorV2.from_string(serializer.validated_data['target_library'])
84+
try:
85+
content_library = ContentLibrary.objects.get_by_key(library_key)
86+
except ContentLibrary.DoesNotExist:
87+
return Response(status=status.HTTP_404_NOT_FOUND)
6088

6189
api.import_course_staged_content_to_library(
62-
usage_ids=data.validated_data['usage_ids'],
63-
import_uuid=data.validated_data['import_uuid'],
90+
usage_ids=serializer.validated_data['usage_ids'],
91+
import_uuid=serializer.validated_data['import_uuid'],
92+
target_learning_package_id=content_library.learning_package_id,
6493
user_id=request.user.pk,
65-
composition_level=data.validated_data['composition_level'],
66-
override=data.validated_data['override'],
94+
composition_level=serializer.validated_data['composition_level'],
95+
override=serializer.validated_data['override'],
6796
)
6897
return Response({'status': 'success'})
6998

7099

71100
class CreateCourseToLibraryImportView(CreateAPIView):
72101
"""
73-
**Use Case**
74-
Allows to create course to library import.
102+
Create course to library import.
103+
75104
**Example Request**
76-
POST /api/import_from_modulestore/v0/create_import/<content_library_id>/
77-
**POST Parameters**
78-
* course_ids (list) - A list of course IDs whose content will be saved
79-
in Staged Content for further import.
80-
**POST Response Values**
81-
If the request is successful, an HTTP 201 "Created" response
82-
is returned with the newly created Import details.
83-
The HTTP 201 response has the following values.
105+
POST /api/import_from_modulestore/v0/create_import/
106+
107+
**Example request data**
108+
```
84109
{
85-
"course_ids": ["course-v1:edX+DemoX+Demo_Course", "course-v1:edX+DemoX+Demo_Course2"],
86-
"status": "pending",
87-
"library_key": "lib:edX:1",
88-
"uuid": "89b71d29-2135-4cf2-991d-e4e13b5a959a"
110+
"course_ids": ["course-v1:edX+DemoX+Demo_Course", "course-v1:edX+M12+2025"],
89111
}
112+
```
113+
114+
**POST Parameters**
115+
- course_ids (list): A list of course IDs for which imports will be created
116+
and content will be saved to the Staged Content.
117+
118+
**Responses**
119+
- 200: Imports created successfully and saving content to Staged Content started.
120+
- 400: Invalid request data.
121+
- 401: Unauthorized.
122+
- 403: Forbidden.
123+
- 404: ContentLibrary not found.
124+
125+
**Example Response**:
126+
```
127+
[
128+
{
129+
"course_id": "course-v1:edX+DemoX+Demo_Course",
130+
"status": "staging",
131+
"uuid": "89b71d29-2135-4cf2-991d-e4e13b5a959a"
132+
},
133+
{
134+
"course_id": "course-v1:edX+M12+2025",
135+
"status": "not_started",
136+
"uuid": "0782921a-4b56-4972-aa3a-edd1c99de85f"
137+
},
138+
]
139+
```
90140
"""
91141

92-
serializer_class = CourseToLibraryImportSerializer
142+
serializer_class = ImportSerializer
93143

94144
permission_classes = (IsAdminUser,)
95145
authentication_classes = (
@@ -98,45 +148,36 @@ class CreateCourseToLibraryImportView(CreateAPIView):
98148
SessionAuthenticationAllowInactiveUser,
99149
)
100150

101-
def get_serializer_context(self) -> dict:
102-
"""
103-
Add library_id to the serializer context.
104-
"""
105-
context = super().get_serializer_context()
106-
context['content_library_id'] = self.kwargs['content_library_id']
107-
return context
108-
109151
def post(self, request, *args, **kwargs):
110152
"""
111153
Create course to library import.
112154
"""
113-
library_key = LibraryLocatorV2.from_string(self.kwargs['content_library_id'])
114-
115-
try:
116-
content_library = ContentLibrary.objects.get_by_key(library_key)
117-
except ContentLibrary.DoesNotExist:
118-
return Response(status=status.HTTP_404_NOT_FOUND)
119155
serializer = self.get_serializer(data=request.data)
120156
serializer.is_valid(raise_exception=True)
121157

122-
result = []
123-
for course_id in serializer.validated_data['course_ids']:
124-
import_event = api.create_import(course_id, request.user.pk, content_library.learning_package.id)
125-
result.append({
126-
'uuid': str(import_event.uuid),
127-
'course_id': str(import_event.source_key),
128-
'status': import_event.get_status_display(),
129-
'library_key': str(import_event.target.contentlibrary.library_key)
130-
})
131-
return Response({'result': result}, status=status.HTTP_201_CREATED)
158+
result = (
159+
api.create_import(course_id, request.user.pk)
160+
for course_id in serializer.validated_data['course_ids']
161+
)
162+
163+
serializer = self.get_serializer(result, many=True)
164+
return Response(serializer.data, status=status.HTTP_201_CREATED)
132165

133166

134167
class GetCourseStructureToLibraryImportView(RetrieveAPIView):
135168
"""
136-
**Use Case**
137-
Get the course structure saved when creating the import.
169+
Get the course structure saved when creating the import.
170+
138171
**Example Request**
139-
GET /api/import_from_modulestore/v0/get_import/{course-to-library-uuid}/
172+
GET /api/import_from_modulestore/v0/get_import/{import_uuid}/
173+
174+
**Responses**
175+
- 200: Course structure retrieved successfully.
176+
- 400: Invalid request data.
177+
- 401: Unauthorized.
178+
- 403: Forbidden.
179+
- 404: Import not found.
180+
140181
**GET Response Values**
141182
The query returns a list of hierarchical structures of
142183
courses that are related to the import in the format:
@@ -202,7 +243,7 @@ class GetCourseStructureToLibraryImportView(RetrieveAPIView):
202243

203244
queryset = Import.objects.all()
204245
lookup_field = 'uuid'
205-
lookup_url_kwarg = 'course_to_lib_uuid'
246+
lookup_url_kwarg = 'import_uuid'
206247

207248
permission_classes = (IsAdminUser,)
208249
authentication_classes = (
@@ -215,7 +256,7 @@ def get(self, request, *args, **kwargs) -> Response:
215256
"""
216257
Get the course structure saved when creating the import.
217258
"""
218-
import_event = get_object_or_404(Import, uuid=self.kwargs['course_to_lib_uuid'])
259+
import_event = get_object_or_404(Import, uuid=self.kwargs['import_uuid'])
219260
staged_content = [
220261
staged_content_for_import.staged_content
221262
for staged_content_for_import in import_event.staged_content_for_import.all()

0 commit comments

Comments
 (0)