Skip to content

Commit 9ee5990

Browse files
authored
fix: always return an absolute url in libraries backup endpoint (#37508)
The 'url' field on the GET /api/libraries/v2/{library_id}/backup/?task_id={task_id} endpoint was returning realtive paths when the file was stored on the default FileSystemStorage backend, which makes it inconsistent with other storage backends and semantically incorrect. This commit addresses this making sure it always returns an absolute url.
1 parent 3f5ac6d commit 9ee5990

4 files changed

Lines changed: 10 additions & 10 deletions

File tree

openedx/core/djangoapps/content_libraries/api/libraries.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,7 @@ def get_backup_task_status(
681681
682682
Returns a dictionary with the following keys:
683683
- state: One of "Pending", "Exporting", "Succeeded", "Failed"
684-
- url: If state is "Succeeded", the URL where the exported .zip file can be downloaded. Otherwise, None.
684+
- file: If state is "Succeeded", the FileField of the exported .zip. Otherwise, None.
685685
If no task is found, returns None.
686686
"""
687687

@@ -690,10 +690,10 @@ def get_backup_task_status(
690690
except UserTaskStatus.DoesNotExist:
691691
return None
692692

693-
result = {'state': task_status.state, 'url': None}
693+
result = {'state': task_status.state, 'file': None}
694694

695695
if task_status.state == UserTaskStatus.SUCCEEDED:
696696
artifact = UserTaskArtifact.objects.get(status=task_status, name='Output')
697-
result['url'] = artifact.file.storage.url(artifact.file.name)
697+
result['file'] = artifact.file
698698

699699
return result

openedx/core/djangoapps/content_libraries/rest_api/libraries.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -786,8 +786,8 @@ def get(self, request, lib_key_str):
786786

787787
if not result:
788788
raise NotFound(detail="No backup found for this library.")
789-
790-
return Response(LibraryBackupTaskStatusSerializer(result).data)
789+
# Passing request context to the serializer so the url absolute path is correctly generated
790+
return Response(LibraryBackupTaskStatusSerializer(result, context={'request': request}).data)
791791

792792

793793
# LTI 1.3 Views

openedx/core/djangoapps/content_libraries/rest_api/serializers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,4 +425,4 @@ class LibraryBackupTaskStatusSerializer(serializers.Serializer):
425425
Serializer for checking the status of a library backup task.
426426
"""
427427
state = serializers.CharField()
428-
url = serializers.URLField(allow_null=True)
428+
url = serializers.FileField(source='file', allow_null=True, use_url=True)

openedx/core/djangoapps/content_libraries/tests/test_api.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,7 +1430,7 @@ def test_get_backup_task_status_in_progress(self) -> None:
14301430
status = api.get_backup_task_status(self.user.id, task_id=task_id)
14311431
assert status is not None
14321432
assert status['state'] == UserTaskStatus.IN_PROGRESS
1433-
assert status['url'] is None
1433+
assert status['file'] is None
14341434

14351435
def test_get_backup_task_status_succeeded(self) -> None:
14361436
# Create a mock UserTaskStatus in SUCCEEDED state
@@ -1444,7 +1444,7 @@ def test_get_backup_task_status_succeeded(self) -> None:
14441444

14451445
# Create a mock UserTaskArtifact
14461446
mock_artifact = mock.Mock()
1447-
mock_artifact.file.storage.url.return_value = "/media/user_tasks/2025/10/01/library-libOEXCSPROB_mOw1rPL.zip"
1447+
mock_artifact.file.url = "/media/user_tasks/2025/10/01/library-libOEXCSPROB_mOw1rPL.zip"
14481448

14491449
with mock.patch(
14501450
'openedx.core.djangoapps.content_libraries.api.libraries.UserTaskStatus.objects.get'
@@ -1458,7 +1458,7 @@ def test_get_backup_task_status_succeeded(self) -> None:
14581458
status = api.get_backup_task_status(self.user.id, task_id=task_id)
14591459
assert status is not None
14601460
assert status['state'] == UserTaskStatus.SUCCEEDED
1461-
assert status['url'] == "/media/user_tasks/2025/10/01/library-libOEXCSPROB_mOw1rPL.zip"
1461+
assert status['file'].url == "/media/user_tasks/2025/10/01/library-libOEXCSPROB_mOw1rPL.zip"
14621462

14631463
def test_get_backup_task_status_failed(self) -> None:
14641464
# Create a mock UserTaskStatus in FAILED state
@@ -1478,4 +1478,4 @@ def test_get_backup_task_status_failed(self) -> None:
14781478
status = api.get_backup_task_status(self.user.id, task_id=task_id)
14791479
assert status is not None
14801480
assert status['state'] == UserTaskStatus.FAILED
1481-
assert status['url'] is None
1481+
assert status['file'] is None

0 commit comments

Comments
 (0)