From 2788f2bc3b5a58762dffab2626935bde780e4031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Ch=C3=A1vez?= Date: Thu, 1 Aug 2024 13:50:13 -0500 Subject: [PATCH] feat: New fields in library v2 details API view (#35091) --- .../core/djangoapps/content_libraries/api.py | 32 ++++++++++++++----- .../content_libraries/serializers.py | 6 +++- .../djangoapps/content_libraries/views.py | 4 +-- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/openedx/core/djangoapps/content_libraries/api.py b/openedx/core/djangoapps/content_libraries/api.py index 727f6c7648a0..d2c6eeeeabd9 100644 --- a/openedx/core/djangoapps/content_libraries/api.py +++ b/openedx/core/djangoapps/content_libraries/api.py @@ -156,6 +156,9 @@ class ContentLibraryMetadata: version = attr.ib(0) type = attr.ib(default=COMPLEX) last_published = attr.ib(default=None, type=datetime) + last_draft_created = attr.ib(default=None, type=datetime) + last_draft_created_by = attr.ib(default=None, type=datetime) + published_by = attr.ib("") has_unpublished_changes = attr.ib(False) # has_unpublished_deletes will be true when the draft version of the library's bundle # contains deletes of any XBlocks that were in the most recently published version @@ -168,6 +171,8 @@ class ContentLibraryMetadata: # Studio, use it in their courses, and copy content out of this library. allow_public_read = attr.ib(False) license = attr.ib("") + created = attr.ib(default=None, type=datetime) + updated = attr.ib(default=None, type=datetime) class AccessLevel: @@ -350,8 +355,11 @@ def get_library(library_key): learning_package = ref.learning_package num_blocks = authoring_api.get_all_drafts(learning_package.id).count() last_publish_log = authoring_api.get_last_publish(learning_package.id) - has_unpublished_changes = authoring_api.get_entities_with_unpublished_changes(learning_package.id) \ - .exists() + last_draft_log = authoring_api.get_entities_with_unpublished_changes(learning_package.id) \ + .order_by('-created').first() + last_draft_created = last_draft_log.created if last_draft_log else None + last_draft_created_by = last_draft_log.created_by.username if last_draft_log and last_draft_log.created_by else None + has_unpublished_changes = last_draft_log is not None # TODO: I'm doing this one to match already-existing behavior, but this is # something that we should remove. It exists to accomodate some complexities @@ -377,6 +385,9 @@ def get_library(library_key): # libraries. The top level version stays for now because LibraryContentBlock # uses it, but that should hopefully change before the Redwood release. version = 0 if last_publish_log is None else last_publish_log.pk + published_by = None + if last_publish_log and last_publish_log.published_by: + published_by = last_publish_log.published_by.username return ContentLibraryMetadata( key=library_key, @@ -386,12 +397,17 @@ def get_library(library_key): num_blocks=num_blocks, version=version, last_published=None if last_publish_log is None else last_publish_log.published_at, + published_by=published_by, + last_draft_created=last_draft_created, + last_draft_created_by=last_draft_created_by, allow_lti=ref.allow_lti, allow_public_learning=ref.allow_public_learning, allow_public_read=ref.allow_public_read, has_unpublished_changes=has_unpublished_changes, has_unpublished_deletes=has_unpublished_deletes, license=ref.license, + created=learning_package.created, + updated=learning_package.updated, ) @@ -735,7 +751,7 @@ def set_library_block_olx(usage_key, new_olx_str): ) -def create_library_block(library_key, block_type, definition_id): +def create_library_block(library_key, block_type, definition_id, user_id=None): """ Create a new XBlock in this library of the specified type (e.g. "html"). """ @@ -777,7 +793,7 @@ def create_library_block(library_key, block_type, definition_id): if _component_exists(usage_key): raise LibraryBlockAlreadyExists(f"An XBlock with ID '{usage_key}' already exists") - _create_component_for_block(ref, usage_key) + _create_component_for_block(ref, usage_key, user_id=user_id) # Now return the metadata about the new block: LIBRARY_BLOCK_CREATED.send_event( @@ -816,7 +832,7 @@ def get_or_create_olx_media_type(block_type: str) -> MediaType: ) -def _create_component_for_block(content_lib, usage_key): +def _create_component_for_block(content_lib, usage_key, user_id=None): """ Create a Component for an XBlock type, and initialize it. @@ -847,7 +863,7 @@ def _create_component_for_block(content_lib, usage_key): local_key=usage_key.block_id, title=display_name, created=now, - created_by=None, + created_by=user_id, ) content = authoring_api.get_or_create_text_content( learning_package.id, @@ -951,13 +967,13 @@ def get_allowed_block_types(library_key): # pylint: disable=unused-argument return info -def publish_changes(library_key): +def publish_changes(library_key, user_id=None): """ Publish all pending changes to the specified library. """ learning_package = ContentLibrary.objects.get_by_key(library_key).learning_package - authoring_api.publish_all_drafts(learning_package.id) + authoring_api.publish_all_drafts(learning_package.id, published_by=user_id) CONTENT_LIBRARY_UPDATED.send_event( content_library=ContentLibraryData( diff --git a/openedx/core/djangoapps/content_libraries/serializers.py b/openedx/core/djangoapps/content_libraries/serializers.py index 89c6b611a822..e714718a77b5 100644 --- a/openedx/core/djangoapps/content_libraries/serializers.py +++ b/openedx/core/djangoapps/content_libraries/serializers.py @@ -36,12 +36,14 @@ class ContentLibraryMetadataSerializer(serializers.Serializer): org = serializers.SlugField(source="key.org") slug = serializers.CharField(source="key.slug", validators=(validate_unicode_slug, )) bundle_uuid = serializers.UUIDField(format='hex_verbose', read_only=True) - #collection_uuid = serializers.UUIDField(format='hex_verbose', write_only=True) title = serializers.CharField() description = serializers.CharField(allow_blank=True) num_blocks = serializers.IntegerField(read_only=True) version = serializers.IntegerField(read_only=True) last_published = serializers.DateTimeField(format=DATETIME_FORMAT, read_only=True) + published_by = serializers.CharField(read_only=True) + last_draft_created = serializers.DateTimeField(format=DATETIME_FORMAT, read_only=True) + last_draft_created_by = serializers.CharField(read_only=True) allow_lti = serializers.BooleanField(default=False, read_only=True) allow_public_learning = serializers.BooleanField(default=False) allow_public_read = serializers.BooleanField(default=False) @@ -49,6 +51,8 @@ class ContentLibraryMetadataSerializer(serializers.Serializer): has_unpublished_deletes = serializers.BooleanField(read_only=True) license = serializers.ChoiceField(choices=LICENSE_OPTIONS, default=ALL_RIGHTS_RESERVED) can_edit_library = serializers.SerializerMethodField() + created = serializers.DateTimeField(format=DATETIME_FORMAT, read_only=True) + updated = serializers.DateTimeField(format=DATETIME_FORMAT, read_only=True) def get_can_edit_library(self, obj): """ diff --git a/openedx/core/djangoapps/content_libraries/views.py b/openedx/core/djangoapps/content_libraries/views.py index 810c99f223db..502150b47da6 100644 --- a/openedx/core/djangoapps/content_libraries/views.py +++ b/openedx/core/djangoapps/content_libraries/views.py @@ -487,7 +487,7 @@ def post(self, request, lib_key_str): """ key = LibraryLocatorV2.from_string(lib_key_str) api.require_permission_for_library_key(key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY) - api.publish_changes(key) + api.publish_changes(key, request.user.id) return Response({}) @convert_exceptions @@ -556,7 +556,7 @@ def post(self, request, lib_key_str): # Create a new regular top-level block: try: - result = api.create_library_block(library_key, **serializer.validated_data) + result = api.create_library_block(library_key, user_id=request.user.id, **serializer.validated_data) except api.IncompatibleTypesError as err: raise ValidationError( # lint-amnesty, pylint: disable=raise-missing-from detail={'block_type': str(err)},