Skip to content

Commit

Permalink
feat(content-libraries): single component publish api
Browse files Browse the repository at this point in the history
  • Loading branch information
DanielVZ96 committed Oct 21, 2024
1 parent 43ab1ec commit 60040fb
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 2 deletions.
22 changes: 22 additions & 0 deletions openedx/core/djangoapps/content_libraries/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1218,6 +1218,28 @@ 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

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):
"""
Expand Down
5 changes: 5 additions & 0 deletions openedx/core/djangoapps/content_libraries/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
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
Expand Down Expand Up @@ -286,6 +287,10 @@ def _delete_library_block_asset(self, block_key, file_name, expect_response=204)
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,21 @@ def test_library_blocks(self):
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):
Expand Down Expand Up @@ -675,12 +690,13 @@ def test_library_permissions(self): # pylint: disable=too-many-statements
# 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)
Expand All @@ -694,9 +710,20 @@ def test_library_permissions(self): # pylint: disable=too-many-statements
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.
Expand Down
3 changes: 2 additions & 1 deletion openedx/core/djangoapps/content_libraries/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@
# 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([
Expand Down
14 changes: 14 additions & 0 deletions openedx/core/djangoapps/content_libraries/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,20 @@ def delete(self, request, usage_key_str, file_path):
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):
Expand Down

0 comments on commit 60040fb

Please sign in to comment.