feat!: add new content authoring event signals
This commit is contained in:
committed by
Navin Karkera
parent
02127673f8
commit
c54070989b
@@ -192,18 +192,55 @@ Content Authoring Events
|
||||
- *Type*
|
||||
- *Date added*
|
||||
|
||||
* - `COURSE_CATALOG_INFO_CHANGED <https://github.com/openedx/openedx-events/blob/main/openedx_events/content_authoring/signals.py#L23>`_
|
||||
* - `COURSE_CATALOG_INFO_CHANGED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L25>`_
|
||||
- org.openedx.content_authoring.course.catalog_info.changed.v1
|
||||
- `2022-08-24 <https://github.com/openedx/edx-platform/blob/a8598fa1fac5e26ac212aa588e8527e727581742/cms/djangoapps/contentstore/signals/handlers.py#L111>`_
|
||||
|
||||
* - `XBLOCK_PUBLISHED <https://github.com/openedx/openedx-events/blob/main/openedx_events/content_authoring/signals.py#L30>`_
|
||||
* - `XBLOCK_PUBLISHED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L63>`_
|
||||
- org.openedx.content_authoring.xblock.published.v1
|
||||
- `2022-12-06 <https://github.com/openedx/edx-platform/blob/master/xmodule/modulestore/mixed.py#L926>`_
|
||||
|
||||
* - `XBLOCK_DELETED <https://github.com/openedx/openedx-events/blob/main/openedx_events/content_authoring/signals.py#L42>`_
|
||||
* - `XBLOCK_DELETED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L75>`_
|
||||
- org.openedx.content_authoring.xblock.deleted.v1
|
||||
- `2022-12-06 <https://github.com/openedx/edx-platform/blob/master/xmodule/modulestore/mixed.py#L804>`_
|
||||
|
||||
* - `XBLOCK_DUPLICATED <https://github.com/openedx/openedx-events/blob/main/openedx_events/content_authoring/signals.py#L54>`_
|
||||
* - `XBLOCK_DUPLICATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L87>`_
|
||||
- org.openedx.content_authoring.xblock.duplicated.v1
|
||||
- `2022-12-06 <https://github.com/openedx/edx-platform/blob/master/cms/djangoapps/contentstore/views/item.py#L965>`_
|
||||
|
||||
* - `XBLOCK_CREATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L36>`_
|
||||
- org.openedx.content_authoring.xblock.created.v1
|
||||
- 2023-07-20
|
||||
|
||||
* - `XBLOCK_UPDATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L47>`_
|
||||
- org.openedx.content_authoring.xblock.updated.v1
|
||||
- 2023-07-20
|
||||
|
||||
* - `COURSE_CREATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L123>`_
|
||||
- org.openedx.content_authoring.course.created.v1
|
||||
- 2023-07-20
|
||||
|
||||
* - `CONTENT_LIBRARY_CREATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L134>`_
|
||||
- org.openedx.content_authoring.content_library.created.v1
|
||||
- 2023-07-20
|
||||
|
||||
* - `CONTENT_LIBRARY_UPDATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L145>`_
|
||||
- org.openedx.content_authoring.content_library.updated.v1
|
||||
- 2023-07-20
|
||||
|
||||
* - `CONTENT_LIBRARY_DELETED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L156>`_
|
||||
- org.openedx.content_authoring.content_library.deleted.v1
|
||||
- 2023-07-20
|
||||
|
||||
* - `LIBRARY_BLOCK_CREATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L167>`_
|
||||
- org.openedx.content_authoring.content_library.created.v1
|
||||
- 2023-07-20
|
||||
|
||||
* - `LIBRARY_BLOCK_UPDATED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L178>`_
|
||||
- org.openedx.content_authoring.content_library.updated.v1
|
||||
- 2023-07-20
|
||||
|
||||
* - `LIBRARY_BLOCK_DELETED <https://github.com/openedx/openedx-events/blob/c0eb4ba1a3d7d066d58e5c87920b8ccb0645f769/openedx_events/content_authoring/signals.py#L189>`_
|
||||
- org.openedx.content_authoring.content_library.deleted.v1
|
||||
- 2023-07-20
|
||||
|
||||
|
||||
@@ -76,6 +76,15 @@ from opaque_keys.edx.locator import (
|
||||
LibraryUsageLocatorV2,
|
||||
LibraryLocator as LibraryLocatorV1
|
||||
)
|
||||
from openedx_events.content_authoring.data import ContentLibraryData, LibraryBlockData
|
||||
from openedx_events.content_authoring.signals import (
|
||||
CONTENT_LIBRARY_CREATED,
|
||||
CONTENT_LIBRARY_DELETED,
|
||||
CONTENT_LIBRARY_UPDATED,
|
||||
LIBRARY_BLOCK_CREATED,
|
||||
LIBRARY_BLOCK_DELETED,
|
||||
LIBRARY_BLOCK_UPDATED,
|
||||
)
|
||||
|
||||
from organizations.models import Organization
|
||||
from xblock.core import XBlock
|
||||
@@ -90,14 +99,7 @@ from openedx.core.djangoapps.content_libraries.models import (
|
||||
ContentLibraryPermission,
|
||||
ContentLibraryBlockImportTask,
|
||||
)
|
||||
from openedx.core.djangoapps.content_libraries.signals import (
|
||||
CONTENT_LIBRARY_CREATED,
|
||||
CONTENT_LIBRARY_UPDATED,
|
||||
CONTENT_LIBRARY_DELETED,
|
||||
LIBRARY_BLOCK_CREATED,
|
||||
LIBRARY_BLOCK_UPDATED,
|
||||
LIBRARY_BLOCK_DELETED,
|
||||
)
|
||||
|
||||
from openedx.core.djangoapps.xblock.api import (
|
||||
get_block_display_name,
|
||||
get_learning_context_impl,
|
||||
@@ -452,7 +454,11 @@ def create_library(
|
||||
)
|
||||
except IntegrityError:
|
||||
raise LibraryAlreadyExists(slug) # lint-amnesty, pylint: disable=raise-missing-from
|
||||
CONTENT_LIBRARY_CREATED.send(sender=None, library_key=ref.library_key)
|
||||
CONTENT_LIBRARY_CREATED.send_event(
|
||||
content_library=ContentLibraryData(
|
||||
library_key=ref.library_key
|
||||
)
|
||||
)
|
||||
return ContentLibraryMetadata(
|
||||
key=ref.library_key,
|
||||
bundle_uuid=bundle.uuid,
|
||||
@@ -602,7 +608,11 @@ def update_library(
|
||||
assert isinstance(description, str)
|
||||
fields["description"] = description
|
||||
update_bundle(ref.bundle_uuid, **fields)
|
||||
CONTENT_LIBRARY_UPDATED.send(sender=None, library_key=ref.library_key)
|
||||
CONTENT_LIBRARY_UPDATED.send_event(
|
||||
content_library=ContentLibraryData(
|
||||
library_key=ref.library_key
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def delete_library(library_key):
|
||||
@@ -617,7 +627,11 @@ def delete_library(library_key):
|
||||
# system, which is a better state than having a reference to a library with
|
||||
# no backing blockstore bundle.
|
||||
ref.delete()
|
||||
CONTENT_LIBRARY_DELETED.send(sender=None, library_key=ref.library_key)
|
||||
CONTENT_LIBRARY_DELETED.send_event(
|
||||
content_library=ContentLibraryData(
|
||||
library_key=ref.library_key
|
||||
)
|
||||
)
|
||||
try:
|
||||
delete_bundle(bundle_uuid)
|
||||
except:
|
||||
@@ -754,7 +768,12 @@ def set_library_block_olx(usage_key, new_olx_str):
|
||||
write_draft_file(draft.uuid, metadata.def_key.olx_path, new_olx_str.encode('utf-8'))
|
||||
# Clear the bundle cache so everyone sees the new block immediately:
|
||||
BundleCache(metadata.def_key.bundle_uuid, draft_name=DRAFT_NAME).clear()
|
||||
LIBRARY_BLOCK_UPDATED.send(sender=None, library_key=usage_key.context_key, usage_key=usage_key)
|
||||
LIBRARY_BLOCK_UPDATED.send_event(
|
||||
library_block=LibraryBlockData(
|
||||
library_key=usage_key.context_key,
|
||||
usage_key=usage_key
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def create_library_block(library_key, block_type, definition_id):
|
||||
@@ -803,7 +822,12 @@ def create_library_block(library_key, block_type, definition_id):
|
||||
# Clear the bundle cache so everyone sees the new block immediately:
|
||||
BundleCache(ref.bundle_uuid, draft_name=DRAFT_NAME).clear()
|
||||
# Now return the metadata about the new block:
|
||||
LIBRARY_BLOCK_CREATED.send(sender=None, library_key=ref.library_key, usage_key=usage_key)
|
||||
LIBRARY_BLOCK_CREATED.send_event(
|
||||
library_block=LibraryBlockData(
|
||||
library_key=ref.library_key,
|
||||
usage_key=usage_key
|
||||
)
|
||||
)
|
||||
return get_library_block(usage_key)
|
||||
|
||||
|
||||
@@ -856,7 +880,12 @@ def delete_library_block(usage_key, remove_from_parent=True):
|
||||
pass
|
||||
# Clear the bundle cache so everyone sees the deleted block immediately:
|
||||
lib_bundle.cache.clear()
|
||||
LIBRARY_BLOCK_DELETED.send(sender=None, library_key=lib_bundle.library_key, usage_key=usage_key)
|
||||
LIBRARY_BLOCK_DELETED.send_event(
|
||||
library_block=LibraryBlockData(
|
||||
library_key=lib_bundle.library_key,
|
||||
usage_key=usage_key
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def create_library_block_child(parent_usage_key, block_type, definition_id):
|
||||
@@ -880,7 +909,12 @@ def create_library_block_child(parent_usage_key, block_type, definition_id):
|
||||
parent_block.runtime.add_child_include(parent_block, include_data)
|
||||
parent_block.save()
|
||||
ref = ContentLibrary.objects.get_by_key(parent_usage_key.context_key)
|
||||
LIBRARY_BLOCK_UPDATED.send(sender=None, library_key=ref.library_key, usage_key=metadata.usage_key)
|
||||
LIBRARY_BLOCK_UPDATED.send_event(
|
||||
library_block=LibraryBlockData(
|
||||
library_key=ref.library_key,
|
||||
usage_key=metadata.usage_key
|
||||
)
|
||||
)
|
||||
return metadata
|
||||
|
||||
|
||||
@@ -930,7 +964,12 @@ def add_library_block_static_asset_file(usage_key, file_name, file_content):
|
||||
file_metadata = blockstore_cache.get_bundle_file_metadata_with_cache(
|
||||
bundle_uuid=def_key.bundle_uuid, path=file_path, draft_name=DRAFT_NAME,
|
||||
)
|
||||
LIBRARY_BLOCK_UPDATED.send(sender=None, library_key=lib_bundle.library_key, usage_key=usage_key)
|
||||
LIBRARY_BLOCK_UPDATED.send_event(
|
||||
library_block=LibraryBlockData(
|
||||
library_key=lib_bundle.library_key,
|
||||
usage_key=usage_key
|
||||
)
|
||||
)
|
||||
return LibraryXBlockStaticFile(path=file_metadata.path, url=file_metadata.url, size=file_metadata.size)
|
||||
|
||||
|
||||
@@ -951,7 +990,12 @@ def delete_library_block_static_asset_file(usage_key, file_name):
|
||||
write_draft_file(draft.uuid, file_path, contents=None)
|
||||
# Clear the bundle cache so everyone sees the new file immediately:
|
||||
lib_bundle.cache.clear()
|
||||
LIBRARY_BLOCK_UPDATED.send(sender=None, library_key=lib_bundle.library_key, usage_key=usage_key)
|
||||
LIBRARY_BLOCK_UPDATED.send_event(
|
||||
library_block=LibraryBlockData(
|
||||
library_key=lib_bundle.library_key,
|
||||
usage_key=usage_key
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def get_allowed_block_types(library_key): # pylint: disable=unused-argument
|
||||
@@ -1044,7 +1088,11 @@ def create_bundle_link(library_key, link_id, target_opaque_key, version=None):
|
||||
set_draft_link(draft.uuid, link_id, target_bundle_uuid, version)
|
||||
# Clear the cache:
|
||||
LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME).cache.clear()
|
||||
CONTENT_LIBRARY_UPDATED.send(sender=None, library_key=library_key)
|
||||
CONTENT_LIBRARY_UPDATED.send_event(
|
||||
content_library=ContentLibraryData(
|
||||
library_key=library_key
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def update_bundle_link(library_key, link_id, version=None, delete=False):
|
||||
@@ -1068,7 +1116,11 @@ def update_bundle_link(library_key, link_id, version=None, delete=False):
|
||||
set_draft_link(draft.uuid, link_id, link.bundle_uuid, version)
|
||||
# Clear the cache:
|
||||
LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME).cache.clear()
|
||||
CONTENT_LIBRARY_UPDATED.send(sender=None, library_key=library_key)
|
||||
CONTENT_LIBRARY_UPDATED.send_event(
|
||||
content_library=ContentLibraryData(
|
||||
library_key=library_key
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def publish_changes(library_key):
|
||||
@@ -1084,7 +1136,12 @@ def publish_changes(library_key):
|
||||
return # If there is no draft, no action is needed.
|
||||
LibraryBundle(library_key, ref.bundle_uuid).cache.clear()
|
||||
LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME).cache.clear()
|
||||
CONTENT_LIBRARY_UPDATED.send(sender=None, library_key=library_key, update_blocks=True)
|
||||
CONTENT_LIBRARY_UPDATED.send_event(
|
||||
content_library=ContentLibraryData(
|
||||
library_key=library_key,
|
||||
update_blocks=True
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def revert_changes(library_key):
|
||||
@@ -1100,7 +1157,12 @@ def revert_changes(library_key):
|
||||
else:
|
||||
return # If there is no draft, no action is needed.
|
||||
LibraryBundle(library_key, ref.bundle_uuid, draft_name=DRAFT_NAME).cache.clear()
|
||||
CONTENT_LIBRARY_UPDATED.send(sender=None, library_key=library_key, update_blocks=True)
|
||||
CONTENT_LIBRARY_UPDATED.send_event(
|
||||
content_library=ContentLibraryData(
|
||||
library_key=library_key,
|
||||
update_blocks=True
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# Import from Courseware
|
||||
|
||||
@@ -9,16 +9,17 @@ from elasticsearch.exceptions import ConnectionError as ElasticConnectionError
|
||||
from search.elastic import _translate_hits, RESERVED_CHARACTERS
|
||||
from search.search_engine_base import SearchEngine
|
||||
from opaque_keys.edx.locator import LibraryUsageLocatorV2
|
||||
from openedx_events.content_authoring.data import ContentLibraryData, LibraryBlockData
|
||||
from openedx_events.content_authoring.signals import (
|
||||
CONTENT_LIBRARY_CREATED,
|
||||
CONTENT_LIBRARY_DELETED,
|
||||
CONTENT_LIBRARY_UPDATED,
|
||||
LIBRARY_BLOCK_CREATED,
|
||||
LIBRARY_BLOCK_DELETED,
|
||||
LIBRARY_BLOCK_UPDATED,
|
||||
)
|
||||
|
||||
from openedx.core.djangoapps.content_libraries.constants import DRAFT_NAME
|
||||
from openedx.core.djangoapps.content_libraries.signals import (
|
||||
CONTENT_LIBRARY_CREATED,
|
||||
CONTENT_LIBRARY_UPDATED,
|
||||
CONTENT_LIBRARY_DELETED,
|
||||
LIBRARY_BLOCK_CREATED,
|
||||
LIBRARY_BLOCK_UPDATED,
|
||||
LIBRARY_BLOCK_DELETED,
|
||||
)
|
||||
from openedx.core.djangoapps.content_libraries.library_bundle import LibraryBundle
|
||||
from openedx.core.djangoapps.content_libraries.models import ContentLibrary
|
||||
from openedx.core.lib.blockstore_api import get_bundle
|
||||
@@ -242,17 +243,21 @@ class LibraryBlockIndexer(SearchIndexerBase):
|
||||
|
||||
@receiver(CONTENT_LIBRARY_CREATED)
|
||||
@receiver(CONTENT_LIBRARY_UPDATED)
|
||||
@receiver(LIBRARY_BLOCK_CREATED)
|
||||
@receiver(LIBRARY_BLOCK_UPDATED)
|
||||
@receiver(LIBRARY_BLOCK_DELETED)
|
||||
def index_library(sender, library_key, **kwargs): # pylint: disable=unused-argument
|
||||
def index_library(**kwargs):
|
||||
"""
|
||||
Index library when created or updated, or when its blocks are modified.
|
||||
"""
|
||||
content_library = kwargs.get('content_library', None)
|
||||
if not content_library or not isinstance(content_library, ContentLibraryData):
|
||||
log.error('Received null or incorrect data for event')
|
||||
return
|
||||
|
||||
library_key = content_library.library_key
|
||||
update_blocks = content_library.update_blocks
|
||||
if ContentLibraryIndexer.indexing_is_enabled():
|
||||
try:
|
||||
ContentLibraryIndexer.index_items([library_key])
|
||||
if kwargs.get('update_blocks', False):
|
||||
if update_blocks:
|
||||
blocks = LibraryBlockIndexer.get_items(filter_terms={
|
||||
'library_key': str(library_key)
|
||||
})
|
||||
@@ -262,12 +267,38 @@ def index_library(sender, library_key, **kwargs): # pylint: disable=unused-argu
|
||||
log.exception(e)
|
||||
|
||||
|
||||
@receiver(LIBRARY_BLOCK_CREATED)
|
||||
@receiver(LIBRARY_BLOCK_DELETED)
|
||||
@receiver(LIBRARY_BLOCK_UPDATED)
|
||||
def index_library_block(**kwargs):
|
||||
"""
|
||||
Index library when its blocks are created, modified, or deleted.
|
||||
"""
|
||||
library_block = kwargs.get('library_block', None)
|
||||
if not library_block or not isinstance(library_block, LibraryBlockData):
|
||||
log.error('Received null or incorrect data for event')
|
||||
return
|
||||
|
||||
library_key = library_block.library_key
|
||||
if ContentLibraryIndexer.indexing_is_enabled():
|
||||
try:
|
||||
ContentLibraryIndexer.index_items([library_key])
|
||||
except ElasticConnectionError as e:
|
||||
log.exception(e)
|
||||
|
||||
|
||||
@receiver(CONTENT_LIBRARY_DELETED)
|
||||
def remove_library_index(sender, library_key, **kwargs): # pylint: disable=unused-argument
|
||||
def remove_library_index(**kwargs):
|
||||
"""
|
||||
Remove from index when library is deleted
|
||||
"""
|
||||
content_library = kwargs.get('content_library', None)
|
||||
if not content_library or not isinstance(content_library, ContentLibraryData):
|
||||
log.error('Received null or incorrect data for event')
|
||||
return
|
||||
|
||||
if ContentLibraryIndexer.indexing_is_enabled():
|
||||
library_key = content_library.library_key
|
||||
try:
|
||||
ContentLibraryIndexer.remove_items([library_key])
|
||||
blocks = LibraryBlockIndexer.get_items(filter_terms={
|
||||
@@ -280,10 +311,16 @@ def remove_library_index(sender, library_key, **kwargs): # pylint: disable=unus
|
||||
|
||||
@receiver(LIBRARY_BLOCK_CREATED)
|
||||
@receiver(LIBRARY_BLOCK_UPDATED)
|
||||
def index_block(sender, usage_key, **kwargs): # pylint: disable=unused-argument
|
||||
def index_block(**kwargs):
|
||||
"""
|
||||
Index block metadata when created
|
||||
Index block metadata when created or updated
|
||||
"""
|
||||
library_block = kwargs.get('library_block', None)
|
||||
if not library_block or not isinstance(library_block, LibraryBlockData):
|
||||
log.error('Received null or incorrect data for event')
|
||||
return
|
||||
|
||||
usage_key = library_block.usage_key
|
||||
if LibraryBlockIndexer.indexing_is_enabled():
|
||||
try:
|
||||
LibraryBlockIndexer.index_items([usage_key])
|
||||
@@ -292,10 +329,16 @@ def index_block(sender, usage_key, **kwargs): # pylint: disable=unused-argument
|
||||
|
||||
|
||||
@receiver(LIBRARY_BLOCK_DELETED)
|
||||
def remove_block_index(sender, usage_key, **kwargs): # pylint: disable=unused-argument
|
||||
def remove_block_index(**kwargs):
|
||||
"""
|
||||
Remove the block from the index when deleted
|
||||
"""
|
||||
library_block = kwargs.get('library_block', None)
|
||||
if not library_block or not isinstance(library_block, LibraryBlockData):
|
||||
log.error('Received null or incorrect data for LIBRARY_BLOCK_DELETED')
|
||||
return
|
||||
|
||||
usage_key = library_block.usage_key
|
||||
if LibraryBlockIndexer.indexing_is_enabled():
|
||||
try:
|
||||
LibraryBlockIndexer.remove_items([usage_key])
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
"""
|
||||
Content libraries related signals.
|
||||
"""
|
||||
|
||||
from django.dispatch import Signal
|
||||
|
||||
# providing_args=['library_key']
|
||||
CONTENT_LIBRARY_CREATED = Signal()
|
||||
# providing_args=['library_key', 'update_blocks']
|
||||
CONTENT_LIBRARY_UPDATED = Signal()
|
||||
# providing_args=['library_key']
|
||||
CONTENT_LIBRARY_DELETED = Signal()
|
||||
|
||||
# Same providing_args=['library_key', 'usage_key'] for next 3 signals.
|
||||
LIBRARY_BLOCK_CREATED = Signal()
|
||||
LIBRARY_BLOCK_DELETED = Signal()
|
||||
LIBRARY_BLOCK_UPDATED = Signal()
|
||||
@@ -331,7 +331,7 @@ class _ContentLibrariesRestApiTestMixin:
|
||||
assert response.status_code == expect_response,\
|
||||
'Unexpected response code {}:\n{}'.format(response.status_code, getattr(response, 'data', '(no data)'))
|
||||
|
||||
def _delete_library_block_asset(self, block_key, file_name, expect_response=200):
|
||||
def _delete_library_block_asset(self, block_key, file_name, expect_response=204):
|
||||
""" Delete a static asset file. """
|
||||
url = URL_LIB_BLOCK_ASSET_FILE.format(block_key=block_key, file_name=file_name)
|
||||
return self._api('delete', url, None, expect_response)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Tests for Blockstore-based Content Libraries
|
||||
"""
|
||||
from uuid import UUID
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import ddt
|
||||
from django.conf import settings
|
||||
@@ -12,6 +12,16 @@ from django.test.utils import override_settings
|
||||
from organizations.models import Organization
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from opaque_keys.edx.locator import LibraryLocatorV2, LibraryUsageLocatorV2
|
||||
from openedx_events.content_authoring.data import ContentLibraryData, LibraryBlockData
|
||||
from openedx_events.content_authoring.signals import (
|
||||
CONTENT_LIBRARY_CREATED,
|
||||
CONTENT_LIBRARY_DELETED,
|
||||
CONTENT_LIBRARY_UPDATED,
|
||||
LIBRARY_BLOCK_CREATED,
|
||||
LIBRARY_BLOCK_DELETED,
|
||||
LIBRARY_BLOCK_UPDATED,
|
||||
)
|
||||
from openedx.core.djangoapps.content_libraries.libraries_index import LibraryBlockIndexer, ContentLibraryIndexer
|
||||
from openedx.core.djangoapps.content_libraries.tests.base import (
|
||||
ContentLibrariesRestApiBlockstoreServiceTest,
|
||||
@@ -485,7 +495,7 @@ class ContentLibrariesTestMixin:
|
||||
assert len(self._get_library_blocks(lib['id'], {'text_search': 'Foo', 'block_type': 'video'})) == 0
|
||||
assert len(self._get_library_blocks(lib['id'], {'text_search': 'Baz', 'block_type': 'video'})) == 1
|
||||
assert len(self._get_library_blocks(lib['id'], {'text_search': 'Baz', 'block_type': ['video', 'html']})) ==\
|
||||
2
|
||||
2
|
||||
assert len(self._get_library_blocks(lib['id'], {'block_type': 'video'})) == 1
|
||||
assert len(self._get_library_blocks(lib['id'], {'block_type': 'problem'})) == 3
|
||||
assert len(self._get_library_blocks(lib['id'], {'block_type': 'squirrel'})) == 0
|
||||
@@ -537,8 +547,8 @@ class ContentLibrariesTestMixin:
|
||||
|
||||
# Check the resulting OLX of the unit:
|
||||
assert self._get_library_block_olx(unit_block['id']) ==\
|
||||
'<unit xblock-family="xblock.v1">\n <xblock-include definition="html/html1"/>\n' \
|
||||
' <xblock-include definition="problem/problem1"/>\n</unit>\n'
|
||||
'<unit xblock-family="xblock.v1">\n <xblock-include definition="html/html1"/>\n' \
|
||||
' <xblock-include definition="problem/problem1"/>\n</unit>\n'
|
||||
|
||||
# The unit can see and render its children:
|
||||
fragment = self._render_block_view(unit_block["id"], "student_view")
|
||||
@@ -932,6 +942,295 @@ class ContentLibrariesTestMixin:
|
||||
else:
|
||||
assert len(types) > 1
|
||||
|
||||
def test_content_library_create_event(self):
|
||||
"""
|
||||
Check that CONTENT_LIBRARY_CREATED event is sent when a content library is created.
|
||||
"""
|
||||
event_receiver = Mock()
|
||||
CONTENT_LIBRARY_CREATED.connect(event_receiver)
|
||||
lib = self._create_library(slug="test_lib_event_create", title="Event Test Library", description="Testing event in library") # lint-amnesty, pylint: disable=line-too-long
|
||||
library_key = LibraryLocatorV2.from_string(lib['id'])
|
||||
|
||||
event_receiver.assert_called_once()
|
||||
self.assertDictContainsSubset(
|
||||
{
|
||||
"signal": CONTENT_LIBRARY_CREATED,
|
||||
"sender": None,
|
||||
"content_library": ContentLibraryData(
|
||||
library_key=library_key,
|
||||
update_blocks=False,
|
||||
),
|
||||
},
|
||||
event_receiver.call_args.kwargs
|
||||
)
|
||||
|
||||
def test_content_library_update_event(self):
|
||||
"""
|
||||
Check that CONTENT_LIBRARY_UPDATED event is sent when a content library is updated.
|
||||
"""
|
||||
event_receiver = Mock()
|
||||
CONTENT_LIBRARY_UPDATED.connect(event_receiver)
|
||||
lib = self._create_library(slug="test_lib_event_update", title="Event Test Library", description="Testing event in library") # lint-amnesty, pylint: disable=line-too-long
|
||||
|
||||
lib2 = self._update_library(lib["id"], title="New Title")
|
||||
library_key = LibraryLocatorV2.from_string(lib2['id'])
|
||||
|
||||
event_receiver.assert_called_once()
|
||||
self.assertDictContainsSubset(
|
||||
{
|
||||
"signal": CONTENT_LIBRARY_UPDATED,
|
||||
"sender": None,
|
||||
"content_library": ContentLibraryData(
|
||||
library_key=library_key,
|
||||
update_blocks=False,
|
||||
),
|
||||
},
|
||||
event_receiver.call_args.kwargs
|
||||
)
|
||||
|
||||
def test_content_library_delete_event(self):
|
||||
"""
|
||||
Check that CONTENT_LIBRARY_DELETED event is sent when a content library is deleted.
|
||||
"""
|
||||
event_receiver = Mock()
|
||||
CONTENT_LIBRARY_DELETED.connect(event_receiver)
|
||||
lib = self._create_library(slug="test_lib_event_delete", title="Event Test Library", description="Testing event in library") # lint-amnesty, pylint: disable=line-too-long
|
||||
library_key = LibraryLocatorV2.from_string(lib['id'])
|
||||
|
||||
self._delete_library(lib["id"])
|
||||
|
||||
event_receiver.assert_called_once()
|
||||
self.assertDictContainsSubset(
|
||||
{
|
||||
"signal": CONTENT_LIBRARY_DELETED,
|
||||
"sender": None,
|
||||
"content_library": ContentLibraryData(
|
||||
library_key=library_key,
|
||||
update_blocks=False,
|
||||
),
|
||||
},
|
||||
event_receiver.call_args.kwargs
|
||||
)
|
||||
|
||||
def test_library_block_create_event(self):
|
||||
"""
|
||||
Check that LIBRARY_BLOCK_CREATED event is sent when a library block is created.
|
||||
"""
|
||||
event_receiver = Mock()
|
||||
LIBRARY_BLOCK_CREATED.connect(event_receiver)
|
||||
lib = self._create_library(slug="test_lib_block_event_create", title="Event Test Library", description="Testing event in library") # lint-amnesty, pylint: disable=line-too-long
|
||||
lib_id = lib["id"]
|
||||
self._add_block_to_library(lib_id, "problem", "problem1")
|
||||
|
||||
library_key = LibraryLocatorV2.from_string(lib_id)
|
||||
usage_key = LibraryUsageLocatorV2(
|
||||
lib_key=library_key,
|
||||
block_type="problem",
|
||||
usage_id="problem1"
|
||||
)
|
||||
|
||||
event_receiver.assert_called_once()
|
||||
self.assertDictContainsSubset(
|
||||
{
|
||||
"signal": LIBRARY_BLOCK_CREATED,
|
||||
"sender": None,
|
||||
"library_block": LibraryBlockData(
|
||||
library_key=library_key,
|
||||
usage_key=usage_key
|
||||
),
|
||||
},
|
||||
event_receiver.call_args.kwargs
|
||||
)
|
||||
|
||||
def test_library_block_olx_update_event(self):
|
||||
"""
|
||||
Check that LIBRARY_BLOCK_CREATED event is sent when the OLX source is updated.
|
||||
"""
|
||||
event_receiver = Mock()
|
||||
LIBRARY_BLOCK_UPDATED.connect(event_receiver)
|
||||
lib = self._create_library(slug="test_lib_block_event_olx_update", title="Event Test Library", description="Testing event in library") # lint-amnesty, pylint: disable=line-too-long
|
||||
lib_id = lib["id"]
|
||||
|
||||
library_key = LibraryLocatorV2.from_string(lib_id)
|
||||
|
||||
block = self._add_block_to_library(lib_id, "problem", "problem1")
|
||||
block_id = block["id"]
|
||||
usage_key = LibraryUsageLocatorV2(
|
||||
lib_key=library_key,
|
||||
block_type="problem",
|
||||
usage_id="problem1"
|
||||
)
|
||||
|
||||
new_olx = """
|
||||
<problem display_name="New Multi Choice Question" max_attempts="5">
|
||||
<multiplechoiceresponse>
|
||||
<p>This is a normal capa problem with unicode 🔥. It has "maximum attempts" set to **5**.</p>
|
||||
<label>Blockstore is designed to store.</label>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">XBlock metadata only</choice>
|
||||
<choice correct="true">XBlock data/metadata and associated static asset files</choice>
|
||||
<choice correct="false">Static asset files for XBlocks and courseware</choice>
|
||||
<choice correct="false">XModule metadata only</choice>
|
||||
</choicegroup>
|
||||
</multiplechoiceresponse>
|
||||
</problem>
|
||||
""".strip()
|
||||
|
||||
self._set_library_block_olx(block_id, new_olx)
|
||||
|
||||
event_receiver.assert_called_once()
|
||||
self.assertDictContainsSubset(
|
||||
{
|
||||
"signal": LIBRARY_BLOCK_UPDATED,
|
||||
"sender": None,
|
||||
"library_block": LibraryBlockData(
|
||||
library_key=library_key,
|
||||
usage_key=usage_key
|
||||
),
|
||||
},
|
||||
event_receiver.call_args.kwargs
|
||||
)
|
||||
|
||||
def test_library_block_child_update_event(self):
|
||||
"""
|
||||
Check that LIBRARY_BLOCK_CREATED event is sent when a child is created.
|
||||
"""
|
||||
event_receiver = Mock()
|
||||
LIBRARY_BLOCK_UPDATED.connect(event_receiver)
|
||||
lib = self._create_library(slug="test_lib_block_event_child_update", title="Event Test Library", description="Testing event in library") # lint-amnesty, pylint: disable=line-too-long
|
||||
lib_id = lib["id"]
|
||||
|
||||
library_key = LibraryLocatorV2.from_string(lib_id)
|
||||
|
||||
parent_block = self._add_block_to_library(lib_id, "unit", "u1")
|
||||
parent_block_id = parent_block["id"]
|
||||
|
||||
self._add_block_to_library(lib["id"], "problem", "problem1", parent_block=parent_block_id)
|
||||
|
||||
usage_key = LibraryUsageLocatorV2(
|
||||
lib_key=library_key,
|
||||
block_type="problem",
|
||||
usage_id="problem1"
|
||||
)
|
||||
|
||||
event_receiver.assert_called_once()
|
||||
self.assertDictContainsSubset(
|
||||
{
|
||||
"signal": LIBRARY_BLOCK_UPDATED,
|
||||
"sender": None,
|
||||
"library_block": LibraryBlockData(
|
||||
library_key=library_key,
|
||||
usage_key=usage_key
|
||||
),
|
||||
},
|
||||
event_receiver.call_args.kwargs
|
||||
)
|
||||
|
||||
def test_library_block_add_asset_update_event(self):
|
||||
"""
|
||||
Check that LIBRARY_BLOCK_CREATED event is sent when a static asset is uploaded associated with the XBlock.
|
||||
"""
|
||||
event_receiver = Mock()
|
||||
LIBRARY_BLOCK_UPDATED.connect(event_receiver)
|
||||
lib = self._create_library(slug="test_lib_block_event_add_asset_update", title="Event Test Library", description="Testing event in library") # lint-amnesty, pylint: disable=line-too-long
|
||||
lib_id = lib["id"]
|
||||
|
||||
library_key = LibraryLocatorV2.from_string(lib_id)
|
||||
|
||||
block = self._add_block_to_library(lib_id, "unit", "u1")
|
||||
block_id = block["id"]
|
||||
self._set_library_block_asset(block_id, "test.txt", b"data")
|
||||
|
||||
usage_key = LibraryUsageLocatorV2(
|
||||
lib_key=library_key,
|
||||
block_type="unit",
|
||||
usage_id="u1"
|
||||
)
|
||||
|
||||
event_receiver.assert_called_once()
|
||||
self.assertDictContainsSubset(
|
||||
{
|
||||
"signal": LIBRARY_BLOCK_UPDATED,
|
||||
"sender": None,
|
||||
"library_block": LibraryBlockData(
|
||||
library_key=library_key,
|
||||
usage_key=usage_key
|
||||
),
|
||||
},
|
||||
event_receiver.call_args.kwargs
|
||||
)
|
||||
|
||||
def test_library_block_del_asset_update_event(self):
|
||||
"""
|
||||
Check that LIBRARY_BLOCK_CREATED event is sent when a static asset is removed from XBlock.
|
||||
"""
|
||||
event_receiver = Mock()
|
||||
LIBRARY_BLOCK_UPDATED.connect(event_receiver)
|
||||
lib = self._create_library(slug="test_lib_block_event_del_asset_update", title="Event Test Library", description="Testing event in library") # lint-amnesty, pylint: disable=line-too-long
|
||||
lib_id = lib["id"]
|
||||
|
||||
library_key = LibraryLocatorV2.from_string(lib_id)
|
||||
|
||||
block = self._add_block_to_library(lib_id, "unit", "u1")
|
||||
block_id = block["id"]
|
||||
self._set_library_block_asset(block_id, "test.txt", b"data")
|
||||
|
||||
self._delete_library_block_asset(block_id, 'text.txt')
|
||||
|
||||
usage_key = LibraryUsageLocatorV2(
|
||||
lib_key=library_key,
|
||||
block_type="unit",
|
||||
usage_id="u1"
|
||||
)
|
||||
|
||||
event_receiver.assert_called()
|
||||
self.assertDictContainsSubset(
|
||||
{
|
||||
"signal": LIBRARY_BLOCK_UPDATED,
|
||||
"sender": None,
|
||||
"library_block": LibraryBlockData(
|
||||
library_key=library_key,
|
||||
usage_key=usage_key
|
||||
),
|
||||
},
|
||||
event_receiver.call_args.kwargs
|
||||
)
|
||||
|
||||
def test_library_block_delete_event(self):
|
||||
"""
|
||||
Check that LIBRARY_BLOCK_DELETED event is sent when a content library is deleted.
|
||||
"""
|
||||
event_receiver = Mock()
|
||||
LIBRARY_BLOCK_DELETED.connect(event_receiver)
|
||||
lib = self._create_library(slug="test_lib_block_event_delete", title="Event Test Library", description="Testing event in library") # lint-amnesty, pylint: disable=line-too-long
|
||||
|
||||
lib_id = lib["id"]
|
||||
library_key = LibraryLocatorV2.from_string(lib_id)
|
||||
|
||||
block = self._add_block_to_library(lib_id, "problem", "problem1")
|
||||
block_id = block['id']
|
||||
|
||||
usage_key = LibraryUsageLocatorV2(
|
||||
lib_key=library_key,
|
||||
block_type="problem",
|
||||
usage_id="problem1"
|
||||
)
|
||||
|
||||
self._delete_library_block(block_id)
|
||||
|
||||
event_receiver.assert_called()
|
||||
self.assertDictContainsSubset(
|
||||
{
|
||||
"signal": LIBRARY_BLOCK_DELETED,
|
||||
"sender": None,
|
||||
"library_block": LibraryBlockData(
|
||||
library_key=library_key,
|
||||
usage_key=usage_key
|
||||
),
|
||||
},
|
||||
event_receiver.call_args.kwargs
|
||||
)
|
||||
|
||||
|
||||
@elasticsearch_test
|
||||
class ContentLibrariesBlockstoreServiceTest(
|
||||
|
||||
@@ -165,7 +165,7 @@ class ContentLibraryIndexerTestMixin:
|
||||
self._set_library_block_asset(block["id"], "whatever.png", b"data")
|
||||
verify_uncommitted_libraries(library_key, True, False)
|
||||
commit_library_and_verify(library_key)
|
||||
self._delete_library_block_asset(block["id"], "whatever.png", expect_response=204)
|
||||
self._delete_library_block_asset(block["id"], "whatever.png")
|
||||
verify_uncommitted_libraries(library_key, True, False)
|
||||
commit_library_and_verify(library_key)
|
||||
|
||||
|
||||
@@ -113,8 +113,7 @@ oauthlib # OAuth specification support for authentica
|
||||
olxcleaner
|
||||
openedx-calc # Library supporting mathematical calculations for Open edX
|
||||
openedx-django-require
|
||||
# openedx-events 3.1.0 introduces producer API
|
||||
openedx-events>=8.2.0 # Open edX Events from Hooks Extension Framework (OEP-50)
|
||||
openedx-events>=8.3.0 # Open edX Events from Hooks Extension Framework (OEP-50)
|
||||
openedx-filters # Open edX Filters from Hooks Extension Framework (OEP-50)
|
||||
openedx-mongodbproxy
|
||||
openedx-django-wiki
|
||||
|
||||
@@ -12,8 +12,14 @@ from contextlib import contextmanager
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from opaque_keys.edx.locator import LibraryLocator
|
||||
from openedx_events.content_authoring.data import XBlockData
|
||||
from openedx_events.content_authoring.signals import XBLOCK_DELETED, XBLOCK_PUBLISHED
|
||||
from openedx_events.content_authoring.data import CourseData, XBlockData
|
||||
from openedx_events.content_authoring.signals import (
|
||||
COURSE_CREATED,
|
||||
XBLOCK_CREATED,
|
||||
XBLOCK_DELETED,
|
||||
XBLOCK_PUBLISHED,
|
||||
XBLOCK_UPDATED
|
||||
)
|
||||
|
||||
from django.utils.timezone import datetime, timezone
|
||||
from xmodule.assetstore import AssetMetadata
|
||||
@@ -127,6 +133,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
|
||||
"""
|
||||
ModuleStore knows how to route requests to the right persistence ms
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
contentstore,
|
||||
@@ -670,6 +677,14 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
|
||||
# add new course to the mapping
|
||||
self.mappings[course_key] = store
|
||||
|
||||
# .. event_implemented_name: COURSE_CREATED
|
||||
COURSE_CREATED.send_event(
|
||||
time=datetime.now(timezone.utc),
|
||||
course=CourseData(
|
||||
course_key=course_key,
|
||||
)
|
||||
)
|
||||
|
||||
return course
|
||||
|
||||
@strip_key
|
||||
@@ -742,7 +757,17 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
|
||||
in the newly created block
|
||||
"""
|
||||
modulestore = self._verify_modulestore_support(course_key, 'create_item')
|
||||
return modulestore.create_item(user_id, course_key, block_type, block_id=block_id, fields=fields, **kwargs)
|
||||
xblock = modulestore.create_item(user_id, course_key, block_type, block_id=block_id, fields=fields, **kwargs)
|
||||
# .. event_implemented_name: XBLOCK_CREATED
|
||||
XBLOCK_CREATED.send_event(
|
||||
time=datetime.now(timezone.utc),
|
||||
xblock_info=XBlockData(
|
||||
usage_key=xblock.location.for_branch(None),
|
||||
block_type=block_type,
|
||||
version=xblock.location
|
||||
)
|
||||
)
|
||||
return xblock
|
||||
|
||||
@strip_key
|
||||
@prepare_asides
|
||||
@@ -763,7 +788,19 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
|
||||
in the newly created block
|
||||
"""
|
||||
modulestore = self._verify_modulestore_support(parent_usage_key.course_key, 'create_child')
|
||||
return modulestore.create_child(user_id, parent_usage_key, block_type, block_id=block_id, fields=fields, **kwargs) # lint-amnesty, pylint: disable=line-too-long
|
||||
xblock = modulestore.create_child(
|
||||
user_id, parent_usage_key, block_type, block_id=block_id, fields=fields, **kwargs
|
||||
)
|
||||
# .. event_implemented_name: XBLOCK_CREATED
|
||||
XBLOCK_CREATED.send_event(
|
||||
time=datetime.now(timezone.utc),
|
||||
xblock_info=XBlockData(
|
||||
usage_key=xblock.location.for_branch(None),
|
||||
block_type=block_type,
|
||||
version=xblock.location
|
||||
)
|
||||
)
|
||||
return xblock
|
||||
|
||||
@strip_key
|
||||
@prepare_asides
|
||||
@@ -792,7 +829,17 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
|
||||
(content, children, and metadata) attribute the change to the given user.
|
||||
"""
|
||||
store = self._verify_modulestore_support(xblock.location.course_key, 'update_item')
|
||||
return store.update_item(xblock, user_id, allow_not_found, **kwargs)
|
||||
xblock = store.update_item(xblock, user_id, allow_not_found, **kwargs)
|
||||
# .. event_implemented_name: XBLOCK_UPDATED
|
||||
XBLOCK_UPDATED.send_event(
|
||||
time=datetime.now(timezone.utc),
|
||||
xblock_info=XBlockData(
|
||||
usage_key=xblock.location.for_branch(None),
|
||||
block_type=xblock.location.block_type,
|
||||
version=xblock.location
|
||||
)
|
||||
)
|
||||
return xblock
|
||||
|
||||
@strip_key
|
||||
def delete_item(self, location, user_id, **kwargs): # lint-amnesty, pylint: disable=arguments-differ
|
||||
|
||||
@@ -15,8 +15,14 @@ from uuid import uuid4
|
||||
from unittest.mock import Mock, call, patch
|
||||
|
||||
import ddt
|
||||
from openedx_events.content_authoring.data import XBlockData
|
||||
from openedx_events.content_authoring.signals import XBLOCK_DELETED, XBLOCK_PUBLISHED
|
||||
from openedx_events.content_authoring.data import CourseData, XBlockData
|
||||
from openedx_events.content_authoring.signals import (
|
||||
COURSE_CREATED,
|
||||
XBLOCK_CREATED,
|
||||
XBLOCK_DELETED,
|
||||
XBLOCK_PUBLISHED,
|
||||
XBLOCK_UPDATED
|
||||
)
|
||||
from openedx_events.tests.utils import OpenEdxEventsTestMixin
|
||||
import pymongo
|
||||
import pytest
|
||||
@@ -113,6 +119,9 @@ class CommonMixedModuleStoreSetup(CourseComparisonTest, OpenEdxEventsTestMixin):
|
||||
'xblock_mixins': modulestore_options['xblock_mixins'],
|
||||
}
|
||||
ENABLED_OPENEDX_EVENTS = [
|
||||
"org.openedx.content_authoring.course.created.v1",
|
||||
"org.openedx.content_authoring.xblock.created.v1",
|
||||
"org.openedx.content_authoring.xblock.updated.v1",
|
||||
"org.openedx.content_authoring.xblock.deleted.v1",
|
||||
"org.openedx.content_authoring.xblock.published.v1",
|
||||
]
|
||||
@@ -498,7 +507,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
|
||||
assert {'course', 'about'}.issubset({item.location.block_type for item in items}) # pylint: disable=line-too-long
|
||||
# Assert that about is a detached category found in get_items
|
||||
assert [item.location.block_type for item in items if item.location.block_type == 'about'][0]\
|
||||
in DETACHED_XBLOCK_TYPES
|
||||
in DETACHED_XBLOCK_TYPES
|
||||
assert len(items) == 2
|
||||
|
||||
# Check that orphans are not found
|
||||
@@ -741,6 +750,79 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
|
||||
self.store.delete_item(vertical.location, self.user_id)
|
||||
assert not self._has_changes(sequential.location)
|
||||
|
||||
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
|
||||
def test_course_create_event(self, default_ms):
|
||||
"""
|
||||
Check that COURSE_CREATED event is sent when a course is created.
|
||||
"""
|
||||
self.initdb(default_ms)
|
||||
|
||||
event_receiver = Mock()
|
||||
COURSE_CREATED.connect(event_receiver)
|
||||
|
||||
test_course = self.store.create_course('test_org', 'test_course', 'test_run', self.user_id)
|
||||
|
||||
event_receiver.assert_called()
|
||||
|
||||
self.assertDictContainsSubset(
|
||||
{
|
||||
"signal": COURSE_CREATED,
|
||||
"sender": None,
|
||||
"course": CourseData(
|
||||
course_key=test_course.id,
|
||||
),
|
||||
},
|
||||
event_receiver.call_args.kwargs
|
||||
)
|
||||
|
||||
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
|
||||
def test_xblock_create_event(self, default_ms):
|
||||
"""
|
||||
Check that XBLOCK_CREATED event is sent when xblock is created.
|
||||
"""
|
||||
self.initdb(default_ms)
|
||||
|
||||
event_receiver = Mock()
|
||||
XBLOCK_CREATED.connect(event_receiver)
|
||||
|
||||
test_course = self.store.create_course('test_org', 'test_course', 'test_run', self.user_id)
|
||||
|
||||
# create sequential to test against
|
||||
sequential = self.store.create_child(self.user_id, test_course.location, 'sequential', 'test_sequential')
|
||||
|
||||
event_receiver.assert_called()
|
||||
|
||||
assert event_receiver.call_args.kwargs['signal'] == XBLOCK_CREATED
|
||||
assert event_receiver.call_args.kwargs['xblock_info'].usage_key == sequential.location
|
||||
assert event_receiver.call_args.kwargs['xblock_info'].block_type == sequential.location.block_type
|
||||
assert event_receiver.call_args.kwargs['xblock_info'].version.for_branch(None) == sequential.location
|
||||
|
||||
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
|
||||
def test_xblock_update_event(self, default_ms):
|
||||
"""
|
||||
Check that XBLOCK_UPDATED event is sent when xblock is updated.
|
||||
"""
|
||||
self.initdb(default_ms)
|
||||
|
||||
event_receiver = Mock()
|
||||
XBLOCK_UPDATED.connect(event_receiver)
|
||||
|
||||
test_course = self.store.create_course('test_org', 'test_course', 'test_run', self.user_id)
|
||||
|
||||
# create sequential to test against
|
||||
sequential = self.store.create_child(self.user_id, test_course.location, 'sequential', 'test_sequential')
|
||||
|
||||
# Change the xblock
|
||||
sequential.display_name = 'Updated Display Name'
|
||||
self.store.update_item(sequential, user_id=self.user_id)
|
||||
|
||||
event_receiver.assert_called()
|
||||
|
||||
assert event_receiver.call_args.kwargs['signal'] == XBLOCK_UPDATED
|
||||
assert event_receiver.call_args.kwargs['xblock_info'].usage_key == sequential.location
|
||||
assert event_receiver.call_args.kwargs['xblock_info'].block_type == sequential.location.block_type
|
||||
assert event_receiver.call_args.kwargs['xblock_info'].version.for_branch(None) == sequential.location
|
||||
|
||||
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
|
||||
def test_xblock_publish_event(self, default_ms):
|
||||
"""
|
||||
@@ -1217,7 +1299,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
|
||||
"""
|
||||
for child_location, parent_location, revision in expected_results:
|
||||
assert parent_location.for_branch(None) if parent_location else parent_location == \
|
||||
self.store.get_parent_location(child_location, revision=revision)
|
||||
self.store.get_parent_location(child_location, revision=revision)
|
||||
|
||||
def verify_item_parent(self, item_location, expected_parent_location, old_parent_location, is_reverted=False):
|
||||
"""
|
||||
@@ -2461,7 +2543,7 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
|
||||
Asserts the number of problems with the given display name is the given expected number.
|
||||
"""
|
||||
assert len(self.store.get_items(course_key.for_branch(None), settings={'display_name': display_name})) ==\
|
||||
expected_number
|
||||
expected_number
|
||||
|
||||
def assertProblemNameEquals(expected_display_name):
|
||||
"""
|
||||
@@ -3085,6 +3167,7 @@ class TestPublishOverExportImport(CommonMixedModuleStoreSetup):
|
||||
Tests which publish (or don't publish) items - and then export/import the course,
|
||||
checking the state of the imported items.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up the database for testing
|
||||
@@ -3690,6 +3773,7 @@ class TestAsidesWithMixedModuleStore(CommonMixedModuleStoreSetup):
|
||||
"""
|
||||
Tests of the MixedModulestore interface methods with XBlock asides.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Setup environment for testing
|
||||
|
||||
Reference in New Issue
Block a user