feat: endpoint to publish single library v2 component (#35677)
This commit is contained in:
committed by
GitHub
parent
869b621b86
commit
a49110b5e1
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user