feat: endpoint to publish single library v2 component (#35677)

This commit is contained in:
Daniel Valenzuela
2024-10-21 16:20:14 -03:00
committed by GitHub
parent 869b621b86
commit a49110b5e1
10 changed files with 80 additions and 8 deletions

View File

@@ -1219,6 +1219,31 @@ def publish_changes(library_key, user_id=None):
)
def publish_component_changes(usage_key: LibraryUsageLocatorV2, user):
"""
Publish all pending changes in a single component.
"""
content_library = require_permission_for_library_key(
usage_key.lib_key,
user,
permissions.CAN_EDIT_THIS_CONTENT_LIBRARY
)
learning_package = content_library.learning_package
assert learning_package
component = get_component_from_usage_key(usage_key)
drafts_to_publish = authoring_api.get_all_drafts(learning_package.id).filter(
entity__key=component.key
)
authoring_api.publish_from_drafts(learning_package.id, draft_qset=drafts_to_publish, published_by=user.id)
LIBRARY_BLOCK_UPDATED.send_event(
library_block=LibraryBlockData(
library_key=usage_key.lib_key,
usage_key=usage_key,
)
)
def revert_changes(library_key):
"""
Revert all pending changes to the specified library, restoring it to the

View File

@@ -27,6 +27,7 @@ URL_LIB_TEAM_USER = URL_LIB_TEAM + 'user/{username}/' # Add/edit/remove a user'
URL_LIB_TEAM_GROUP = URL_LIB_TEAM + 'group/{group_name}/' # Add/edit/remove a group's permission to use this library
URL_LIB_PASTE_CLIPBOARD = URL_LIB_DETAIL + 'paste_clipboard/' # Paste user clipboard (POST) containing Xblock data
URL_LIB_BLOCK = URL_PREFIX + 'blocks/{block_key}/' # Get data about a block, or delete it
URL_LIB_BLOCK_PUBLISH = URL_LIB_BLOCK + 'publish/' # Publish changes from a specified XBlock
URL_LIB_BLOCK_OLX = URL_LIB_BLOCK + 'olx/' # Get or set the OLX of the specified XBlock
URL_LIB_BLOCK_ASSETS = URL_LIB_BLOCK + 'assets/' # List the static asset files of the specified XBlock
URL_LIB_BLOCK_ASSET_FILE = URL_LIB_BLOCK + 'assets/{file_name}' # Get, delete, or upload a specific static asset file
@@ -286,6 +287,10 @@ class ContentLibrariesRestApiTest(APITransactionTestCase):
url = URL_LIB_BLOCK_ASSET_FILE.format(block_key=block_key, file_name=file_name)
return self._api('delete', url, None, expect_response)
def _publish_library_block(self, block_key, expect_response=200):
""" Publish changes from a specified XBlock """
return self._api('post', URL_LIB_BLOCK_PUBLISH.format(block_key=block_key), None, expect_response)
def _paste_clipboard_content_in_library(self, lib_key, block_id, expect_response=200):
""" Paste's the users clipboard content into Library """
url = URL_LIB_PASTE_CLIPBOARD.format(lib_key=lib_key)

View File

@@ -257,7 +257,7 @@ class ContentLibrariesTestCase(ContentLibrariesRestApiTest, OpenEdxEventsTestMix
# General Content Library XBlock tests:
def test_library_blocks(self):
def test_library_blocks(self): # pylint: disable=too-many-statements
"""
Test the happy path of creating and working with XBlocks in a content
library.
@@ -359,6 +359,21 @@ class ContentLibrariesTestCase(ContentLibrariesRestApiTest, OpenEdxEventsTestMix
assert self._get_library(lib_id)['has_unpublished_deletes'] is False
assert self._get_library_block_olx(block_id) == orig_olx
# Now edit and publish the single block instead of the whole library:
new_olx = "<problem><p>Edited OLX</p></problem>"
self._set_library_block_olx(block_id, new_olx)
assert self._get_library_block_olx(block_id) == new_olx
unpublished_block_data = self._get_library_block(block_id)
assert unpublished_block_data['has_unpublished_changes'] is True
block_update_date = datetime(2024, 8, 8, 8, 8, 9, tzinfo=timezone.utc)
with freeze_time(block_update_date):
self._publish_library_block(block_id)
# Confirm the block is now published:
published_block_data = self._get_library_block(block_id)
assert published_block_data['last_published'] == block_update_date.isoformat().replace('+00:00', 'Z')
assert published_block_data['published_by'] == "Bob"
assert published_block_data['has_unpublished_changes'] is False
# fin
def test_library_blocks_studio_view(self):
@@ -675,12 +690,13 @@ class ContentLibrariesTestCase(ContentLibrariesRestApiTest, OpenEdxEventsTestMix
# self._get_library_block_assets(block3_key)
# self._get_library_block_asset(block3_key, file_name="whatever.png")
# Users without authoring permission cannot edit nor delete XBlocks:
# Users without authoring permission cannot edit nor publish nor delete XBlocks:
for user in [reader, random_user]:
with self.as_user(user):
self._set_library_block_olx(block3_key, "<problem/>", expect_response=403)
self._set_library_block_fields(block3_key, {"data": "<problem />", "metadata": {}}, expect_response=403)
self._set_library_block_asset(block3_key, "static/test.txt", b"data", expect_response=403)
self._publish_library_block(block3_key, expect_response=403)
self._delete_library_block(block3_key, expect_response=403)
self._commit_library_changes(lib_id, expect_response=403)
self._revert_library_changes(lib_id, expect_response=403)
@@ -694,9 +710,20 @@ class ContentLibrariesTestCase(ContentLibrariesRestApiTest, OpenEdxEventsTestMix
self._set_library_block_asset(block3_key, "static/test.txt", b"data")
self._get_library_block_asset(block3_key, file_name="static/test.txt")
self._delete_library_block(block3_key)
self._publish_library_block(block3_key)
self._commit_library_changes(lib_id)
self._revert_library_changes(lib_id) # This is a no-op after the commit, but should still have 200 response
# Users without authoring permission cannot commit Xblock changes:
# First we need to add some unpublished changes
with self.as_user(admin):
block4_data = self._add_block_to_library(lib_id, "problem", "problem4")
block5_data = self._add_block_to_library(lib_id, "problem", "problem5")
block4_key = block4_data["id"]
block5_key = block5_data["id"]
self._set_library_block_olx(block4_key, "<problem/>")
self._set_library_block_olx(block5_key, "<problem/>")
def test_no_lockout(self):
"""
Test that administrators cannot be removed if they are the only administrator granted access.

View File

@@ -66,7 +66,8 @@ urlpatterns = [
# CRUD for static asset files associated with a block in the library:
path('assets/', views.LibraryBlockAssetListView.as_view()),
path('assets/<path:file_path>', views.LibraryBlockAssetView.as_view()),
# Future: publish/discard changes for just this one block
path('publish/', views.LibraryBlockPublishView.as_view()),
# Future: discard changes for just this one block
# Future: set a block's tags (tags are stored in a Tag bundle and linked in)
])),
re_path(r'^lti/1.3/', include([

View File

@@ -822,6 +822,20 @@ class LibraryBlockAssetView(APIView):
return Response(status=status.HTTP_204_NO_CONTENT)
@method_decorator(non_atomic_requests, name="dispatch")
@view_auth_classes()
class LibraryBlockPublishView(APIView):
"""
Commit/publish all of the draft changes made to the component.
"""
@convert_exceptions
def post(self, request, usage_key_str):
key = LibraryUsageLocatorV2.from_string(usage_key_str)
api.publish_component_changes(key, request.user)
return Response({})
@method_decorator(non_atomic_requests, name="dispatch")
@view_auth_classes()
class LibraryImportTaskViewSet(GenericViewSet):

View File

@@ -146,7 +146,7 @@ optimizely-sdk<5.0
# Date: 2023-09-18
# pinning this version to avoid updates while the library is being developed
# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35269
openedx-learning==0.16.0
openedx-learning==0.16.1
# Date: 2023-11-29
# Open AI version 1.0.0 dropped support for openai.ChatCompletion which is currently in use in enterprise.

View File

@@ -823,7 +823,7 @@ openedx-filters==1.11.0
# -r requirements/edx/kernel.in
# lti-consumer-xblock
# ora2
openedx-learning==0.16.0
openedx-learning==0.16.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/kernel.in

View File

@@ -1375,7 +1375,7 @@ openedx-filters==1.11.0
# -r requirements/edx/testing.txt
# lti-consumer-xblock
# ora2
openedx-learning==0.16.0
openedx-learning==0.16.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/doc.txt

View File

@@ -984,7 +984,7 @@ openedx-filters==1.11.0
# -r requirements/edx/base.txt
# lti-consumer-xblock
# ora2
openedx-learning==0.16.0
openedx-learning==0.16.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt

View File

@@ -1035,7 +1035,7 @@ openedx-filters==1.11.0
# -r requirements/edx/base.txt
# lti-consumer-xblock
# ora2
openedx-learning==0.16.0
openedx-learning==0.16.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt