* refactor: use django signals to trigger LIBRARY_COLLECTION events
* refactor: use collection usage_key as search document id
This change standardises the search document "id" to be a meilisearch ID
generated from the usage key, for all types of indexed objects.
This is important for collections so we can locate the collection
document in the search index solely from the data provided by the
LIBRARY_COLLECTION_DELETED event (library_key + collection_key), even if
the collection has been deleted from the database.
* refactor: avoid fetching more data than we have to.
* get_library_collection_usage_key and
searchable_doc_tags_for_collection do not need a Collection object;
the usage key can be created from the library_key and collection_key.
* updated searchable_doc_for_collection to require the parts of the
collection usage key + an optional collection. This allows us to
identify the collection's search document from its usage key without
requiring an existing Collection object (in case it's been deleted).
Also removes the edge case for indexing Collections not associated
with a ContentLibrary -- this won't ever really happen.
* feat: remove soft- and hard-deleted collections from search index
* feat: adds library_component_usage_key to content_libraries.api
* refactor: send CONTENT_OBJECT_ASSOCIATON_CHANGED on django model signals
so that added/removed collections are removed/re-added to component documents.
Special case: When a collection is soft-deleted/restored, we detect this
in the search index and update the collection's component documents
directly, without a CONTENT_OBJECT_ASSOCIATON_CHANGED signal.
* chore: bumps openedx-learning to 0.13.0
When serializing to OLX, the Learning Core runtime wraps HTML content in
CDATA to avoid having to escape every individual `<`, `>`, and `&`. The
runtime also puts newlines around the content within the CDATA,
So, given HTML content `...`, we get `<![CDATA[\n...\n]]>`.
The problem is that every time you serialize an HTML block to OLX, it
adds another pair of newlines. These newlines aren't visible to the end
users, but they do make it so that importing and exporting content never
reached a stable, aka "canonical" form. It also makes unit testing
difficult, because the value of `html_block.data` becomes a moving
target.
We do not believe these newlines are necessary, so we have removed them
from the `CDATA` block, and added a unit test to ensure that HTML blocks
having a canonical serialization.
Closes: https://github.com/openedx/edx-platform/issues/35525
* feat: Add Library Collections REST endpoints
* test: Add tests for Collections REST APIs
* chore: Add missing __init__ files
* docs: Add warning about unstable REST APIs
* feat: emit CONTENT_OBJECT_ASSOCIATIONS_CHANGED
whenever a content object's tags or collections have changed,
and handle that event in content/search.
The deprecated CONTENT_OBJECT_TAGS_CHANGED event is still emitted when
tags change; to be removed after Sumac.
* docs: replaces CONTENT_OBJECT_TAGS_CHANGED with CONTENT_OBJECT_ASSOCIATIONS_CHANGED
* chore: updates openedx-events==9.14.0
* chore: updates openedx-learning==0.11.4
Co-authored-by: Yusuf Musleh <yusuf@opencraft.com>
Co-authored-by: Chris Chávez <xnpiochv@gmail.com>
Co-authored-by: Rômulo Penido <romulo@thinkdash.dev>
Blockstore and all of its (experimental) functionality has been replaced with
openedx-learning, aka "Learning Core". This commit uninstalls the now-unused
openedx-blockstore package and removes all dangling references to it.
Note: This also removes the `copy_library_from_v1_to_v2` management command,
which has been broken ever since we switched from Blockstore to Learning Core.
Part of this DEPR: https://github.com/openedx/public-engineering/issues/238
This moves the Content Libraries V2 backend from Blockstore [1] over to
Learning Core [2] For high-level overview and rationale of this move, see
the Blockstore DEPR [3]. There are several follow-up tasks [4], most notably
adding support for static assets in libraries.
BREAKING CHANGE: Existing V2 libraries, backed by Blockstore, will stop
working. They will continue to be listed in Studio, but their content
will be unavailable. They need to be deleted (via Django admin) or manually
migrated to Learning Core. We do not expect production sites to be in
this situation, as the feature has never left "experimental" status.
[1] https://github.com/openedx-unsupported/blockstore
[2] https://github.com/openedx/openedx-learning/
[3] https://github.com/openedx/public-engineering/issues/238
[4] https://github.com/openedx/edx-platform/issues/34283
After we merged this PR: https://github.com/openedx/edx-platform/pull/33920
this error began popping up in logs:
Unable to load XBlock 'staffgradedxblock'
....
ImportError: cannot import name 'get_course_blocks' from
partially initialized module 'lms.djangoapps.course_blocks.api'
(most likely due to a circular import) ...
The root cause was the new imports of `derived_key` and `BlockKey` into
xmodule/library_content_block.py. Those new imports come from
xmodule/modulestore/store_utilities.py, which runs
`XBlock.load_classes()` at the module level, which fails because we are
still in the process of loading xmodule/library_content_block.
As a solution, we move both `derived_key` and `BlockKey` to
xmodule/util/keys.py. We could potentially move that file to opaque-keys
eventually, depending on how well we think that those concepts generalize.
Also:
* We rename the function from derived_key to derive_key, as
functions should be verbs.
* We combine the first to parameters of derive_key (a source ContextKey
and a source BlockKey) into a single parameter (a source UsageKey). In
my opinion, this makes the function call easier to understand.
BREAKING CHANGE: Removes all code, tests, and settings related to
indexing of V2 (blockstore-backed) content libraries in elasticsearch.
This includes indexing of top-level library metadata as well as indexing
of library block metadata. Operators who enabled the experimental
Library Authoring MFE *and* the experimental ENABLE_CONTENT_LIBRARY_INDEX
feature may notice that sorting, filtering, and searching of V2
libraries and their blocks may now be slower and/or less powerful.
The ENABLE_CONTENT_LIBRARY_INDEX feature was already disabled by
default, so most/all operators (including edx.org) should not notice
any difference.
Removed settings include:
* FEATURES['ENABLE_CONTENT_LIBRARY_INDEX']
* ENABLE_ELASTICSEARCH_FOR_TESTS
* TEST_ELASTICSEARCH_USE_SSL
* TEST_ELASTICSEARCH_HOST
* TEST_ELASTICSEARCH_PORT
For rationale, see the updated "Status" section of:
./openedx/core/djangoapps/content_libraries/docs/decisions/0001-index-libraries-in-elasticsearch.rst
Originally, Blockstore was an independent micro-service, accessed via a REST API.
Then, we changed Blockstore so it could be installed as an in-process Django app.
To support both modes, there existed a blockstore_api wrapper library in edx-platform,
with toggles controlling whether the wrapper called out to the micro-service's REST API versus the
Django app's Python API. Now that the micro-service Blockstore implementation is deprecated,
though, this wrapper library and toggles are just unnecessary complexity.
As a first step towards cleanup, we:
* remove several toggles and settings (details below);
* remove the blocokstore_api wrapper methods which called the REST API and
marshalled them back into Python objects; and
* remove all test cases which relied on the Blockstore micro-service (and were skippped in CI).
In the future, we will remove the content libraries indexer,
clean up the remaining bits of blockstore_api, and flatten out all
the Blockstore-related test class hierarchies which are no longer nceessary.
BREAKING CHANGE:
* These Django settings are removed:
* BLOCKSTORE_PUBLIC_URL_ROOT
* BLOCKSTORE_API_URL
* BLOCKSTORE_API_AUTH_TOKEN
* BLOCKSTORE_USE_BLOCKSTORE_APP_API
* The blockstore.use_blockstore_app_api Waffle switch is removed.
* edx-platform will act as it did when the DJango setting BLOCKSTORE_USE_BLOCKSTORE_APP_API
or the Waffle switch blockstore.use_blockstore_app_api were enabled. That is, any running Blockstore
micro-service instance will be ignored, and the Blockstore package which is installed into edx-platform
will be used instead.
Ref: https://github.com/openedx/blockstore/issues/296
After refactoring the library_content block to use asynchronous tasks
for syncing and duplicating children, we are seeing an error arise
during library_content duplication process on edx.org:
Traceback (most recent call last):
File "/edx/app/edxapp/edx-platform/openedx/core/djangoapps/content_libraries/tasks.py", line 305, in duplicate_children
_copy_overrides(store=store, user_id=user_id, source_block=source_block, dest_block=dest_block)
File "/edx/app/edxapp/edx-platform/openedx/core/djangoapps/content_libraries/tasks.py", line 383, in _copy_overrides
dest_block=dest_block.runtime.get_block(dest_child_key),
File "/edx/app/edxapp/edx-platform/xmodule/x_module.py", line 1401, in get_block
block = self.load_item(usage_id, for_parent=for_parent)
File "/edx/app/edxapp/edx-platform/xmodule/modulestore/split_mongo/caching_descriptor_system.py", line 127, in _load_item
block_data = self.get_module_data(block_key, course_key)
File "/edx/app/edxapp/edx-platform/xmodule/modulestore/split_mongo/caching_descriptor_system.py", line 154, in get_module_data
raise ItemNotFoundError(block_key)
xmodule.modulestore.exceptions.ItemNotFoundError: BlockKey(...)
We cannot reproduce the issue locally.
We are not entirely certain the cause of this, but we think it might
have do with caching. Specifically, the `store.get_item` and
`source_block.runtime.get_block` methods might use a different cache
than `dest_block.runtime.get_block`. It's possible that writes to
Mongo are sitting in one of those caches, causing reads from the
`dest_block.runtime`'s cache to fail to find dest_block's children.
We attempt to fix this by using the same "block getting" method
consistently. So instead of using a mix of `store.get_item`,
`source_block.runtime.get_block`, and `dest_block.runtime.get_block`,
we just use `store.get_item` everywhere.
Refactors and reworks the LibraryContentBlock so that its
sync-from-library operations are asynchronous and work with
V2 content libraries. This also required us to make
library_content block duplication asynchronous, as that
involves syncing from the source library.
For the sake of clarity, this PR includes two major method renames:
* update_children(...) -> sync_from_library(...)
* refresh_library(...) -> sync_from_library(upgrade_to_latest=True, ...)
an an XBlock HTTP handler rename:
/refresh_children -> /upgrade_and_sync
There are still a couple issues with import or duplication
of library_content blocks referencing V2 libraries other than
latest. These will be resolved in an upcoming PR.
Part of: https://openedx.atlassian.net/wiki/spaces/COMM/pages/3820617729/Spec+Memo+Content+Library+Authoring+Experience+V2
Follow-up work: https://github.com/openedx/edx-platform/issues/33640
Co-authored-by: Connor Haugh <chaugh@2u.com>
Co-authored-by: Eugene Dyudyunov <evgen.dyudyunov@raccoongang.com>
* chore: typing + fixes for content_staging
* chore: typing + fixes for learning_sequences
* chore: typing + fixes for content_libraries
* chore: typing + fixes for new XBlock runtime
* feat: type hinting more code with mypy
* feat: add xblock endpoint for updating an xblock
fix: remove debugger
feat: make function call more generic
refactor: just use request.json for request data as before
refactor: extract method
fix: revert wrong method change
fix: refactor correct method
feat: use handle_xblock method so that we can do more than update xblocks
fix: usage_key_string defaults to None
add all CRUD operations
fix usage key parameter
refactor: create /views folder
refactor: move xblock view functions to xblock_services
fix: tests
fix: tests
refactor: move xblock API endpoint to contentstore
* docs: add explanatory comment to new xblock_service
* feat: add feature flag for enabling content editing api
* feat: raise 404 if studio content api is disabled
* tests: test xblock endpoint
* test: make all post tests work
* test: check that xblock_handler receives correct args
* refactor: create util mixin for course factories with staff
* refactor: extract course staff authorization tests
* refactor: extract tests to api view testcase class
* test: add get tests
* test: fix tests
* test: fix tests
* test: fix tests
* test: add all crud tests
* fix: refactor to fix tests
* fix: merge conflict
* fix: merge conflict
* fix: tests after merge
* fix: json request decorator
* fix: lint
* fix: lint
* fix: lint
* fix: lint
* fix: new test files
* fix: lint
* fix: lint and apply PR suggestions
* fix: lint
* fix: lint
* fix: lint
* fix: lint
* fix: lint
* fix: lint
* test: warn about dependencies from cms->openedx->lms and vice versa
* test: warn about importing from package's internal implementation code
* chore: Update some imports to use public APIs only
* chore: Update 'bookmarks' app to have stricter public API
* fix: we are sharing 'adapters' from olx_rest_api to content_staging
This will ensure that errors raised by these tasks will alert the right
team. `send_course_enrollment_email` is the one I set out to fix, but I
discovered a few others.
I located tasks that were missing decorators by running the following
search and visually inspecting the results, although semgrep might be able
to do better:
```
ack '^@.*task|^@set_code_owner_attribute' cms lms common openedx xmodule --ignore-dir=tests --python
```
Also, add more detailed explanation of why a couple of tasks can't use the
decorator. This should only be an issue on tasks inheriting from
UserTaskMixin, which in practice is just CourseExportTask and
CourseImportTask (and the apparently unused EnrollmentReadTask and
EnrollmentWriteTask), via UserTask.
Previously, CMS Django admin would demand that at least one
LTIConfig be associated with the new library. This is not
necessary, but Django thought it was, because the `blank`
flag was not enabled on the ContentLibrary model's
`authored_lti_config` many-to-many field.
This should not affect the experimental blockstore-based
library authoring frontend, which already allows libraries
to be created without any authorized_lti_configs.
This will not affect the existing modulestore-based
library authoring frontend, since it doesn't even use
any of the models in question.
A migration is included with this commit, but it should NOT
change MySQL schema, since `blank` is a Django-level validation
flag.
* fix: call blockstore APIs in atomic transactions.
To ensure database integry when using the blockstore APIs, the Content
Library views which invoke blockstore API methods are wrapped in
database transactions.
* fix: assert create_library is called inside an atomic transaction block
Rendering asset URLs requires HTML blocks to be associated with a course key.
This change allows HTML blocks to be associated with libraries and still
render without error.