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.
This commit is contained in:
@@ -681,7 +681,7 @@ def get_backup_task_status(
|
||||
|
||||
Returns a dictionary with the following keys:
|
||||
- state: One of "Pending", "Exporting", "Succeeded", "Failed"
|
||||
- url: If state is "Succeeded", the URL where the exported .zip file can be downloaded. Otherwise, None.
|
||||
- file: If state is "Succeeded", the FileField of the exported .zip. Otherwise, None.
|
||||
If no task is found, returns None.
|
||||
"""
|
||||
|
||||
@@ -690,10 +690,10 @@ def get_backup_task_status(
|
||||
except UserTaskStatus.DoesNotExist:
|
||||
return None
|
||||
|
||||
result = {'state': task_status.state, 'url': None}
|
||||
result = {'state': task_status.state, 'file': None}
|
||||
|
||||
if task_status.state == UserTaskStatus.SUCCEEDED:
|
||||
artifact = UserTaskArtifact.objects.get(status=task_status, name='Output')
|
||||
result['url'] = artifact.file.storage.url(artifact.file.name)
|
||||
result['file'] = artifact.file
|
||||
|
||||
return result
|
||||
|
||||
@@ -786,8 +786,8 @@ class LibraryBackupView(APIView):
|
||||
|
||||
if not result:
|
||||
raise NotFound(detail="No backup found for this library.")
|
||||
|
||||
return Response(LibraryBackupTaskStatusSerializer(result).data)
|
||||
# Passing request context to the serializer so the url absolute path is correctly generated
|
||||
return Response(LibraryBackupTaskStatusSerializer(result, context={'request': request}).data)
|
||||
|
||||
|
||||
# LTI 1.3 Views
|
||||
|
||||
@@ -425,4 +425,4 @@ class LibraryBackupTaskStatusSerializer(serializers.Serializer):
|
||||
Serializer for checking the status of a library backup task.
|
||||
"""
|
||||
state = serializers.CharField()
|
||||
url = serializers.URLField(allow_null=True)
|
||||
url = serializers.FileField(source='file', allow_null=True, use_url=True)
|
||||
|
||||
@@ -1430,7 +1430,7 @@ class ContentLibraryExportTest(ContentLibrariesRestApiTest):
|
||||
status = api.get_backup_task_status(self.user.id, task_id=task_id)
|
||||
assert status is not None
|
||||
assert status['state'] == UserTaskStatus.IN_PROGRESS
|
||||
assert status['url'] is None
|
||||
assert status['file'] is None
|
||||
|
||||
def test_get_backup_task_status_succeeded(self) -> None:
|
||||
# Create a mock UserTaskStatus in SUCCEEDED state
|
||||
@@ -1444,7 +1444,7 @@ class ContentLibraryExportTest(ContentLibrariesRestApiTest):
|
||||
|
||||
# Create a mock UserTaskArtifact
|
||||
mock_artifact = mock.Mock()
|
||||
mock_artifact.file.storage.url.return_value = "/media/user_tasks/2025/10/01/library-libOEXCSPROB_mOw1rPL.zip"
|
||||
mock_artifact.file.url = "/media/user_tasks/2025/10/01/library-libOEXCSPROB_mOw1rPL.zip"
|
||||
|
||||
with mock.patch(
|
||||
'openedx.core.djangoapps.content_libraries.api.libraries.UserTaskStatus.objects.get'
|
||||
@@ -1458,7 +1458,7 @@ class ContentLibraryExportTest(ContentLibrariesRestApiTest):
|
||||
status = api.get_backup_task_status(self.user.id, task_id=task_id)
|
||||
assert status is not None
|
||||
assert status['state'] == UserTaskStatus.SUCCEEDED
|
||||
assert status['url'] == "/media/user_tasks/2025/10/01/library-libOEXCSPROB_mOw1rPL.zip"
|
||||
assert status['file'].url == "/media/user_tasks/2025/10/01/library-libOEXCSPROB_mOw1rPL.zip"
|
||||
|
||||
def test_get_backup_task_status_failed(self) -> None:
|
||||
# Create a mock UserTaskStatus in FAILED state
|
||||
@@ -1478,4 +1478,4 @@ class ContentLibraryExportTest(ContentLibrariesRestApiTest):
|
||||
status = api.get_backup_task_status(self.user.id, task_id=task_id)
|
||||
assert status is not None
|
||||
assert status['state'] == UserTaskStatus.FAILED
|
||||
assert status['url'] is None
|
||||
assert status['file'] is None
|
||||
|
||||
Reference in New Issue
Block a user