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
This commit is contained in:
Cristhian Garcia
2024-10-22 11:08:26 -05:00
committed by GitHub
parent fcf78db09e
commit e67acd459f
5 changed files with 64 additions and 14 deletions

View File

@@ -219,8 +219,8 @@ class LibrariesEmbedViewTestCase(ContentLibrariesRestApiTest, OpenEdxEventsTestM
# show up.
html = self._embed_block(block_id, version='published')
# This is the pattern we're looking for:
# <img src="https://localhost:18010/library_assets/b5864c63-e1da-4d48-8c8a-cc718e2f9ad3/static/deer.jpg"/>
assert re.search(r'/library_assets/[0-9a-f-]*/static/deer.jpg', html)
# <img src="https://{host}/library_assets/component_versions/.../static/deer.jpg"/>
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).

View File

@@ -123,19 +123,20 @@ class ContentLibrariesComponentVersionAssetTest(ContentLibrariesRestApiTest):
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 @@ class ContentLibrariesComponentVersionAssetTest(ContentLibrariesRestApiTest):
# 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 @@ class ContentLibrariesComponentVersionAssetTest(ContentLibrariesRestApiTest):
"""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 @@ class ContentLibrariesComponentVersionAssetTest(ContentLibrariesRestApiTest):
)
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

View File

@@ -76,9 +76,17 @@ urlpatterns = [
path('pub/jwks/', views.LtiToolJwksView.as_view(), name='lti-pub-jwks'),
])),
])),
path(
'library_assets/<uuid:component_version_uuid>/<path:asset_path>',
views.component_version_asset,
name='library-assets',
path('library_assets/', include([
path(
'component_versions/<uuid:component_version_uuid>/<path:asset_path>',
views.component_version_asset,
name='library-assets',
),
path(
'blocks/<usage_v2:usage_key>/<path:asset_path>',
views.component_draft_asset,
name='library-draft-assets',
),
])
),
]

View File

@@ -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)

View File

@@ -377,7 +377,7 @@ class LearningCoreXBlockRuntime(XBlockRuntime):
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