From 9c36732e2256df8e2fefe86c760ba1803e7d6d02 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Fri, 11 Oct 2024 15:50:27 -0400 Subject: [PATCH] temp: add implementations for delete_library_block_static_asset_file, add_library_block_static_asset_file --- cms/envs/test.py | 10 +++ .../core/djangoapps/content_libraries/api.py | 76 +++++++++++++++++-- .../tests/test_static_assets.py | 6 -- .../djangoapps/content_libraries/views.py | 4 +- 4 files changed, 83 insertions(+), 13 deletions(-) diff --git a/cms/envs/test.py b/cms/envs/test.py index 38b7c7817149..49db50608858 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -333,3 +333,13 @@ "SECRET": "***", "URL": "***", } + +############## openedx-learning (Learning Core) config ############## +OPENEDX_LEARNING = { + 'MEDIA': { + 'BACKEND': 'django.core.files.storage.InMemoryStorage', + 'OPTIONS': { + 'location': MEDIA_ROOT + "_private" + } + } +} diff --git a/openedx/core/djangoapps/content_libraries/api.py b/openedx/core/djangoapps/content_libraries/api.py index 60bcfdf2924f..5562a8c97806 100644 --- a/openedx/core/djangoapps/content_libraries/api.py +++ b/openedx/core/djangoapps/content_libraries/api.py @@ -56,6 +56,7 @@ import base64 import hashlib import logging +import mimetypes import attr import requests @@ -1042,12 +1043,12 @@ def get_library_block_static_asset_files(usage_key) -> list[LibraryXBlockStaticF ] -def add_library_block_static_asset_file(usage_key, file_name, file_content) -> LibraryXBlockStaticFile: +def add_library_block_static_asset_file(usage_key, file_path, file_content, user=None) -> LibraryXBlockStaticFile: """ Upload a static asset file into the library, to be associated with the specified XBlock. Will silently overwrite an existing file of the same name. - file_name should be a name like "doc.pdf". It may optionally contain slashes + file_path should be a name like "doc.pdf". It may optionally contain slashes like 'en/doc.pdf' file_content should be a binary string. @@ -1059,10 +1060,58 @@ def add_library_block_static_asset_file(usage_key, file_name, file_content) -> L video_block = UsageKey.from_string("lb:VideoTeam:python-intro:video:1") add_library_block_static_asset_file(video_block, "subtitles-en.srt", subtitles.encode('utf-8')) """ - raise NotImplementedError("Static assets not yet implemented for Learning Core") + # File path validations copied over from v1 library logic... + if file_path != file_path.strip().strip('/'): + raise InvalidNameError("file_path cannot start/end with / or whitespace.") + if '//' in file_path or '..' in file_path: + raise InvalidNameError("Invalid sequence (// or ..) in file_path.") + component = get_component_from_usage_key(usage_key) + + media_type_str, _encoding = mimetypes.guess_type(file_path) + media_type = authoring_api.get_or_create_media_type(media_type_str) + now = datetime.now(tz=timezone.utc) -def delete_library_block_static_asset_file(usage_key, file_name): + with transaction.atomic(): + content = authoring_api.get_or_create_file_content( + component.publishable_entity.learning_package.id, + media_type.id, + data=file_content, + created=now, + ) + component_version = authoring_api.create_next_component_version( + component.pk, + content_to_replace={file_path: content.id}, + created=now, + created_by=user.id if user else None, + ) + transaction.on_commit( + lambda: LIBRARY_BLOCK_UPDATED.send_event( + library_block=LibraryBlockData( + library_key=usage_key.context_key, + usage_key=usage_key, + ) + ) + ) + + # Now figure out the URL for the newly created asset... + site_root_url = get_xblock_app_config().get_site_root_url() + local_path = reverse( + 'content_libraries:library-assets', + kwargs={ + 'component_version_uuid': component_version.uuid, + 'asset_path': file_path, + } + ) + + return LibraryXBlockStaticFile( + path=file_path, + url=site_root_url + local_path, + size=content.size, + ) + + +def delete_library_block_static_asset_file(usage_key, file_path, user=None): """ Delete a static asset file from the library. @@ -1072,7 +1121,24 @@ def delete_library_block_static_asset_file(usage_key, file_name): video_block = UsageKey.from_string("lb:VideoTeam:python-intro:video:1") delete_library_block_static_asset_file(video_block, "subtitles-en.srt") """ - raise NotImplementedError("Static assets not yet implemented for Learning Core") + component = get_component_from_usage_key(usage_key) + now = datetime.now(tz=timezone.utc) + + with transaction.atomic(): + component_version = authoring_api.create_next_component_version( + component.pk, + content_to_replace={file_path: None}, + created=now, + created_by=user.id if user else None, + ) + transaction.on_commit( + lambda: LIBRARY_BLOCK_UPDATED.send_event( + library_block=LibraryBlockData( + library_key=usage_key.context_key, + usage_key=usage_key, + ) + ) + ) def get_allowed_block_types(library_key): # pylint: disable=unused-argument 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 92ff4c1767d0..fe57f785836c 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py @@ -23,15 +23,9 @@ """ -@skip("Assets are being reimplemented in Learning Core. Disable until that's ready.") class ContentLibrariesStaticAssetsTest(ContentLibrariesRestApiTest): """ Tests for static asset files in Learning-Core-based Content Libraries - - WARNING: every test should have a unique library slug, because even though - the django/mysql database gets reset for each test case, the lookup between - library slug and bundle UUID does not because it's assumed to be immutable - and cached forever. """ def test_asset_filenames(self): diff --git a/openedx/core/djangoapps/content_libraries/views.py b/openedx/core/djangoapps/content_libraries/views.py index 78ea7e9eb3a6..e1a49146d096 100644 --- a/openedx/core/djangoapps/content_libraries/views.py +++ b/openedx/core/djangoapps/content_libraries/views.py @@ -759,7 +759,7 @@ def put(self, request, usage_key_str, file_path): raise ValidationError("File too big") file_content = file_wrapper.read() try: - result = api.add_library_block_static_asset_file(usage_key, file_path, file_content) + result = api.add_library_block_static_asset_file(usage_key, file_path, file_content, request.user) except ValueError: raise ValidationError("Invalid file path") # lint-amnesty, pylint: disable=raise-missing-from return Response(LibraryXBlockStaticFileSerializer(result).data) @@ -774,7 +774,7 @@ def delete(self, request, usage_key_str, file_path): usage_key.lib_key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY, ) try: - api.delete_library_block_static_asset_file(usage_key, file_path) + api.delete_library_block_static_asset_file(usage_key, file_path, request.user) except ValueError: raise ValidationError("Invalid file path") # lint-amnesty, pylint: disable=raise-missing-from return Response(status=status.HTTP_204_NO_CONTENT)