From e67acd459f16d597120c88c3907d7cc7a46e5de4 Mon Sep 17 00:00:00 2001 From: Cristhian Garcia Date: Tue, 22 Oct 2024 11:08:26 -0500 Subject: [PATCH] feat: add endpoint to get draft version of a library component asset (#35681) Adds a new Studio-only libraries static asset endpoint at /library_assets/blocks/{usage_key}. This endpoint will serve assets only from the Draft branch, and is only available to people who have read permission to the containing library. This also moves the existing library asset endpoint that did lookups by Component Version to /library_assets/component_versions/{uuid} This change was motivated by the desire to make it easier to make the editor preview work for images by having a single URL that will consistently point to the latest version of the asset, rather than having a new URL after every save (which the Component Version lookup --- .../tests/test_embed_block.py | 4 +- .../tests/test_static_assets.py | 41 +++++++++++++++---- .../core/djangoapps/content_libraries/urls.py | 16 ++++++-- .../djangoapps/content_libraries/views.py | 15 +++++++ .../xblock/runtime/learning_core_runtime.py | 2 +- 5 files changed, 64 insertions(+), 14 deletions(-) diff --git a/openedx/core/djangoapps/content_libraries/tests/test_embed_block.py b/openedx/core/djangoapps/content_libraries/tests/test_embed_block.py index a554e6157e8e..e9909b7d6063 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_embed_block.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_embed_block.py @@ -219,8 +219,8 @@ def test_embed_view_versions_static_assets(self): # show up. html = self._embed_block(block_id, version='published') # This is the pattern we're looking for: - # - assert re.search(r'/library_assets/[0-9a-f-]*/static/deer.jpg', html) + # + assert re.search(r'/library_assets/component_versions/[0-9a-f-]*/static/deer.jpg', html) # Now grab the draft version (4), which is going to once again not have # the asset (because we deleted it). diff --git a/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py b/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py index a5f69f94b174..b903a0ca977a 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py @@ -123,19 +123,20 @@ def setUp(self): block = self._add_block_to_library(library["id"], "html", "html1") self._set_library_block_asset(block["id"], "static/test.svg", SVG_DATA) usage_key = UsageKey.from_string(block["id"]) + self.usage_key = usage_key self.component = get_component_from_usage_key(usage_key) self.draft_component_version = self.component.versioning.draft def test_good_responses(self): get_response = self.client.get( - f"/library_assets/{self.draft_component_version.uuid}/static/test.svg" + f"/library_assets/component_versions/{self.draft_component_version.uuid}/static/test.svg" ) assert get_response.status_code == 200 content = b''.join(chunk for chunk in get_response.streaming_content) assert content == SVG_DATA good_head_response = self.client.head( - f"/library_assets/{self.draft_component_version.uuid}/static/test.svg" + f"/library_assets/component_versions/{self.draft_component_version.uuid}/static/test.svg" ) assert good_head_response.headers == get_response.headers @@ -144,20 +145,20 @@ def test_missing(self): # Non-existent version... wrong_version_uuid = UUID('11111111-1111-1111-1111-111111111111') response = self.client.get( - f"/library_assets/{wrong_version_uuid}/static/test.svg" + f"/library_assets/component_versions/{wrong_version_uuid}/static/test.svg" ) assert response.status_code == 404 # Non-existent file... response = self.client.get( - f"/library_assets/{self.draft_component_version.uuid}/static/missing.svg" + f"/library_assets/component_versions/{self.draft_component_version.uuid}/static/missing.svg" ) assert response.status_code == 404 # File-like ComponenVersionContent entry that isn't an actually # downloadable file... response = self.client.get( - f"/library_assets/{self.draft_component_version.uuid}/block.xml" + f"/library_assets/component_versions/{self.draft_component_version.uuid}/block.xml" ) assert response.status_code == 404 @@ -165,7 +166,7 @@ def test_anonymous_user(self): """Anonymous users shouldn't get access to library assets.""" self.client.logout() response = self.client.get( - f"/library_assets/{self.draft_component_version.uuid}/static/test.svg" + f"/library_assets/component_versions/{self.draft_component_version.uuid}/static/test.svg" ) assert response.status_code == 403 @@ -181,6 +182,32 @@ def test_unauthorized_user(self): ) self.client.login(username="student", password="student-pass") get_response = self.client.get( - f"/library_assets/{self.draft_component_version.uuid}/static/test.svg" + f"/library_assets/component_versions/{self.draft_component_version.uuid}/static/test.svg" ) assert get_response.status_code == 403 + + def test_draft_version(self): + """Get draft version of asset""" + get_response = self.client.get( + f"/library_assets/blocks/{self.usage_key}/static/test.svg" + ) + assert get_response.status_code == 200 + content = b''.join(chunk for chunk in get_response.streaming_content) + assert content == SVG_DATA + + good_head_response = self.client.head( + f"/library_assets/blocks/{self.usage_key}/static/test.svg" + ) + assert good_head_response.headers == get_response.headers + + def test_draft_version_404(self): + """Get draft version of asset""" + get_response = self.client.get( + f"/library_assets/blocks/{self.usage_key}@/static/test.svg" + ) + assert get_response.status_code == 404 + + get_response = self.client.get( + f"/library_assets/blocks/{self.usage_key}/static/test2.svg" + ) + assert get_response.status_code == 404 diff --git a/openedx/core/djangoapps/content_libraries/urls.py b/openedx/core/djangoapps/content_libraries/urls.py index 7806c75500a6..5bf36162d56d 100644 --- a/openedx/core/djangoapps/content_libraries/urls.py +++ b/openedx/core/djangoapps/content_libraries/urls.py @@ -76,9 +76,17 @@ path('pub/jwks/', views.LtiToolJwksView.as_view(), name='lti-pub-jwks'), ])), ])), - path( - 'library_assets//', - views.component_version_asset, - name='library-assets', + path('library_assets/', include([ + path( + 'component_versions//', + views.component_version_asset, + name='library-assets', + ), + path( + 'blocks//', + views.component_draft_asset, + name='library-draft-assets', + ), + ]) ), ] diff --git a/openedx/core/djangoapps/content_libraries/views.py b/openedx/core/djangoapps/content_libraries/views.py index 585b08535d2c..50b532f25bc2 100644 --- a/openedx/core/djangoapps/content_libraries/views.py +++ b/openedx/core/djangoapps/content_libraries/views.py @@ -1237,3 +1237,18 @@ def component_version_asset(request, component_version_uuid, asset_path): content.read_file().chunks(), headers=redirect_response.headers, ) + + +@require_safe +def component_draft_asset(request, usage_key, asset_path): + """ + Serves the draft version of static assets associated with a Library Component. + + See `component_version_asset` for more details + """ + try: + component_version_uuid = api.get_component_from_usage_key(usage_key).versioning.draft.uuid + except ObjectDoesNotExist as exc: + raise Http404() from exc + + return component_version_asset(request, component_version_uuid, asset_path) diff --git a/openedx/core/djangoapps/xblock/runtime/learning_core_runtime.py b/openedx/core/djangoapps/xblock/runtime/learning_core_runtime.py index 0942a5a8b3c7..1d1df738ab37 100644 --- a/openedx/core/djangoapps/xblock/runtime/learning_core_runtime.py +++ b/openedx/core/djangoapps/xblock/runtime/learning_core_runtime.py @@ -377,7 +377,7 @@ def _lookup_asset_url(self, block: XBlock, asset_path: str) -> str | None: then this method will be called with asset_path="test.png" and should return a URL like: - http://studio.local.openedx.io:8001/library_assets/cd31871e-a342-4c3f-ba2f-a661bf630996/static/test.png + http://studio.local.openedx.io:8001/library_assets/component_versions/cd31871e-a342-4c3f-ba2f-a661bf630996/static/test.png If the asset file is not recognized, return None