|
7 | 7 | from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication |
8 | 8 | from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser |
9 | 9 | from opaque_keys import InvalidKeyError |
| 10 | +from opaque_keys.edx.keys import CourseKey |
10 | 11 | from opaque_keys.edx.locator import LibraryLocatorV2 |
11 | 12 | from rest_framework import status |
12 | 13 | from rest_framework.exceptions import ParseError |
| 14 | +from rest_framework.fields import BooleanField |
13 | 15 | from rest_framework.mixins import ListModelMixin |
14 | 16 | from rest_framework.permissions import IsAdminUser, IsAuthenticated |
| 17 | +from rest_framework.request import Request |
15 | 18 | from rest_framework.response import Response |
16 | 19 | from rest_framework.views import APIView |
17 | 20 | from rest_framework.viewsets import GenericViewSet |
18 | 21 | from user_tasks.models import UserTaskStatus |
19 | 22 | from user_tasks.views import StatusViewSet |
20 | | -from opaque_keys.edx.keys import CourseKey |
21 | 23 |
|
22 | 24 | from cms.djangoapps.modulestore_migrator.api import ( |
23 | | - start_migration_to_library, |
24 | | - start_bulk_migration_to_library, |
25 | 25 | get_all_migrations_info, |
| 26 | + get_migration_blocks_info, |
| 27 | + start_bulk_migration_to_library, |
| 28 | + start_migration_to_library, |
26 | 29 | ) |
| 30 | +from common.djangoapps.student.auth import has_studio_write_access |
27 | 31 | from openedx.core.djangoapps.content.course_overviews.models import CourseOverview |
28 | 32 | from openedx.core.djangoapps.content_libraries import api as lib_api |
29 | 33 | from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser |
30 | | -from common.djangoapps.student.auth import has_studio_write_access |
31 | 34 |
|
32 | 35 | from ...models import ModulestoreMigration |
33 | 36 | from .serializers import ( |
| 37 | + BlockMigrationInfoSerializer, |
34 | 38 | BulkModulestoreMigrationSerializer, |
35 | | - MigrationInfoResponseSerializer, |
36 | 39 | LibraryMigrationCourseSerializer, |
| 40 | + MigrationInfoResponseSerializer, |
37 | 41 | ModulestoreMigrationSerializer, |
38 | 42 | StatusWithModulestoreMigrationsSerializer, |
39 | 43 | ) |
@@ -493,3 +497,105 @@ def get_queryset(self): |
493 | 497 | queryset = queryset.filter(target__key=library_key, source__key__startswith='course-v1') |
494 | 498 |
|
495 | 499 | return queryset |
| 500 | + |
| 501 | + |
| 502 | +class BlockMigrationInfo(APIView): |
| 503 | + """ |
| 504 | + Retrieve migration blocks information given task_uuid, source_key or target_key. |
| 505 | +
|
| 506 | + It returns the migration block information for each block migrated by a specific task. |
| 507 | +
|
| 508 | + API Endpoints |
| 509 | + ------------- |
| 510 | + GET /api/modulestore_migrator/v1/migration_blocks/ |
| 511 | + Retrieve migration blocks info for given task_uuid, source_key or target_key. |
| 512 | +
|
| 513 | + Query parameters: |
| 514 | + task_uuid (str): task uuid |
| 515 | + Example: ?task_uuid=dfe72eca-c54f-4b43-b53b-7996031f2102 |
| 516 | + source_key (str): Source content key |
| 517 | + Example: ?source_key=course-v1:UNIX+UX1+2025_T3 |
| 518 | + target_key (str): target content key |
| 519 | + Example: ?target_key=lib:UNIX:CIT1 |
| 520 | + is_failed (boolean): has the block failed to migrate/import |
| 521 | + Example: ?is_failed=true |
| 522 | +
|
| 523 | + Example request: |
| 524 | + GET /api/modulestore_migrator/v1/migration_blocks/?task_uuid=dfe72eca-c54f-4b43-b53b&is_failed=true |
| 525 | +
|
| 526 | + Example response: |
| 527 | + """ |
| 528 | + |
| 529 | + permission_classes = (IsAuthenticated,) |
| 530 | + authentication_classes = ( |
| 531 | + BearerAuthenticationAllowInactiveUser, |
| 532 | + JwtAuthentication, |
| 533 | + SessionAuthenticationAllowInactiveUser, |
| 534 | + ) |
| 535 | + |
| 536 | + @apidocs.schema( |
| 537 | + parameters=[ |
| 538 | + apidocs.string_parameter( |
| 539 | + "target_key", |
| 540 | + apidocs.ParameterLocation.QUERY, |
| 541 | + description="Filter blocks by target key", |
| 542 | + ), |
| 543 | + apidocs.string_parameter( |
| 544 | + "source_key", |
| 545 | + apidocs.ParameterLocation.QUERY, |
| 546 | + description="Filter blocks by source key", |
| 547 | + ), |
| 548 | + apidocs.string_parameter( |
| 549 | + "target_collection_key", |
| 550 | + apidocs.ParameterLocation.QUERY, |
| 551 | + description="Filter blocks by target_collection_key", |
| 552 | + ), |
| 553 | + apidocs.string_parameter( |
| 554 | + "task_uuid", |
| 555 | + apidocs.ParameterLocation.QUERY, |
| 556 | + description="Filter blocks by task_uuid", |
| 557 | + ), |
| 558 | + apidocs.string_parameter( |
| 559 | + "is_failed", |
| 560 | + apidocs.ParameterLocation.QUERY, |
| 561 | + description="Filter blocks based on its migration status", |
| 562 | + ), |
| 563 | + ], |
| 564 | + responses={ |
| 565 | + 200: MigrationInfoResponseSerializer, |
| 566 | + 400: "Missing required parameter: target_key", |
| 567 | + 401: "The requester is not authenticated.", |
| 568 | + }, |
| 569 | + ) |
| 570 | + def get(self, request: Request): |
| 571 | + """ |
| 572 | + Handle the migration info `GET` request |
| 573 | + """ |
| 574 | + source_key = request.query_params.get("source_key") |
| 575 | + target_key = request.query_params.get("target_key") |
| 576 | + target_collection_key = request.query_params.get("target_collection_key") |
| 577 | + task_uuid = request.query_params.get("task_uuid") |
| 578 | + is_failed: str | bool | None = request.query_params.get("is_failed") |
| 579 | + if not target_key: |
| 580 | + return Response({"error": "Target key cannot be blank."}, status=400) |
| 581 | + try: |
| 582 | + target_key_parsed = LibraryLocatorV2.from_string(target_key) |
| 583 | + except InvalidKeyError as e: |
| 584 | + return Response({"error": str(e)}, status=400) |
| 585 | + lib_api.require_permission_for_library_key( |
| 586 | + target_key_parsed, |
| 587 | + request.user, |
| 588 | + lib_api.permissions.CAN_VIEW_THIS_CONTENT_LIBRARY |
| 589 | + ) |
| 590 | + if is_failed is not None: |
| 591 | + is_failed = BooleanField().to_internal_value(is_failed) |
| 592 | + |
| 593 | + data = get_migration_blocks_info( |
| 594 | + target_key, |
| 595 | + source_key, |
| 596 | + target_collection_key, |
| 597 | + task_uuid, |
| 598 | + is_failed, |
| 599 | + ).values('source__key', 'target__key', 'unsupported_reason') |
| 600 | + serializer = BlockMigrationInfoSerializer(data, many=True) |
| 601 | + return Response(serializer.data) |
0 commit comments