Skip to content

Commit

Permalink
feat: add endpoint to get draft version of a library component asset (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
Ian2012 authored Oct 22, 2024
1 parent fcf78db commit e67acd4
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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:
# <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).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -144,28 +145,28 @@ 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

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

Expand All @@ -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
16 changes: 12 additions & 4 deletions openedx/core/djangoapps/content_libraries/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,17 @@
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',
),
])
),
]
15 changes: 15 additions & 0 deletions openedx/core/djangoapps/content_libraries/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit e67acd4

Please sign in to comment.