Skip to content

Commit e4e504b

Browse files
committed
feat: add library migration list endpoint
1 parent 6deb4f8 commit e4e504b

File tree

3 files changed

+116
-7
lines changed

3 files changed

+116
-7
lines changed

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: 62 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-
StatusWithModulestoreMigrationSerializer,
20-
ModulestoreMigrationSerializer,
2127
BulkModulestoreMigrationSerializer,
28+
LibraryMigrationCourseSerializer,
29+
ModulestoreMigrationSerializer,
30+
StatusWithModulestoreMigrationSerializer
2231
)
2332

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

2735

@@ -281,3 +289,52 @@ def create(self, request, *args, **kwargs):
281289
serializer = self.get_serializer(task_status)
282290

283291
return Response(serializer.data, status=status.HTTP_201_CREATED)
292+
293+
294+
@apidocs.schema_for(
295+
"list",
296+
"List all course migrations to a library.",
297+
responses={
298+
201: LibraryMigrationCourseSerializer,
299+
401: "The requester is not authenticated.",
300+
403: "The requester does not have permission to access the library.",
301+
},
302+
)
303+
class LibraryCourseMigrationViewSet(GenericViewSet, ListModelMixin):
304+
"""
305+
Show infomation about migrations related to a destination library.
306+
"""
307+
308+
serializer_class = LibraryMigrationCourseSerializer
309+
pagination_class = None
310+
queryset = ModulestoreMigration.objects.all().select_related('target_collection', 'target', 'task_status')
311+
312+
def get_serializer_context(self):
313+
"""
314+
Add course name list to the serializer context.
315+
"""
316+
context = super().get_serializer_context()
317+
queryset = self.get_queryset()
318+
course_keys = queryset.values_list('source__key', flat=True)
319+
courses = CourseOverview.get_all_courses(course_keys=course_keys)
320+
context['course_names'] = dict((str(course.id), course.display_name) for course in courses)
321+
return context
322+
323+
def get_queryset(self):
324+
"""
325+
Override the default queryset to filter by the library key.
326+
"""
327+
queryset = super().get_queryset()
328+
lib_key_str = self.kwargs['lib_key_str']
329+
try:
330+
library_key = LibraryLocatorV2.from_string(lib_key_str)
331+
except InvalidKeyError as exc:
332+
raise ParseError(detail=f"Malformed library key: {lib_key_str}") from exc
333+
lib_api.require_permission_for_library_key(
334+
library_key,
335+
self.request.user,
336+
lib_api.permissions.CAN_VIEW_THIS_CONTENT_LIBRARY
337+
)
338+
queryset = queryset.filter(target__key=library_key, source__key__startswith='course-v1')
339+
340+
return queryset

0 commit comments

Comments
 (0)