Skip to content

Commit b22beab

Browse files
committed
feat: add library migration list endpoint
1 parent 7cd4170 commit b22beab

3 files changed

Lines changed: 115 additions & 7 deletions

File tree

cms/djangoapps/modulestore_migrator/rest_api/v1/serializers.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
from opaque_keys import InvalidKeyError
66
from opaque_keys.edx.keys import LearningContextKey
77
from opaque_keys.edx.locator import LibraryLocatorV2
8+
from openedx_learning.api.authoring_models import Collection
89
from rest_framework import serializers
910
from user_tasks.serializers import StatusSerializer
1011

1112
from cms.djangoapps.modulestore_migrator.data import CompositionLevel, RepeatHandlingStrategy
12-
from cms.djangoapps.modulestore_migrator.models import ModulestoreMigration
13+
from cms.djangoapps.modulestore_migrator.models import ModulestoreMigration, ModulestoreSource
1314

1415

1516
class ModulestoreMigrationSerializer(serializers.Serializer):
@@ -173,3 +174,47 @@ def get_fields(self):
173174
fields = super().get_fields()
174175
fields.pop('name', None)
175176
return fields
177+
178+
179+
class LibraryMigrationCourseSourceSerializer(serializers.ModelSerializer):
180+
"""
181+
Serializer for the source course of a library migration.
182+
"""
183+
display_name = serializers.SerializerMethodField()
184+
185+
class Meta:
186+
model = ModulestoreSource
187+
fields = ['key', 'display_name']
188+
189+
def get_display_name(self, obj):
190+
"""
191+
Return the display name of the source course
192+
"""
193+
return self.context["course_names"].get(str(obj.key), None)
194+
195+
196+
class LibraryMigrationCollectionSerializer(serializers.ModelSerializer):
197+
"""
198+
Serializer for the target collection of a library migration.
199+
"""
200+
class Meta:
201+
model = Collection
202+
fields = ["key", "title"]
203+
204+
205+
class LibraryMigrationCourseSerializer(serializers.ModelSerializer):
206+
"""
207+
Serializer for the course or legacylibrary migrations to V2 library.
208+
"""
209+
source = LibraryMigrationCourseSourceSerializer() # type: ignore[assignment]
210+
target_collection = LibraryMigrationCollectionSerializer(required=False)
211+
task_status = StatusSerializer()
212+
213+
class Meta:
214+
model = ModulestoreMigration
215+
fields = [
216+
'source',
217+
'target_collection',
218+
'task_status',
219+
'is_failed',
220+
]

cms/djangoapps/modulestore_migrator/rest_api/v1/urls.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@
33
"""
44

55
from rest_framework.routers import SimpleRouter
6-
from .views import MigrationViewSet, BulkMigrationViewSet
6+
7+
from .views import BulkMigrationViewSet, LibraryCourseMigrationViewSet, MigrationViewSet
78

89
ROUTER = SimpleRouter()
910
ROUTER.register(r'migrations', MigrationViewSet, basename='migrations')
1011
ROUTER.register(r'bulk_migration', BulkMigrationViewSet, basename='bulk-migration')
12+
ROUTER.register(
13+
r'library/(?P<lib_key_str>[^/.]+)/migrations/courses',
14+
LibraryCourseMigrationViewSet,
15+
basename='library-migrations',
16+
)
17+
1118

1219
urlpatterns = ROUTER.urls

cms/djangoapps/modulestore_migrator/rest_api/v1/views.py

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,30 @@
66
import edx_api_doc_tools as apidocs
77
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
88
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
9+
from opaque_keys import InvalidKeyError
10+
from opaque_keys.edx.locator import LibraryLocatorV2
11+
from rest_framework import status
12+
from rest_framework.exceptions import ParseError
13+
from rest_framework.mixins import ListModelMixin
914
from rest_framework.permissions import IsAdminUser
1015
from rest_framework.response import Response
11-
from rest_framework import status
16+
from rest_framework.viewsets import GenericViewSet
1217
from user_tasks.models import UserTaskStatus
1318
from user_tasks.views import StatusViewSet
1419

15-
from cms.djangoapps.modulestore_migrator.api import start_migration_to_library, start_bulk_migration_to_library
20+
from cms.djangoapps.modulestore_migrator.api import start_bulk_migration_to_library, start_migration_to_library
21+
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
22+
from openedx.core.djangoapps.content_libraries import api as lib_api
1623
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
1724

25+
from ...models import ModulestoreMigration
1826
from .serializers import (
19-
StatusWithModulestoreMigrationsSerializer,
20-
ModulestoreMigrationSerializer,
2127
BulkModulestoreMigrationSerializer,
28+
LibraryMigrationCourseSerializer,
29+
ModulestoreMigrationSerializer,
30+
StatusWithModulestoreMigrationsSerializer,
2231
)
2332

24-
2533
log = logging.getLogger(__name__)
2634

2735

@@ -328,3 +336,51 @@ def cancel(self, request, *args, **kwargs):
328336
We disable this endpoint to avoid confusion.
329337
"""
330338
raise NotImplementedError
339+
340+
@apidocs.schema_for(
341+
"list",
342+
"List all course migrations to a library.",
343+
responses={
344+
201: LibraryMigrationCourseSerializer,
345+
401: "The requester is not authenticated.",
346+
403: "The requester does not have permission to access the library.",
347+
},
348+
)
349+
class LibraryCourseMigrationViewSet(GenericViewSet, ListModelMixin):
350+
"""
351+
Show infomation about migrations related to a destination library.
352+
"""
353+
354+
serializer_class = LibraryMigrationCourseSerializer
355+
pagination_class = None
356+
queryset = ModulestoreMigration.objects.all().select_related('target_collection', 'target', 'task_status')
357+
358+
def get_serializer_context(self):
359+
"""
360+
Add course name list to the serializer context.
361+
"""
362+
context = super().get_serializer_context()
363+
queryset = self.get_queryset()
364+
course_keys = queryset.values_list('source__key', flat=True)
365+
courses = CourseOverview.get_all_courses(course_keys=course_keys)
366+
context['course_names'] = dict((str(course.id), course.display_name) for course in courses)
367+
return context
368+
369+
def get_queryset(self):
370+
"""
371+
Override the default queryset to filter by the library key.
372+
"""
373+
queryset = super().get_queryset()
374+
lib_key_str = self.kwargs['lib_key_str']
375+
try:
376+
library_key = LibraryLocatorV2.from_string(lib_key_str)
377+
except InvalidKeyError as exc:
378+
raise ParseError(detail=f"Malformed library key: {lib_key_str}") from exc
379+
lib_api.require_permission_for_library_key(
380+
library_key,
381+
self.request.user,
382+
lib_api.permissions.CAN_VIEW_THIS_CONTENT_LIBRARY
383+
)
384+
queryset = queryset.filter(target__key=library_key, source__key__startswith='course-v1')
385+
386+
return queryset

0 commit comments

Comments
 (0)