diff --git a/.github/workflows/ci-static-analysis.yml b/.github/workflows/ci-static-analysis.yml index 7e768a45646..a3b0527aad7 100644 --- a/.github/workflows/ci-static-analysis.yml +++ b/.github/workflows/ci-static-analysis.yml @@ -10,7 +10,7 @@ jobs: matrix: python-version: - "3.11" - os: ["ubuntu-20.04"] + os: ["ubuntu-latest"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/compile-python-requirements.yml b/.github/workflows/compile-python-requirements.yml index 0ff99b9c685..21cb80083f1 100644 --- a/.github/workflows/compile-python-requirements.yml +++ b/.github/workflows/compile-python-requirements.yml @@ -15,7 +15,7 @@ defaults: jobs: recompile-python-dependencies: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Check out target branch diff --git a/.github/workflows/js-tests.yml b/.github/workflows/js-tests.yml index 4d025e54016..c9d2d7ab119 100644 --- a/.github/workflows/js-tests.yml +++ b/.github/workflows/js-tests.yml @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04] + os: [ubuntu-latest] node-version: [18, 20] python-version: - "3.11" diff --git a/.github/workflows/lint-imports.yml b/.github/workflows/lint-imports.yml index 8ead8396bf3..e3c59ec0930 100644 --- a/.github/workflows/lint-imports.yml +++ b/.github/workflows/lint-imports.yml @@ -9,7 +9,7 @@ on: jobs: lint-imports: name: Lint Python Imports - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Check out branch diff --git a/.github/workflows/migrations-check.yml b/.github/workflows/migrations-check.yml index 183b90effa2..f253d48e4f4 100644 --- a/.github/workflows/migrations-check.yml +++ b/.github/workflows/migrations-check.yml @@ -13,7 +13,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04] + os: [ubuntu-latest] python-version: - "3.11" # 'pinned' is used to install the latest patch version of Django @@ -52,7 +52,7 @@ jobs: steps: - name: Setup mongodb user run: | - mongosh edxapp --eval ' + docker exec ${{ job.services.mongo.id }} mongosh edxapp --eval ' db.createUser( { user: "edxapp", @@ -67,7 +67,7 @@ jobs: - name: Verify mongo and mysql db credentials run: | mysql -h 127.0.0.1 -uedxapp001 -ppassword -e "select 1;" edxapp - mongosh --host 127.0.0.1 --username edxapp --password password --eval 'use edxapp; db.adminCommand("ping");' edxapp + docker exec ${{ job.services.mongo.id }} mongosh --host 127.0.0.1 --username edxapp --password password --eval 'use edxapp; db.adminCommand("ping");' edxapp - name: Checkout repo uses: actions/checkout@v4 diff --git a/.github/workflows/publish-ci-docker-image.yml b/.github/workflows/publish-ci-docker-image.yml index 0a9f50f6daf..6a0f3768b7e 100644 --- a/.github/workflows/publish-ci-docker-image.yml +++ b/.github/workflows/publish-ci-docker-image.yml @@ -7,7 +7,7 @@ on: jobs: push: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/pylint-checks.yml b/.github/workflows/pylint-checks.yml index eeb53c24ed9..144cc77a3da 100644 --- a/.github/workflows/pylint-checks.yml +++ b/.github/workflows/pylint-checks.yml @@ -8,7 +8,7 @@ on: jobs: run-pylint: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: fail-fast: false matrix: @@ -20,7 +20,7 @@ jobs: - module-name: openedx-1 path: "--django-settings-module=lms.envs.test openedx/core/types/ openedx/core/djangoapps/ace_common/ openedx/core/djangoapps/agreements/ openedx/core/djangoapps/api_admin/ openedx/core/djangoapps/auth_exchange/ openedx/core/djangoapps/bookmarks/ openedx/core/djangoapps/cache_toolbox/ openedx/core/djangoapps/catalog/ openedx/core/djangoapps/ccxcon/ openedx/core/djangoapps/commerce/ openedx/core/djangoapps/common_initialization/ openedx/core/djangoapps/common_views/ openedx/core/djangoapps/config_model_utils/ openedx/core/djangoapps/content/ openedx/core/djangoapps/content_libraries/ openedx/core/djangoapps/content_staging/ openedx/core/djangoapps/contentserver/ openedx/core/djangoapps/cookie_metadata/ openedx/core/djangoapps/cors_csrf/ openedx/core/djangoapps/course_apps/ openedx/core/djangoapps/course_date_signals/ openedx/core/djangoapps/course_groups/ openedx/core/djangoapps/courseware_api/ openedx/core/djangoapps/crawlers/ openedx/core/djangoapps/credentials/ openedx/core/djangoapps/credit/ openedx/core/djangoapps/dark_lang/ openedx/core/djangoapps/debug/ openedx/core/djangoapps/discussions/ openedx/core/djangoapps/django_comment_common/ openedx/core/djangoapps/embargo/ openedx/core/djangoapps/enrollments/ openedx/core/djangoapps/external_user_ids/ openedx/core/djangoapps/zendesk_proxy/ openedx/core/djangolib/ openedx/core/lib/ openedx/core/tests/ openedx/core/djangoapps/course_live/" - module-name: openedx-2 - path: "--django-settings-module=lms.envs.test openedx/core/djangoapps/geoinfo/ openedx/core/djangoapps/header_control/ openedx/core/djangoapps/heartbeat/ openedx/core/djangoapps/lang_pref/ openedx/core/djangoapps/models/ openedx/core/djangoapps/monkey_patch/ openedx/core/djangoapps/oauth_dispatch/ openedx/core/djangoapps/olx_rest_api/ openedx/core/djangoapps/password_policy/ openedx/core/djangoapps/plugin_api/ openedx/core/djangoapps/plugins/ openedx/core/djangoapps/profile_images/ openedx/core/djangoapps/programs/ openedx/core/djangoapps/safe_sessions/ openedx/core/djangoapps/schedules/ openedx/core/djangoapps/service_status/ openedx/core/djangoapps/session_inactivity_timeout/ openedx/core/djangoapps/signals/ openedx/core/djangoapps/site_configuration/ openedx/core/djangoapps/system_wide_roles/ openedx/core/djangoapps/theming/ openedx/core/djangoapps/user_api/ openedx/core/djangoapps/user_authn/ openedx/core/djangoapps/util/ openedx/core/djangoapps/verified_track_content/ openedx/core/djangoapps/video_config/ openedx/core/djangoapps/video_pipeline/ openedx/core/djangoapps/waffle_utils/ openedx/core/djangoapps/xblock/ openedx/core/djangoapps/xmodule_django/ openedx/core/tests/ openedx/features/ openedx/testing/ openedx/tests/ openedx/core/djangoapps/learner_pathway/ openedx/core/djangoapps/notifications/ openedx/core/djangoapps/staticfiles/ openedx/core/djangoapps/content_tagging/" + path: "--django-settings-module=lms.envs.test openedx/core/djangoapps/geoinfo/ openedx/core/djangoapps/header_control/ openedx/core/djangoapps/heartbeat/ openedx/core/djangoapps/lang_pref/ openedx/core/djangoapps/models/ openedx/core/djangoapps/monkey_patch/ openedx/core/djangoapps/oauth_dispatch/ openedx/core/djangoapps/olx_rest_api/ openedx/core/djangoapps/password_policy/ openedx/core/djangoapps/plugin_api/ openedx/core/djangoapps/plugins/ openedx/core/djangoapps/profile_images/ openedx/core/djangoapps/programs/ openedx/core/djangoapps/safe_sessions/ openedx/core/djangoapps/schedules/ openedx/core/djangoapps/service_status/ openedx/core/djangoapps/session_inactivity_timeout/ openedx/core/djangoapps/signals/ openedx/core/djangoapps/site_configuration/ openedx/core/djangoapps/system_wide_roles/ openedx/core/djangoapps/theming/ openedx/core/djangoapps/user_api/ openedx/core/djangoapps/user_authn/ openedx/core/djangoapps/util/ openedx/core/djangoapps/verified_track_content/ openedx/core/djangoapps/video_config/ openedx/core/djangoapps/video_pipeline/ openedx/core/djangoapps/waffle_utils/ openedx/core/djangoapps/xblock/ openedx/core/djangoapps/xmodule_django/ openedx/core/tests/ openedx/features/ openedx/testing/ openedx/tests/ openedx/core/djangoapps/notifications/ openedx/core/djangoapps/staticfiles/ openedx/core/djangoapps/content_tagging/" - module-name: common path: "--django-settings-module=lms.envs.test common pavelib" - module-name: cms diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index cf8ffd5d291..5445d70e3b4 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows/quality-checks.yml @@ -13,7 +13,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04] + os: [ubuntu-latest] python-version: - "3.11" node-version: [20] diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 7f2b4925af8..d880d735176 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -17,7 +17,7 @@ jobs: runs-on: "${{ matrix.os }}" strategy: matrix: - os: ["ubuntu-20.04"] + os: ["ubuntu-latest"] python-version: - "3.11" diff --git a/.github/workflows/static-assets-check.yml b/.github/workflows/static-assets-check.yml index 7bbfd3369b6..0a417f9b1c7 100644 --- a/.github/workflows/static-assets-check.yml +++ b/.github/workflows/static-assets-check.yml @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04] + os: [ubuntu-latest] python-version: - "3.11" node-version: [18, 20] @@ -72,9 +72,6 @@ jobs: run: | pip install -r requirements/edx/assets.txt - - name: Initiate Mongo DB Service - run: sudo systemctl start mongod - - name: Add node_modules bin to $Path run: echo $GITHUB_WORKSPACE/node_modules/.bin >> $GITHUB_PATH diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 3e442b75d4e..5fef1c8352c 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -15,7 +15,7 @@ concurrency: jobs: run-tests: name: ${{ matrix.shard_name }}(py=${{ matrix.python-version }},dj=${{ matrix.django-version }},mongo=${{ matrix.mongo-version }}) - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: python-version: @@ -66,7 +66,29 @@ jobs: - name: install system requirements run: | - sudo apt-get update && sudo apt-get install libmysqlclient-dev libxmlsec1-dev lynx + sudo apt-get update && sudo apt-get install libmysqlclient-dev libxmlsec1-dev lynx openssl + + # This is needed until the ENABLE_BLAKE2B_HASHING can be removed and we + # can stop using MD4 by default. + - name: enable md4 hashing in libssl + run: | + cat < int: docs = [] for collection in batch: try: - doc = searchable_doc_for_collection(collection) - doc.update(searchable_doc_tags_for_collection(library_key, collection)) + doc = searchable_doc_for_collection(library_key, collection.key, collection=collection) + doc.update(searchable_doc_tags_for_collection(library_key, collection.key)) docs.append(doc) except Exception as err: # pylint: disable=broad-except status_cb(f"Error indexing collection {collection}: {err}") @@ -512,15 +513,28 @@ def delete_index_doc(usage_key: UsageKey) -> None: Args: usage_key (UsageKey): The usage key of the XBlock to be removed from the index """ - current_rebuild_index_name = _get_running_rebuild_index_name() + doc = searchable_doc_for_usage_key(usage_key) + _delete_index_doc(doc[Fields.id]) + + +def _delete_index_doc(doc_id) -> None: + """ + Helper function that deletes the document with the given ID from the search index + + If there is a rebuild in progress, the document will also be removed from the new index. + """ + if not doc_id: + return client = _get_meilisearch_client() + current_rebuild_index_name = _get_running_rebuild_index_name() tasks = [] if current_rebuild_index_name: - # If there is a rebuild in progress, the document will also be deleted from the new index. - tasks.append(client.index(current_rebuild_index_name).delete_document(meili_id_from_opaque_key(usage_key))) - tasks.append(client.index(STUDIO_INDEX_NAME).delete_document(meili_id_from_opaque_key(usage_key))) + # If there is a rebuild in progress, the document will also be removed from the new index. + tasks.append(client.index(current_rebuild_index_name).delete_document(doc_id)) + + tasks.append(client.index(STUDIO_INDEX_NAME).delete_document(doc_id)) _wait_for_meili_tasks(tasks) @@ -563,20 +577,94 @@ def upsert_library_block_index_doc(usage_key: UsageKey) -> None: _update_index_docs(docs) +def _get_document_from_index(document_id: str) -> dict: + """ + Returns the Document identified by the given ID, from the given index. + + Returns None if the document or index do not exist. + """ + client = _get_meilisearch_client() + document = None + index_name = STUDIO_INDEX_NAME + try: + index = client.get_index(index_name) + document = index.get_document(document_id) + except (MeilisearchError, MeilisearchApiError) as err: + # The index or document doesn't exist + log.warning(f"Unable to fetch document {document_id} from {index_name}: {err}") + + return document + + def upsert_library_collection_index_doc(library_key: LibraryLocatorV2, collection_key: str) -> None: """ - Creates or updates the document for the given Library Collection in the search index + Creates, updates, or deletes the document for the given Library Collection in the search index. + + If the Collection is not found or disabled (i.e. soft-deleted), then delete it from the search index. """ - content_library = lib_api.ContentLibrary.objects.get_by_key(library_key) - collection = authoring_api.get_collection( - learning_package_id=content_library.learning_package_id, - collection_key=collection_key, - ) - docs = [ - searchable_doc_for_collection(collection) - ] + doc = searchable_doc_for_collection(library_key, collection_key) + update_components = False - _update_index_docs(docs) + # Soft-deleted/disabled collections are removed from the index + # and their components updated. + if doc.get('_disabled'): + + _delete_index_doc(doc[Fields.id]) + + update_components = True + + # Hard-deleted collections are also deleted from the index, + # but their components are automatically updated as part of the deletion process, so we don't have to. + elif not doc.get(Fields.type): + + _delete_index_doc(doc[Fields.id]) + + # Otherwise, upsert the collection. + # Newly-added/restored collection get their components updated too. + else: + already_indexed = _get_document_from_index(doc[Fields.id]) + if not already_indexed: + update_components = True + + _update_index_docs([doc]) + + # Asynchronously update the collection's components "collections" field + if update_components: + from .tasks import update_library_components_collections as update_task + + update_task.delay(str(library_key), collection_key) + + +def update_library_components_collections( + library_key: LibraryLocatorV2, + collection_key: str, + batch_size: int = 1000, +) -> None: + """ + Updates the "collections" field for all components associated with a given Library Collection. + + Because there may be a lot of components, we send these updates to Meilisearch in batches. + """ + library = lib_api.get_library(library_key) + components = authoring_api.get_collection_components(library.learning_package.id, collection_key) + + paginator = Paginator(components, batch_size) + for page in paginator.page_range: + docs = [] + + for component in paginator.page(page).object_list: + usage_key = lib_api.library_component_usage_key( + library_key, + component, + ) + doc = searchable_doc_collections(usage_key) + docs.append(doc) + + log.info( + f"Updating document.collections for library {library_key} components" + f" page {page} / {paginator.num_pages}" + ) + _update_index_docs(docs) def upsert_content_library_index_docs(library_key: LibraryLocatorV2) -> None: @@ -614,10 +702,8 @@ def upsert_collection_tags_index_docs(collection_usage_key: LibraryCollectionLoc """ Updates the tags data in documents for the given library collection """ - collection = lib_api.get_library_collection_from_usage_key(collection_usage_key) - doc = {Fields.id: collection.id} - doc.update(searchable_doc_tags_for_collection(collection_usage_key.library_key, collection)) + doc = searchable_doc_tags_for_collection(collection_usage_key.library_key, collection_usage_key.collection_id) _update_index_docs([doc]) diff --git a/openedx/core/djangoapps/content/search/documents.py b/openedx/core/djangoapps/content/search/documents.py index f9041468c29..eabeab9654c 100644 --- a/openedx/core/djangoapps/content/search/documents.py +++ b/openedx/core/djangoapps/content/search/documents.py @@ -16,7 +16,7 @@ from openedx.core.djangoapps.content_libraries import api as lib_api from openedx.core.djangoapps.content_tagging import api as tagging_api from openedx.core.djangoapps.xblock import api as xblock_api -from openedx_learning.api.authoring_models import LearningPackage +from openedx_learning.api.authoring_models import Collection log = logging.getLogger(__name__) @@ -112,6 +112,15 @@ def _meili_access_id_from_context_key(context_key: LearningContextKey) -> int: return access.id +def searchable_doc_for_usage_key(usage_key: UsageKey) -> dict: + """ + Generates a base document identified by its usage key. + """ + return { + Fields.id: meili_id_from_opaque_key(usage_key), + } + + def _fields_from_block(block) -> dict: """ Given an XBlock instance, call its index_dictionary() method to load any @@ -297,14 +306,14 @@ def searchable_doc_for_library_block(xblock_metadata: lib_api.LibraryXBlockMetad library_name = lib_api.get_library(xblock_metadata.usage_key.context_key).title block = xblock_api.load_block(xblock_metadata.usage_key, user=None) - doc = { - Fields.id: meili_id_from_opaque_key(xblock_metadata.usage_key), + doc = searchable_doc_for_usage_key(xblock_metadata.usage_key) + doc.update({ Fields.type: DocType.library_block, Fields.breadcrumbs: [], Fields.created: xblock_metadata.created.timestamp(), Fields.modified: xblock_metadata.modified.timestamp(), Fields.last_published: xblock_metadata.last_published.timestamp() if xblock_metadata.last_published else None, - } + }) doc.update(_fields_from_block(block)) @@ -319,9 +328,7 @@ def searchable_doc_tags(usage_key: UsageKey) -> dict: Generate a dictionary document suitable for ingestion into a search engine like Meilisearch or Elasticsearch, with the tags data for the given content object. """ - doc = { - Fields.id: meili_id_from_opaque_key(usage_key), - } + doc = searchable_doc_for_usage_key(usage_key) doc.update(_tags_for_content_object(usage_key)) return doc @@ -332,9 +339,7 @@ def searchable_doc_collections(usage_key: UsageKey) -> dict: Generate a dictionary document suitable for ingestion into a search engine like Meilisearch or Elasticsearch, with the collections data for the given content object. """ - doc = { - Fields.id: meili_id_from_opaque_key(usage_key), - } + doc = searchable_doc_for_usage_key(usage_key) doc.update(_collections_for_content_object(usage_key)) return doc @@ -342,21 +347,17 @@ def searchable_doc_collections(usage_key: UsageKey) -> dict: def searchable_doc_tags_for_collection( library_key: LibraryLocatorV2, - collection, + collection_key: str, ) -> dict: """ Generate a dictionary document suitable for ingestion into a search engine like Meilisearch or Elasticsearch, with the tags data for the given library collection. """ - doc = { - Fields.id: collection.id, - } - collection_usage_key = lib_api.get_library_collection_usage_key( library_key, - collection.key, + collection_key, ) - + doc = searchable_doc_for_usage_key(collection_usage_key) doc.update(_tags_for_content_object(collection_usage_key)) return doc @@ -368,49 +369,65 @@ def searchable_doc_for_course_block(block) -> dict: like Meilisearch or Elasticsearch, so that the given course block can be found using faceted search. """ - doc = { - Fields.id: meili_id_from_opaque_key(block.usage_key), + doc = searchable_doc_for_usage_key(block.usage_key) + doc.update({ Fields.type: DocType.course_block, - } + }) doc.update(_fields_from_block(block)) return doc -def searchable_doc_for_collection(collection) -> dict: +def searchable_doc_for_collection( + library_key: LibraryLocatorV2, + collection_key: str, + *, + # Optionally provide the collection if we've already fetched one + collection: Collection | None = None, +) -> dict: """ Generate a dictionary document suitable for ingestion into a search engine like Meilisearch or Elasticsearch, so that the given collection can be found using faceted search. + + If no collection is found for the given library_key + collection_key, the returned document will contain only basic + information derived from the collection usage key, and no Fields.type value will be included in the returned dict. """ - doc = { - Fields.id: collection.id, - Fields.block_id: collection.key, - Fields.type: DocType.collection, - Fields.display_name: collection.title, - Fields.description: collection.description, - Fields.created: collection.created.timestamp(), - Fields.modified: collection.modified.timestamp(), - # Add related learning_package.key as context_key by default. - # If related contentlibrary is found, it will override this value below. - # Mostly contentlibrary.library_key == learning_package.key - Fields.context_key: collection.learning_package.key, - Fields.num_children: collection.entities.count(), - } - # Just in case learning_package is not related to a library + collection_usage_key = lib_api.get_library_collection_usage_key( + library_key, + collection_key, + ) + + doc = searchable_doc_for_usage_key(collection_usage_key) + try: - context_key = collection.learning_package.contentlibrary.library_key - org = str(context_key.org) + collection = collection or lib_api.get_library_collection_from_usage_key(collection_usage_key) + except lib_api.ContentLibraryCollectionNotFound: + # Collection not found, so we can only return the base doc + pass + + if collection: + assert collection.key == collection_key + doc.update({ - Fields.context_key: str(context_key), - Fields.org: org, - Fields.usage_key: str(lib_api.get_library_collection_usage_key(context_key, collection.key)), + Fields.context_key: str(library_key), + Fields.org: str(library_key.org), + Fields.usage_key: str(collection_usage_key), + Fields.block_id: collection.key, + Fields.type: DocType.collection, + Fields.display_name: collection.title, + Fields.description: collection.description, + Fields.created: collection.created.timestamp(), + Fields.modified: collection.modified.timestamp(), + Fields.num_children: collection.entities.count(), + Fields.access_id: _meili_access_id_from_context_key(library_key), + Fields.breadcrumbs: [{"display_name": collection.learning_package.title}], }) - except LearningPackage.contentlibrary.RelatedObjectDoesNotExist: - log.warning(f"Related library not found for {collection}") - doc[Fields.access_id] = _meili_access_id_from_context_key(doc[Fields.context_key]) - # Add the breadcrumbs. - doc[Fields.breadcrumbs] = [{"display_name": collection.learning_package.title}] + + # Disabled collections should be removed from the search index, + # so we mark them as _disabled + if not collection.enabled: + doc['_disabled'] = True return doc diff --git a/openedx/core/djangoapps/content/search/handlers.py b/openedx/core/djangoapps/content/search/handlers.py index f50dead8474..085387d336b 100644 --- a/openedx/core/djangoapps/content/search/handlers.py +++ b/openedx/core/djangoapps/content/search/handlers.py @@ -23,6 +23,7 @@ LIBRARY_BLOCK_DELETED, LIBRARY_BLOCK_UPDATED, LIBRARY_COLLECTION_CREATED, + LIBRARY_COLLECTION_DELETED, LIBRARY_COLLECTION_UPDATED, XBLOCK_CREATED, XBLOCK_DELETED, @@ -166,6 +167,7 @@ def content_library_updated_handler(**kwargs) -> None: @receiver(LIBRARY_COLLECTION_CREATED) +@receiver(LIBRARY_COLLECTION_DELETED) @receiver(LIBRARY_COLLECTION_UPDATED) @only_if_meilisearch_enabled def library_collection_updated_handler(**kwargs) -> None: diff --git a/openedx/core/djangoapps/content/search/tasks.py b/openedx/core/djangoapps/content/search/tasks.py index d9dad834db2..98390a12f3b 100644 --- a/openedx/core/djangoapps/content/search/tasks.py +++ b/openedx/core/djangoapps/content/search/tasks.py @@ -90,10 +90,23 @@ def update_content_library_index_docs(library_key_str: str) -> None: @set_code_owner_attribute def update_library_collection_index_doc(library_key_str: str, collection_key: str) -> None: """ - Celery task to update the content index documents for a library collection + Celery task to update the content index document for a library collection """ library_key = LibraryLocatorV2.from_string(library_key_str) log.info("Updating content index documents for collection %s in library%s", collection_key, library_key) api.upsert_library_collection_index_doc(library_key, collection_key) + + +@shared_task(base=LoggedTask, autoretry_for=(MeilisearchError, ConnectionError)) +@set_code_owner_attribute +def update_library_components_collections(library_key_str: str, collection_key: str) -> None: + """ + Celery task to update the "collections" field for components in the given content library collection. + """ + library_key = LibraryLocatorV2.from_string(library_key_str) + + log.info("Updating document.collections for library %s collection %s components", library_key, collection_key) + + api.update_library_components_collections(library_key, collection_key) diff --git a/openedx/core/djangoapps/content/search/tests/test_api.py b/openedx/core/djangoapps/content/search/tests/test_api.py index 4aa41a156da..4c6227af309 100644 --- a/openedx/core/djangoapps/content/search/tests/test_api.py +++ b/openedx/core/djangoapps/content/search/tests/test_api.py @@ -141,7 +141,7 @@ def setUp(self): "context_key": "lib:org1:lib", "org": "org1", "breadcrumbs": [{"display_name": "Library"}], - "content": {"problem_types": [], "capa_content": " "}, + "content": {"problem_types": [], "capa_content": ""}, "type": "library_block", "access_id": lib_access.id, "last_published": None, @@ -157,7 +157,7 @@ def setUp(self): "context_key": "lib:org1:lib", "org": "org1", "breadcrumbs": [{"display_name": "Library"}], - "content": {"problem_types": [], "capa_content": " "}, + "content": {"problem_types": [], "capa_content": ""}, "type": "library_block", "access_id": lib_access.id, "last_published": None, @@ -187,7 +187,7 @@ def setUp(self): ) self.collection_usage_key = "lib-collection:org1:lib:MYCOL" self.collection_dict = { - "id": self.collection.id, + "id": "lib-collectionorg1libmycol-5b647617", "block_id": self.collection.key, "usage_key": self.collection_usage_key, "type": "collection", @@ -461,7 +461,7 @@ def test_index_library_block_and_collections(self, mock_meilisearch): # Build expected docs at each stage lib_access, _ = SearchAccess.objects.get_or_create(context_key=self.library.key) doc_collection1_created = { - "id": collection1.id, + "id": "lib-collectionorg1libcol1-283a79c9", "block_id": collection1.key, "usage_key": f"lib-collection:org1:lib:{collection1.key}", "type": "collection", @@ -476,7 +476,7 @@ def test_index_library_block_and_collections(self, mock_meilisearch): "breadcrumbs": [{"display_name": "Library"}], } doc_collection2_created = { - "id": collection2.id, + "id": "lib-collectionorg1libcol2-46823d4d", "block_id": collection2.key, "usage_key": f"lib-collection:org1:lib:{collection2.key}", "type": "collection", @@ -491,7 +491,7 @@ def test_index_library_block_and_collections(self, mock_meilisearch): "breadcrumbs": [{"display_name": "Library"}], } doc_collection2_updated = { - "id": collection2.id, + "id": "lib-collectionorg1libcol2-46823d4d", "block_id": collection2.key, "usage_key": f"lib-collection:org1:lib:{collection2.key}", "type": "collection", @@ -506,7 +506,7 @@ def test_index_library_block_and_collections(self, mock_meilisearch): "breadcrumbs": [{"display_name": "Library"}], } doc_collection1_updated = { - "id": collection1.id, + "id": "lib-collectionorg1libcol1-283a79c9", "block_id": collection1.key, "usage_key": f"lib-collection:org1:lib:{collection1.key}", "type": "collection", @@ -593,14 +593,14 @@ def test_index_tags_in_collections(self, mock_meilisearch): # Build expected docs with tags at each stage doc_collection_with_tags1 = { - "id": self.collection.id, + "id": "lib-collectionorg1libmycol-5b647617", "tags": { 'taxonomy': ['A'], 'level0': ['A > one', 'A > two'] } } doc_collection_with_tags2 = { - "id": self.collection.id, + "id": "lib-collectionorg1libmycol-5b647617", "tags": { 'taxonomy': ['A', 'B'], 'level0': ['A > one', 'A > two', 'B > four', 'B > three'] @@ -615,3 +615,105 @@ def test_index_tags_in_collections(self, mock_meilisearch): ], any_order=True, ) + + @override_settings(MEILISEARCH_ENABLED=True) + def test_delete_collection(self, mock_meilisearch): + """ + Test soft-deleting, restoring, and hard-deleting a collection. + """ + # Add a component to the collection + updated_date = datetime(2023, 6, 7, 8, 9, 10, tzinfo=timezone.utc) + with freeze_time(updated_date): + library_api.update_library_collection_components( + self.library.key, + collection_key=self.collection.key, + usage_keys=[ + self.problem1.usage_key, + ], + ) + + doc_collection = copy.deepcopy(self.collection_dict) + doc_collection["num_children"] = 1 + doc_collection["modified"] = updated_date.timestamp() + doc_problem_with_collection = { + "id": self.doc_problem1["id"], + "collections": { + "display_name": [self.collection.title], + "key": [self.collection.key], + }, + } + + # Should update the collection and its component + assert mock_meilisearch.return_value.index.return_value.update_documents.call_count == 2 + mock_meilisearch.return_value.index.return_value.update_documents.assert_has_calls( + [ + call([doc_collection]), + call([doc_problem_with_collection]), + ], + any_order=True, + ) + mock_meilisearch.return_value.index.reset_mock() + + # Soft-delete the collection + authoring_api.delete_collection( + self.collection.learning_package_id, + self.collection.key, + ) + + doc_problem_without_collection = { + "id": self.doc_problem1["id"], + "collections": {}, + } + + # Should delete the collection document + mock_meilisearch.return_value.index.return_value.delete_document.assert_called_once_with( + self.collection_dict["id"], + ) + # ...and update the component's "collections" field + mock_meilisearch.return_value.index.return_value.update_documents.assert_called_once_with([ + doc_problem_without_collection, + ]) + mock_meilisearch.return_value.index.reset_mock() + + # We need to mock get_document here so that when we restore the collection below, meilisearch knows the + # collection is being re-added, so it will update its components too. + mock_meilisearch.return_value.get_index.return_value.get_document.return_value = None + + # Restore the collection + restored_date = datetime(2023, 8, 9, 10, 11, 12, tzinfo=timezone.utc) + with freeze_time(restored_date): + authoring_api.restore_collection( + self.collection.learning_package_id, + self.collection.key, + ) + + doc_collection = copy.deepcopy(self.collection_dict) + doc_collection["num_children"] = 1 + doc_collection["modified"] = restored_date.timestamp() + + # Should update the collection and its component's "collections" field + assert mock_meilisearch.return_value.index.return_value.update_documents.call_count == 2 + mock_meilisearch.return_value.index.return_value.update_documents.assert_has_calls( + [ + call([doc_collection]), + call([doc_problem_with_collection]), + ], + any_order=True, + ) + mock_meilisearch.return_value.index.reset_mock() + + # Hard-delete the collection + authoring_api.delete_collection( + self.collection.learning_package_id, + self.collection.key, + hard_delete=True, + ) + + # Should delete the collection document + mock_meilisearch.return_value.index.return_value.delete_document.assert_called_once_with( + self.collection_dict["id"], + ) + # ...and cascade delete updates the "collections" field for the associated components + mock_meilisearch.return_value.index.return_value.update_documents.assert_called_once_with([ + doc_problem_without_collection, + ]) diff --git a/openedx/core/djangoapps/content/search/tests/test_documents.py b/openedx/core/djangoapps/content/search/tests/test_documents.py index 9d51bd127bb..755ab4d19ad 100644 --- a/openedx/core/djangoapps/content/search/tests/test_documents.py +++ b/openedx/core/djangoapps/content/search/tests/test_documents.py @@ -5,7 +5,6 @@ from organizations.models import Organization from freezegun import freeze_time -from openedx_learning.api import authoring as authoring_api from openedx.core.djangoapps.content_tagging import api as tagging_api from openedx.core.djangoapps.content_libraries import api as library_api @@ -299,11 +298,11 @@ def test_html_library_block(self): } def test_collection_with_library(self): - doc = searchable_doc_for_collection(self.collection) - doc.update(searchable_doc_tags_for_collection(self.library.key, self.collection)) + doc = searchable_doc_for_collection(self.library.key, self.collection.key) + doc.update(searchable_doc_tags_for_collection(self.library.key, self.collection.key)) assert doc == { - "id": self.collection.id, + "id": "lib-collectionedx2012_falltoy_collection-d1d907a4", "block_id": self.collection.key, "usage_key": self.collection_usage_key, "type": "collection", @@ -321,33 +320,3 @@ def test_collection_with_library(self): 'level0': ['Difficulty > Normal'] } } - - def test_collection_with_no_library(self): - created_date = datetime(2023, 4, 5, 6, 7, 8, tzinfo=timezone.utc) - with freeze_time(created_date): - learning_package = authoring_api.create_learning_package( - key="course-v1:edX+toy+2012_Fall", - title="some learning_package", - description="some description", - ) - collection = authoring_api.create_collection( - learning_package_id=learning_package.id, - key="MYCOL", - title="my_collection", - created_by=None, - description="my collection description" - ) - doc = searchable_doc_for_collection(collection) - assert doc == { - "id": collection.id, - "block_id": collection.key, - "type": "collection", - "display_name": "my_collection", - "description": "my collection description", - "num_children": 0, - "context_key": learning_package.key, - "access_id": self.toy_course_access_id, - "breadcrumbs": [{"display_name": "some learning_package"}], - "created": created_date.timestamp(), - "modified": created_date.timestamp(), - } diff --git a/openedx/core/djangoapps/content/search/tests/test_handlers.py b/openedx/core/djangoapps/content/search/tests/test_handlers.py index 8a6627e3902..bdc4814d1c8 100644 --- a/openedx/core/djangoapps/content/search/tests/test_handlers.py +++ b/openedx/core/djangoapps/content/search/tests/test_handlers.py @@ -148,7 +148,7 @@ def test_create_delete_library_block(self, meilisearch_client): "context_key": "lib:orgA:lib_a", "org": "orgA", "breadcrumbs": [{"display_name": "Library Org A"}], - "content": {"problem_types": [], "capa_content": " "}, + "content": {"problem_types": [], "capa_content": ""}, "access_id": lib_access.id, "last_published": None, "created": created_date.timestamp(), diff --git a/openedx/core/djangoapps/content_libraries/api.py b/openedx/core/djangoapps/content_libraries/api.py index 3dc33aec961..b9f3779af53 100644 --- a/openedx/core/djangoapps/content_libraries/api.py +++ b/openedx/core/djangoapps/content_libraries/api.py @@ -79,20 +79,15 @@ from opaque_keys import InvalidKeyError from openedx_events.content_authoring.data import ( ContentLibraryData, - ContentObjectChangedData, LibraryBlockData, - LibraryCollectionData, ) from openedx_events.content_authoring.signals import ( - CONTENT_OBJECT_ASSOCIATIONS_CHANGED, CONTENT_LIBRARY_CREATED, CONTENT_LIBRARY_DELETED, CONTENT_LIBRARY_UPDATED, LIBRARY_BLOCK_CREATED, LIBRARY_BLOCK_DELETED, LIBRARY_BLOCK_UPDATED, - LIBRARY_COLLECTION_CREATED, - LIBRARY_COLLECTION_UPDATED, ) from openedx_learning.api import authoring as authoring_api from openedx_learning.api.authoring_models import Collection, Component, MediaType, LearningPackage, PublishableEntity @@ -223,7 +218,6 @@ class LibraryXBlockMetadata: last_draft_created_by = attr.ib("") published_by = attr.ib("") has_unpublished_changes = attr.ib(False) - tags_count = attr.ib(0) created = attr.ib(default=None, type=datetime) @classmethod @@ -242,10 +236,9 @@ def from_component(cls, library_key, component): last_draft_created_by = draft.publishable_entity_version.created_by if draft else None return cls( - usage_key=LibraryUsageLocatorV2( + usage_key=library_component_usage_key( library_key, - component.component_type.name, - component.local_key, + component, ), display_name=component.versioning.draft.title, created=component.created, @@ -787,6 +780,20 @@ def set_library_block_olx(usage_key, new_olx_str): ) +def library_component_usage_key( + library_key: LibraryLocatorV2, + component: Component, +) -> LibraryUsageLocatorV2: + """ + Returns a LibraryUsageLocatorV2 for the given library + component. + """ + return LibraryUsageLocatorV2( # type: ignore[abstract] + library_key, + block_type=component.component_type.name, + usage_id=component.local_key, + ) + + def validate_can_add_block_to_library( library_key: LibraryLocatorV2, block_type: str, @@ -1103,8 +1110,7 @@ def create_library_collection( content_library: ContentLibrary | None = None, ) -> Collection: """ - Creates a Collection in the given ContentLibrary, - and emits a LIBRARY_COLLECTION_CREATED event. + Creates a Collection in the given ContentLibrary. If you've already fetched a ContentLibrary for the given library_key, pass it in here to avoid refetching. """ @@ -1125,14 +1131,6 @@ def create_library_collection( except IntegrityError as err: raise LibraryCollectionAlreadyExists from err - # Emit event for library collection created - LIBRARY_COLLECTION_CREATED.send_event( - library_collection=LibraryCollectionData( - library_key=library_key, - collection_key=collection.key, - ) - ) - return collection @@ -1146,8 +1144,7 @@ def update_library_collection( content_library: ContentLibrary | None = None, ) -> Collection: """ - Creates a Collection in the given ContentLibrary, - and emits a LIBRARY_COLLECTION_CREATED event. + Updates a Collection in the given ContentLibrary. """ if not content_library: content_library = ContentLibrary.objects.get_by_key(library_key) # type: ignore[attr-defined] @@ -1165,14 +1162,6 @@ def update_library_collection( except Collection.DoesNotExist as exc: raise ContentLibraryCollectionNotFound from exc - # Emit event for library collection updated - LIBRARY_COLLECTION_UPDATED.send_event( - library_collection=LibraryCollectionData( - library_key=library_key, - collection_key=collection.key, - ) - ) - return collection @@ -1243,40 +1232,16 @@ def update_library_collection_components( created_by=created_by, ) - # Emit event for library collection updated - LIBRARY_COLLECTION_UPDATED.send_event( - library_collection=LibraryCollectionData( - library_key=library_key, - collection_key=collection.key, - ) - ) - - # Emit a CONTENT_OBJECT_ASSOCIATIONS_CHANGED event for each of the objects added/removed - for usage_key in usage_keys: - CONTENT_OBJECT_ASSOCIATIONS_CHANGED.send_event( - content_object=ContentObjectChangedData( - object_id=str(usage_key), - changes=["collections"], - ), - ) - return collection def get_library_collection_usage_key( library_key: LibraryLocatorV2, collection_key: str, - # As an optimization, callers may pass in a pre-fetched ContentLibrary instance - content_library: ContentLibrary | None = None, ) -> LibraryCollectionLocator: """ Returns the LibraryCollectionLocator associated to a collection """ - if not content_library: - content_library = ContentLibrary.objects.get_by_key(library_key) # type: ignore[attr-defined] - assert content_library - assert content_library.learning_package_id - assert content_library.library_key == library_key return LibraryCollectionLocator(library_key, collection_key) diff --git a/openedx/core/djangoapps/content_libraries/serializers.py b/openedx/core/djangoapps/content_libraries/serializers.py index e9e04646ace..51ba55cd6b4 100644 --- a/openedx/core/djangoapps/content_libraries/serializers.py +++ b/openedx/core/djangoapps/content_libraries/serializers.py @@ -154,6 +154,7 @@ class LibraryXBlockMetadataSerializer(serializers.Serializer): last_draft_created_by = serializers.CharField(read_only=True) has_unpublished_changes = serializers.BooleanField(read_only=True) created = serializers.DateTimeField(format=DATETIME_FORMAT, read_only=True) + modified = serializers.DateTimeField(format=DATETIME_FORMAT, read_only=True) # When creating a new XBlock in a library, the slug becomes the ID part of # the definition key and usage key: diff --git a/openedx/core/djangoapps/content_libraries/signal_handlers.py b/openedx/core/djangoapps/content_libraries/signal_handlers.py index 768b49d55f2..fedee045a9f 100644 --- a/openedx/core/djangoapps/content_libraries/signal_handlers.py +++ b/openedx/core/djangoapps/content_libraries/signal_handlers.py @@ -5,13 +5,28 @@ import logging from django.conf import settings +from django.db.models.signals import post_save, post_delete, m2m_changed from django.dispatch import receiver +from opaque_keys import InvalidKeyError +from opaque_keys.edx.locator import LibraryLocatorV2, LibraryUsageLocatorV2 +from openedx_events.content_authoring.data import ( + ContentObjectChangedData, + LibraryCollectionData, +) +from openedx_events.content_authoring.signals import ( + CONTENT_OBJECT_ASSOCIATIONS_CHANGED, + LIBRARY_COLLECTION_CREATED, + LIBRARY_COLLECTION_DELETED, + LIBRARY_COLLECTION_UPDATED, +) +from openedx_learning.api.authoring import get_collection_components, get_component, get_components +from openedx_learning.api.authoring_models import Collection, CollectionPublishableEntity, Component + from lms.djangoapps.grades.api import signals as grades_signals -from opaque_keys import InvalidKeyError # lint-amnesty, pylint: disable=wrong-import-order -from opaque_keys.edx.locator import LibraryUsageLocatorV2 # lint-amnesty, pylint: disable=wrong-import-order -from .models import LtiGradedResource +from .api import library_component_usage_key +from .models import ContentLibrary, LtiGradedResource log = logging.getLogger(__name__) @@ -55,3 +70,139 @@ def score_changed_handler(sender, **kwargs): # pylint: disable=unused-argument resource.update_score(weighted_earned, weighted_possible, modified) log.info("LTI 1.3: Score Signal: Grade upgraded: resource; %s", resource) + + +@receiver(post_save, sender=Collection, dispatch_uid="library_collection_saved") +def library_collection_saved(sender, instance, created, **kwargs): + """ + Raises LIBRARY_COLLECTION_CREATED if the Collection is new, + or LIBRARY_COLLECTION_UPDATED if updated an existing Collection. + """ + try: + library = ContentLibrary.objects.get(learning_package_id=instance.learning_package_id) + except ContentLibrary.DoesNotExist: + log.error("{instance} is not associated with a content library.") + return + + if created: + LIBRARY_COLLECTION_CREATED.send_event( + library_collection=LibraryCollectionData( + library_key=library.library_key, + collection_key=instance.key, + ) + ) + else: + LIBRARY_COLLECTION_UPDATED.send_event( + library_collection=LibraryCollectionData( + library_key=library.library_key, + collection_key=instance.key, + ) + ) + + +@receiver(post_delete, sender=Collection, dispatch_uid="library_collection_deleted") +def library_collection_deleted(sender, instance, **kwargs): + """ + Raises LIBRARY_COLLECTION_DELETED for the deleted Collection. + """ + try: + library = ContentLibrary.objects.get(learning_package_id=instance.learning_package_id) + except ContentLibrary.DoesNotExist: + log.error("{instance} is not associated with a content library.") + return + + LIBRARY_COLLECTION_DELETED.send_event( + library_collection=LibraryCollectionData( + library_key=library.library_key, + collection_key=instance.key, + ) + ) + + +def _library_collection_component_changed( + component: Component, + library_key: LibraryLocatorV2 | None = None, +) -> None: + """ + Sends a CONTENT_OBJECT_ASSOCIATIONS_CHANGED event for the component. + """ + if not library_key: + try: + library = ContentLibrary.objects.get( + learning_package_id=component.learning_package_id, + ) + except ContentLibrary.DoesNotExist: + log.error("{component} is not associated with a content library.") + return + + library_key = library.library_key + + assert library_key + + usage_key = library_component_usage_key( + library_key, + component, + ) + CONTENT_OBJECT_ASSOCIATIONS_CHANGED.send_event( + content_object=ContentObjectChangedData( + object_id=str(usage_key), + changes=["collections"], + ), + ) + + +@receiver(post_save, sender=CollectionPublishableEntity, dispatch_uid="library_collection_entity_saved") +def library_collection_entity_saved(sender, instance, created, **kwargs): + """ + Sends a CONTENT_OBJECT_ASSOCIATIONS_CHANGED event for components added to a collection. + """ + if created: + # Component.pk matches its entity.pk + component = get_component(instance.entity_id) + _library_collection_component_changed(component) + + +@receiver(post_delete, sender=CollectionPublishableEntity, dispatch_uid="library_collection_entity_deleted") +def library_collection_entity_deleted(sender, instance, **kwargs): + """ + Sends a CONTENT_OBJECT_ASSOCIATIONS_CHANGED event for components removed from a collection. + """ + # Component.pk matches its entity.pk + component = get_component(instance.entity_id) + _library_collection_component_changed(component) + + +@receiver(m2m_changed, sender=CollectionPublishableEntity, dispatch_uid="library_collection_entities_changed") +def library_collection_entities_changed(sender, instance, action, pk_set, **kwargs): + """ + Sends a CONTENT_OBJECT_ASSOCIATIONS_CHANGED event for components added/removed/cleared from a collection. + """ + if not isinstance(instance, Collection): + return + + if action not in ["post_add", "post_remove", "post_clear"]: + return + + try: + library = ContentLibrary.objects.get( + learning_package_id=instance.learning_package_id, + ) + except ContentLibrary.DoesNotExist: + log.error("{instance} is not associated with a content library.") + return + + if pk_set: + components = get_collection_components( + instance.learning_package_id, + instance.key, + ).filter(pk__in=pk_set) + else: + # When action=="post_clear", pk_set==None + # Since the collection instance now has an empty entities set, + # we don't know which ones were removed, so we need to update associations for all library components. + components = get_components( + instance.learning_package_id, + ) + + for component in components.all(): + _library_collection_component_changed(component, library.library_key) diff --git a/openedx/core/djangoapps/content_libraries/tests/test_api.py b/openedx/core/djangoapps/content_libraries/tests/test_api.py index b02e71b002a..8041c508dc3 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_api.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_api.py @@ -20,9 +20,11 @@ from openedx_events.content_authoring.signals import ( CONTENT_OBJECT_ASSOCIATIONS_CHANGED, LIBRARY_COLLECTION_CREATED, + LIBRARY_COLLECTION_DELETED, LIBRARY_COLLECTION_UPDATED, ) from openedx_events.tests.utils import OpenEdxEventsTestMixin +from openedx_learning.api import authoring as authoring_api from .. import api from ..models import ContentLibrary @@ -264,6 +266,7 @@ class ContentLibraryCollectionsTest(ContentLibrariesRestApiTest, OpenEdxEventsTe ENABLED_OPENEDX_EVENTS = [ CONTENT_OBJECT_ASSOCIATIONS_CHANGED.event_type, LIBRARY_COLLECTION_CREATED.event_type, + LIBRARY_COLLECTION_DELETED.event_type, LIBRARY_COLLECTION_UPDATED.event_type, ] @@ -386,6 +389,29 @@ def test_update_library_collection_wrong_library(self): self.col2.key, ) + def test_delete_library_collection(self): + event_receiver = mock.Mock() + LIBRARY_COLLECTION_DELETED.connect(event_receiver) + + authoring_api.delete_collection( + self.lib1.learning_package_id, + self.col1.key, + hard_delete=True, + ) + + assert event_receiver.call_count == 1 + self.assertDictContainsSubset( + { + "signal": LIBRARY_COLLECTION_DELETED, + "sender": None, + "library_collection": LibraryCollectionData( + self.lib1.library_key, + collection_key="COL1", + ), + }, + event_receiver.call_args_list[0].kwargs, + ) + def test_update_library_collection_components(self): assert not list(self.col1.entities.all()) @@ -429,11 +455,11 @@ def test_update_library_collection_components_event(self): assert event_receiver.call_count == 3 self.assertDictContainsSubset( { - "signal": LIBRARY_COLLECTION_UPDATED, + "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, "sender": None, - "library_collection": LibraryCollectionData( - self.lib1.library_key, - collection_key="COL1", + "content_object": ContentObjectChangedData( + object_id=self.lib1_problem_block["id"], + changes=["collections"], ), }, event_receiver.call_args_list[0].kwargs, @@ -443,7 +469,7 @@ def test_update_library_collection_components_event(self): "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, "sender": None, "content_object": ContentObjectChangedData( - object_id=self.lib1_problem_block["id"], + object_id=self.lib1_html_block["id"], changes=["collections"], ), }, @@ -451,11 +477,11 @@ def test_update_library_collection_components_event(self): ) self.assertDictContainsSubset( { - "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, + "signal": LIBRARY_COLLECTION_UPDATED, "sender": None, - "content_object": ContentObjectChangedData( - object_id=self.lib1_html_block["id"], - changes=["collections"], + "library_collection": LibraryCollectionData( + self.lib1.library_key, + collection_key="COL1", ), }, event_receiver.call_args_list[2].kwargs, diff --git a/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py b/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py index 677178bb3b3..d995a2c7968 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py @@ -1,18 +1,15 @@ """ Tests for Learning-Core-based Content Libraries """ -from unittest.mock import Mock, patch +from datetime import datetime, timezone from unittest import skip +from unittest.mock import Mock, patch +from uuid import uuid4 import ddt -from datetime import datetime, timezone -from uuid import uuid4 from django.contrib.auth.models import Group from django.test.client import Client from freezegun import freeze_time -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 ( @@ -21,20 +18,23 @@ CONTENT_LIBRARY_UPDATED, LIBRARY_BLOCK_CREATED, LIBRARY_BLOCK_DELETED, - LIBRARY_BLOCK_UPDATED, + LIBRARY_BLOCK_UPDATED ) from openedx_events.tests.utils import OpenEdxEventsTestMixin +from organizations.models import Organization +from rest_framework.test import APITestCase + +from common.djangoapps.student.tests.factories import UserFactory +from openedx.core.djangoapps.content_libraries.constants import CC_4_BY, COMPLEX, PROBLEM, VIDEO from openedx.core.djangoapps.content_libraries.tests.base import ( - ContentLibrariesRestApiTest, + URL_BLOCK_GET_HANDLER_URL, URL_BLOCK_METADATA_URL, URL_BLOCK_RENDER_VIEW, - URL_BLOCK_GET_HANDLER_URL, URL_BLOCK_XBLOCK_HANDLER, + ContentLibrariesRestApiTest ) -from openedx.core.djangoapps.content_libraries.constants import VIDEO, COMPLEX, PROBLEM, CC_4_BY from openedx.core.djangoapps.xblock import api as xblock_api from openedx.core.djangolib.testing.utils import skip_unless_cms -from common.djangoapps.student.tests.factories import UserFactory @skip_unless_cms @@ -1049,6 +1049,9 @@ def test_library_paste_clipboard(self): self.assertDictContainsEntries(self._get_library_block(paste_data["id"]), { **block_data, "last_draft_created_by": None, + "last_draft_created": paste_data["last_draft_created"], + "created": paste_data["created"], + "modified": paste_data["modified"], "id": f"lb:CL-TEST:test_lib_paste_clipboard:problem:{pasted_block_id}", }) diff --git a/openedx/core/djangoapps/content_libraries/tests/test_runtime.py b/openedx/core/djangoapps/content_libraries/tests/test_runtime.py index 89b8cdefd86..f79808a7ec9 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_runtime.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_runtime.py @@ -3,6 +3,7 @@ """ import json from gettext import GNUTranslations +from django.test import TestCase from completion.test_utils import CompletionWaffleTestMixin from django.db import connections, transaction @@ -24,6 +25,7 @@ from openedx.core.djangoapps.dark_lang.models import DarkLangConfig from openedx.core.djangoapps.xblock import api as xblock_api from openedx.core.djangolib.testing.utils import skip_unless_lms, skip_unless_cms +from openedx.core.lib.xblock_serializer import api as serializer_api from common.djangoapps.student.tests.factories import UserFactory @@ -59,6 +61,86 @@ def setUp(self): ) +@skip_unless_cms +class ContentLibraryOlxTests(ContentLibraryContentTestMixin, TestCase): + """ + Basic test of the Learning-Core-based XBlock serialization-deserialization, using XBlocks in a content library. + """ + + def test_html_round_trip(self): + """ + Test that if we deserialize and serialize an HTMLBlock repeatedly, two things hold true: + + 1. Even if the OLX changes format, the inner content does not change format. + 2. The OLX settles into a stable state after 1 round trip. + + (We are particularly testing HTML, but it would be good to confirm that these principles hold true for + XBlocks in general.) + """ + usage_key = library_api.create_library_block(self.library.key, "html", "roundtrip").usage_key + + # The block's actual HTML has some extraneous spaces and newlines, as well as comment. + # We expect this to be preserved through the round-trips. + block_content = '''\ +
+
+

There is a space on either side of this sentence.

+

\tThere is a tab on either side of this sentence.\t

+

🙃There is an emoji on either side of this sentence.🙂

+

There is nothing on either side of this sentence.

+
+

\t ]]>

+ +
''' + + # The OLX containing the HTML also has some extraneous stuff, which do *not* expect to survive the round-trip. + olx_1 = f'''\ + + ''' + + # Here is what we expect the OLX to settle down to. Notable changes: + # * url_name is added. + # * some_fake_field is gone. + # * The OLX comment is gone. + # * A trailing newline is added at the end of the export. + # DEVS: If you are purposefully tweaking the formatting of the xblock serializer, then it's fine to + # update the value of this variable, as long as: + # 1. the {block_content} remains unchanged, and + # 2. the canonical_olx remains stable through the 2nd round trip. + canonical_olx = ( + f'\n' + ) + + # Save the block to LC, and re-load it. + library_api.set_library_block_olx(usage_key, olx_1) + library_api.publish_changes(self.library.key) + block_saved_1 = xblock_api.load_block(usage_key, self.staff_user) + + # Content should be preserved... + assert block_saved_1.data == block_content + + # ...but the serialized OLX will have changed to match the 'canonical' OLX. + olx_2 = serializer_api.serialize_xblock_to_olx(block_saved_1).olx_str + assert olx_2 == canonical_olx + + # Now, save that OLX back to LC, and re-load it again. + library_api.set_library_block_olx(usage_key, olx_2) + library_api.publish_changes(self.library.key) + block_saved_2 = xblock_api.load_block(usage_key, self.staff_user) + + # Again, content should be preserved... + assert block_saved_2.data == block_saved_1.data == block_content + + # ...and this time, the OLX should have settled too. + olx_3 = serializer_api.serialize_xblock_to_olx(block_saved_2).olx_str + assert olx_3 == olx_2 == canonical_olx + + class ContentLibraryRuntimeTests(ContentLibraryContentTestMixin): """ Basic tests of the Learning-Core-based XBlock runtime using XBlocks in a diff --git a/openedx/core/djangoapps/content_libraries/tests/test_views_collections.py b/openedx/core/djangoapps/content_libraries/tests/test_views_collections.py index bc600759b5b..43c1627c2c7 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_views_collections.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_views_collections.py @@ -17,6 +17,7 @@ URL_PREFIX = '/api/libraries/v2/{lib_key}/' URL_LIB_COLLECTIONS = URL_PREFIX + 'collections/' URL_LIB_COLLECTION = URL_LIB_COLLECTIONS + '{collection_key}/' +URL_LIB_COLLECTION_RESTORE = URL_LIB_COLLECTIONS + '{collection_key}/restore/' URL_LIB_COLLECTION_COMPONENTS = URL_LIB_COLLECTION + 'components/' @@ -330,15 +331,33 @@ def test_update_invalid_library_collection(self): def test_delete_library_collection(self): """ - Test deleting a Content Library Collection - - Note: Currently not implemented and should return a 405 + Test soft-deleting and restoring a Content Library Collection """ resp = self.client.delete( URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.key) ) + assert resp.status_code == 204 - assert resp.status_code == 405 + resp = self.client.get( + URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.key) + ) + assert resp.status_code == 404 + + resp = self.client.post( + URL_LIB_COLLECTION_RESTORE.format(lib_key=self.lib2.library_key, collection_key=self.col3.key) + ) + assert resp.status_code == 204 + + resp = self.client.get( + URL_LIB_COLLECTION.format(lib_key=self.lib2.library_key, collection_key=self.col3.key) + ) + # Check that correct Content Library Collection data retrieved + expected_collection = { + "title": "Collection 3", + "description": "Description for Collection 3", + } + assert resp.status_code == 200 + self.assertDictContainsEntries(resp.data, expected_collection) def test_get_components(self): """ diff --git a/openedx/core/djangoapps/content_libraries/views_collections.py b/openedx/core/djangoapps/content_libraries/views_collections.py index 2f40a178862..b6c1c999ba9 100644 --- a/openedx/core/djangoapps/content_libraries/views_collections.py +++ b/openedx/core/djangoapps/content_libraries/views_collections.py @@ -11,7 +11,7 @@ from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet -from rest_framework.status import HTTP_405_METHOD_NOT_ALLOWED +from rest_framework.status import HTTP_204_NO_CONTENT from opaque_keys.edx.locator import LibraryLocatorV2 from openedx_learning.api import authoring as authoring_api @@ -163,13 +163,31 @@ def partial_update(self, request, *args, **kwargs) -> Response: @convert_exceptions def destroy(self, request, *args, **kwargs) -> Response: """ - Deletes a Collection that belongs to a Content Library - - Note: (currently not allowed) + Soft-deletes a Collection that belongs to a Content Library """ - # TODO: Implement the deletion logic and emit event signal + collection = super().get_object() + assert collection.learning_package_id + authoring_api.delete_collection( + collection.learning_package_id, + collection.key, + hard_delete=False, + ) + return Response(None, status=HTTP_204_NO_CONTENT) - return Response(None, status=HTTP_405_METHOD_NOT_ALLOWED) + @convert_exceptions + @action(detail=True, methods=['post'], url_path='restore', url_name='collection-restore') + def restore(self, request, *args, **kwargs) -> Response: + """ + Restores a soft-deleted Collection that belongs to a Content Library + """ + content_library = self.get_content_library() + assert content_library.learning_package_id + collection_key = kwargs["key"] + authoring_api.restore_collection( + content_library.learning_package_id, + collection_key, + ) + return Response(None, status=HTTP_204_NO_CONTENT) @convert_exceptions @action(detail=True, methods=['delete', 'patch'], url_path='components', url_name='components-update') diff --git a/openedx/core/djangoapps/content_staging/tests/test_clipboard.py b/openedx/core/djangoapps/content_staging/tests/test_clipboard.py index 00c4466b7d4..551f94e90e1 100644 --- a/openedx/core/djangoapps/content_staging/tests/test_clipboard.py +++ b/openedx/core/djangoapps/content_staging/tests/test_clipboard.py @@ -159,7 +159,7 @@ def test_copy_html(self): Sample ]]> - """).lstrip() + """).replace("\n", "") + "\n" # No newlines, expect one trailing newline. # Now if we GET the clipboard again, the GET response should exactly equal the last POST response: assert client.get(CLIPBOARD_ENDPOINT).json() == response_data diff --git a/openedx/core/djangoapps/learner_pathway/__init__.py b/openedx/core/djangoapps/learner_pathway/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/openedx/core/djangoapps/learner_pathway/admin.py b/openedx/core/djangoapps/learner_pathway/admin.py deleted file mode 100644 index f69e12dccdf..00000000000 --- a/openedx/core/djangoapps/learner_pathway/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Admin interface for learner_pathway App. -""" diff --git a/openedx/core/djangoapps/learner_pathway/apps.py b/openedx/core/djangoapps/learner_pathway/apps.py deleted file mode 100644 index 101ffd55701..00000000000 --- a/openedx/core/djangoapps/learner_pathway/apps.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -Configuration for learner_pathway Django app -""" - -from django.apps import AppConfig -from django.utils.translation import gettext_lazy as _ - - -class LearnerPathwayConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'learner_pathway' - verbose_name = _("Learner Pathways") diff --git a/openedx/core/djangoapps/learner_pathway/migrations/__init__.py b/openedx/core/djangoapps/learner_pathway/migrations/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/openedx/core/djangoapps/learner_pathway/models.py b/openedx/core/djangoapps/learner_pathway/models.py deleted file mode 100644 index a03ee95ad3a..00000000000 --- a/openedx/core/djangoapps/learner_pathway/models.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Models for learner_pathway App. -""" diff --git a/openedx/core/djangoapps/learner_pathway/tests/__init__.py b/openedx/core/djangoapps/learner_pathway/tests/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/openedx/core/djangoapps/learner_pathway/views.py b/openedx/core/djangoapps/learner_pathway/views.py deleted file mode 100644 index 8a87b81297d..00000000000 --- a/openedx/core/djangoapps/learner_pathway/views.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Views for learner_pathway App. -""" diff --git a/openedx/core/djangoapps/user_api/accounts/api.py b/openedx/core/djangoapps/user_api/accounts/api.py index 6e7a2111885..6cc466ba003 100644 --- a/openedx/core/djangoapps/user_api/accounts/api.py +++ b/openedx/core/djangoapps/user_api/accounts/api.py @@ -3,7 +3,6 @@ Programmatic integration point for User API Accounts sub-application """ - import datetime import re @@ -152,6 +151,12 @@ def update_account_settings(requesting_user, update, username=None): _validate_email_change(user, update, field_errors) _validate_secondary_email(user, update, field_errors) + if update.get('country', '') in settings.DISABLED_COUNTRIES: + field_errors['country'] = { + 'developer_message': 'Country is disabled for registration', + 'user_message': 'This country cannot be selected for user registration' + } + old_name = _validate_name_change(user_profile, update, field_errors) old_language_proficiencies = _get_old_language_proficiencies_if_updating(user_profile, update) diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py index 81a3158eb5b..5123c4cf41c 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py @@ -3,7 +3,6 @@ Most of the functionality is covered in test_views.py. """ - import datetime import itertools import unicodedata @@ -16,6 +15,7 @@ from django.http import HttpResponse from django.test import TestCase from django.test.client import RequestFactory +from django.test.utils import override_settings from django.urls import reverse from pytz import UTC from social_django.models import UserSocialAuth @@ -82,7 +82,8 @@ def create_account(self, username, password, email): @skip_unless_lms @ddt.ddt -@patch('common.djangoapps.student.views.management.render_to_response', Mock(side_effect=mock_render_to_response, autospec=True)) # lint-amnesty, pylint: disable=line-too-long +@patch('common.djangoapps.student.views.management.render_to_response', + Mock(side_effect=mock_render_to_response, autospec=True)) # lint-amnesty, pylint: disable=line-too-long class TestAccountApi(UserSettingsEventTestMixin, EmailTemplateTagMixin, CreateAccountMixin, RetirementTestCase): """ These tests specifically cover the parts of the API methods that are not covered by test_views.py. @@ -205,7 +206,7 @@ def test_add_social_links(self): account_settings = get_account_settings(self.default_request)[0] assert account_settings['social_links'] == \ - sorted((original_social_links + extra_social_links), key=(lambda s: s['platform'])) + sorted((original_social_links + extra_social_links), key=(lambda s: s['platform'])) def test_replace_social_links(self): original_facebook_link = dict(platform="facebook", social_link="https://www.facebook.com/myself") @@ -306,7 +307,7 @@ def test_update_validation_error_for_enterprise( with pytest.raises(AccountValidationError) as validation_error: update_account_settings(self.user, update_data) field_errors = validation_error.value.field_errors - assert 'This field is not editable via this API' ==\ + assert 'This field is not editable via this API' == \ field_errors[field_name_value[0]]['developer_message'] else: update_account_settings(self.user, update_data) @@ -424,8 +425,8 @@ def test_name_update_does_not_require_idv(self, has_passable_cert, enrolled_in_v """ Test that the user can change their name if change does not require IDV. """ - with patch('openedx.core.djangoapps.user_api.accounts.api.get_certificates_for_user') as mock_get_certs,\ - patch('openedx.core.djangoapps.user_api.accounts.api.get_verified_enrollments') as \ + with patch('openedx.core.djangoapps.user_api.accounts.api.get_certificates_for_user') as mock_get_certs, \ + patch('openedx.core.djangoapps.user_api.accounts.api.get_verified_enrollments') as \ mock_get_verified_enrollments: mock_get_certs.return_value = ( [{'status': CertificateStatuses.downloadable}] if @@ -439,7 +440,8 @@ def test_name_update_does_not_require_idv(self, has_passable_cert, enrolled_in_v assert account_settings['name'] == 'New Name' @patch('django.core.mail.EmailMultiAlternatives.send') - @patch('common.djangoapps.student.views.management.render_to_string', Mock(side_effect=mock_render_to_string, autospec=True)) # lint-amnesty, pylint: disable=line-too-long + @patch('common.djangoapps.student.views.management.render_to_string', + Mock(side_effect=mock_render_to_string, autospec=True)) def test_update_sending_email_fails(self, send_mail): """Test what happens if all validation checks pass, but sending the email for email change fails.""" send_mail.side_effect = [Exception, None] @@ -514,6 +516,7 @@ def test_language_proficiency_eventing(self): """ Test that eventing of language proficiencies, which happens update_account_settings method, behaves correctly. """ + def verify_event_emitted(new_value, old_value): """ Confirm that the user setting event was properly emitted @@ -571,6 +574,20 @@ def test_change_country_removes_state(self): assert account_settings['country'] is None assert account_settings['state'] is None + @override_settings(DISABLED_COUNTRIES=['KP']) + def test_change_to_disabled_country(self): + """ + Test that changing the country to a disabled country is not allowed + """ + # First set the country and state + update_account_settings(self.user, {"country": UserProfile.COUNTRY_WITH_STATES, "state": "MA"}) + account_settings = get_account_settings(self.default_request)[0] + assert account_settings['country'] == UserProfile.COUNTRY_WITH_STATES + assert account_settings['state'] == 'MA' + + with self.assertRaises(AccountValidationError): + update_account_settings(self.user, {"country": "KP"}) + def test_get_name_validation_error_too_long(self): """ Test validation error when the name is too long. diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_settings_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_settings_views.py index 874497664d9..badee6e8755 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_settings_views.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_settings_views.py @@ -9,7 +9,7 @@ from django.test import TestCase from django.test.utils import override_settings from django.urls import reverse -from edx_rest_api_client import exceptions +from requests import exceptions from edx_toggles.toggles.testutils import override_waffle_flag from lms.djangoapps.commerce.models import CommerceConfiguration @@ -210,7 +210,7 @@ def test_commerce_order_detail(self): assert order_detail[i] == expected def test_commerce_order_detail_exception(self): - with mock_get_orders(exception=exceptions.HttpNotFoundError): + with mock_get_orders(exception=exceptions.HTTPError): order_detail = get_user_orders(self.user) assert not order_detail diff --git a/openedx/core/djangoapps/user_api/tests/test_views.py b/openedx/core/djangoapps/user_api/tests/test_views.py index 981cc52dfd8..75740cf5d2b 100644 --- a/openedx/core/djangoapps/user_api/tests/test_views.py +++ b/openedx/core/djangoapps/user_api/tests/test_views.py @@ -635,7 +635,7 @@ def _assert_time_zone_is_valid(self, time_zone_info): assert time_zone_info['description'] == get_display_time_zone(time_zone_name) # The time zones count may need to change each time we upgrade pytz - @ddt.data((ALL_TIME_ZONES_URI, 433), + @ddt.data((ALL_TIME_ZONES_URI, 432), (COUNTRY_TIME_ZONES_URI, 23)) @ddt.unpack def test_get_basic(self, country_uri, expected_count): diff --git a/openedx/core/djangoapps/user_authn/config/waffle.py b/openedx/core/djangoapps/user_authn/config/waffle.py index c34c81e1d06..3cbb0cb2e18 100644 --- a/openedx/core/djangoapps/user_authn/config/waffle.py +++ b/openedx/core/djangoapps/user_authn/config/waffle.py @@ -2,7 +2,6 @@ Waffle flags and switches for user authn. """ - from edx_toggles.toggles import WaffleSwitch _WAFFLE_NAMESPACE = 'user_authn' diff --git a/openedx/core/djangoapps/user_authn/views/registration_form.py b/openedx/core/djangoapps/user_authn/views/registration_form.py index 1d1089ce021..7a0207f8b93 100644 --- a/openedx/core/djangoapps/user_authn/views/registration_form.py +++ b/openedx/core/djangoapps/user_authn/views/registration_form.py @@ -23,6 +23,7 @@ from openedx.core.djangoapps.user_api import accounts from openedx.core.djangoapps.user_api.helpers import FormDescription from openedx.core.djangoapps.user_authn.utils import check_pwned_password, is_registration_api_v1 as is_api_v1 +from openedx.core.djangoapps.user_authn.views.utils import remove_disabled_country_from_list from openedx.core.djangolib.markup import HTML, Text from openedx.features.enterprise_support.api import enterprise_customer_for_request from common.djangoapps.student.models import ( @@ -297,6 +298,15 @@ def cleaned_extended_profile(self): if key in self.extended_profile_fields and value is not None } + def clean_country(self): + """ + Check if the user's country is in the embargoed countries list. + """ + country = self.cleaned_data.get("country") + if country in settings.DISABLED_COUNTRIES: + raise ValidationError(_("Registration from this country is not allowed due to restrictions.")) + return self.cleaned_data.get("country") + def get_registration_extension_form(*args, **kwargs): """ @@ -686,7 +696,7 @@ def _add_marketing_emails_opt_in_field(self, form_desc, required=False): """ opt_in_label = _( 'I agree that {platform_name} may send me marketing messages.').format( - platform_name=configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), + platform_name=configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), ) form_desc.add_field( @@ -974,7 +984,7 @@ def _add_country_field(self, form_desc, required=True): label=country_label, instructions=country_instructions, field_type="select", - options=list(countries), + options=list(remove_disabled_country_from_list(dict(countries)).items()), include_default_option=True, required=required, error_messages={ diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_register.py b/openedx/core/djangoapps/user_authn/views/tests/test_register.py index 02d5a72074f..16f7da8010d 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_register.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_register.py @@ -65,6 +65,7 @@ password_validators_instruction_texts, password_validators_restrictions ) + ENABLE_AUTO_GENERATED_USERNAME = settings.FEATURES.copy() ENABLE_AUTO_GENERATED_USERNAME['ENABLE_AUTO_GENERATED_USERNAME'] = True @@ -1556,7 +1557,7 @@ def test_activation_email(self): assert len(mail.outbox) == 1 sent_email = mail.outbox[0] assert sent_email.to == [self.EMAIL] - assert sent_email.subject ==\ + assert sent_email.subject == \ f'Action Required: Activate your {settings.PLATFORM_NAME} account' assert f'high-quality {settings.PLATFORM_NAME} courses' in sent_email.body @@ -2468,6 +2469,31 @@ def test_register_error_with_pwned_password(self, emit): }) assert response.status_code == 400 + @override_settings(DISABLED_COUNTRIES=['KP']) + def test_register_with_disabled_country(self): + """ + Test case to check user registration is forbidden when registration is disabled for a country + """ + response = self.client.post(self.url, { + "email": self.EMAIL, + "name": self.NAME, + "username": self.USERNAME, + "password": self.PASSWORD, + "honor_code": "true", + "country": "KP", + }) + assert response.status_code == 400 + response_json = json.loads(response.content.decode('utf-8')) + self.assertDictEqual( + response_json, + {'country': + [ + { + 'user_message': 'Registration from this country is not allowed due to restrictions.' + } + ], 'error_code': 'validation-error'} + ) + @httpretty.activate @ddt.ddt @@ -2575,6 +2601,24 @@ def test_success(self): self._verify_user_existence(user_exists=True, social_link_exists=True, user_is_active=False) + @override_settings(DISABLED_COUNTRIES=['US']) + def test_with_disabled_country(self): + """ + Test case to check user registration is forbidden when registration is disabled for a country + """ + self._verify_user_existence(user_exists=False, social_link_exists=False) + self._setup_provider_response(success=True) + response = self.client.post(self.url, self.data()) + assert response.status_code == 400 + assert response.json() == { + 'country': [ + { + 'user_message': 'Registration from this country is not allowed due to restrictions.' + } + ], 'error_code': 'validation-error' + } + self._verify_user_existence(user_exists=False, social_link_exists=False, user_is_active=False) + def test_unlinked_active_user(self): user = UserFactory() response = self.client.post(self.url, self.data(user)) diff --git a/openedx/core/djangoapps/user_authn/views/utils.py b/openedx/core/djangoapps/user_authn/views/utils.py index 9b4054bd403..b9fb096621f 100644 --- a/openedx/core/djangoapps/user_authn/views/utils.py +++ b/openedx/core/djangoapps/user_authn/views/utils.py @@ -3,6 +3,8 @@ """ import logging import re +from typing import Dict + from django.conf import settings from django.contrib import messages from django.utils.translation import gettext as _ @@ -177,3 +179,18 @@ def get_auto_generated_username(data): # We generate the username regardless of whether the name is empty or invalid. We do this # because the name validations occur later, ensuring that users cannot create an account without a valid name. return f"{username_prefix}_{username_suffix}" if username_prefix else username_suffix + + +def remove_disabled_country_from_list(countries: Dict) -> Dict: + """ + Remove disabled countries from the list of countries. + + Args: + - countries (dict): List of countries. + + Returns: + - dict: Dict of countries with disabled countries removed. + """ + for country_code in settings.DISABLED_COUNTRIES: + del countries[country_code] + return countries diff --git a/openedx/core/lib/xblock_serializer/block_serializer.py b/openedx/core/lib/xblock_serializer/block_serializer.py index 966380f2506..f12bf5336af 100644 --- a/openedx/core/lib/xblock_serializer/block_serializer.py +++ b/openedx/core/lib/xblock_serializer/block_serializer.py @@ -133,7 +133,7 @@ def _serialize_html_block(self, block) -> etree.Element: # Escape any CDATA special chars escaped_block_data = block.data.replace("]]>", "]]>") - olx_node.text = etree.CDATA("\n" + escaped_block_data + "\n") + olx_node.text = etree.CDATA(escaped_block_data) return olx_node diff --git a/package-lock.json b/package-lock.json index 87c75af6904..82a6611d3ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,7 +67,7 @@ "style-loader": "0.18.2", "svg-inline-loader": "0.8.2", "uglify-js": "2.7.0", - "underscore": "1.12.1", + "underscore": "1.13.1", "underscore.string": "3.3.6", "webpack": "^5.90.3", "webpack-bundle-tracker": "0.4.3", @@ -24564,9 +24564,9 @@ } }, "node_modules/underscore": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", - "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", + "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", "license": "MIT" }, "node_modules/underscore.string": { diff --git a/package.json b/package.json index 3bd526c3b48..1f48500e6e2 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "style-loader": "0.18.2", "svg-inline-loader": "0.8.2", "uglify-js": "2.7.0", - "underscore": "1.12.1", + "underscore": "1.13.1", "underscore.string": "3.3.6", "webpack": "^5.90.3", "webpack-bundle-tracker": "0.4.3", diff --git a/requirements/constraints.txt b/requirements/constraints.txt index fffc9ac163b..5ae1118f09b 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -26,7 +26,7 @@ celery>=5.2.2,<6.0.0 # The team that owns this package will manually bump this package rather than having it pulled in automatically. # This is to allow them to better control its deployment and to do it in a process that works better # for them. -edx-enterprise==4.25.13 +edx-enterprise==4.25.19 # Stay on LTS version, remove once this is added to common constraint Django<5.0 @@ -93,7 +93,7 @@ libsass==0.10.0 click==8.1.6 # pinning this version to avoid updates while the library is being developed -openedx-learning==0.11.5 +openedx-learning==0.13.1 # Open AI version 1.0.0 dropped support for openai.ChatCompletion which is currently in use in enterprise. openai<=0.28.1 diff --git a/requirements/edx-sandbox/base.txt b/requirements/edx-sandbox/base.txt index 4a7b0c0a7d3..1d94a4649a5 100644 --- a/requirements/edx-sandbox/base.txt +++ b/requirements/edx-sandbox/base.txt @@ -4,7 +4,7 @@ # # make upgrade # -cffi==1.17.0 +cffi==1.17.1 # via cryptography chem==1.3.0 # via -r requirements/edx-sandbox/base.in @@ -14,17 +14,17 @@ click==8.1.6 # nltk codejail-includes==1.0.0 # via -r requirements/edx-sandbox/base.in -contourpy==1.2.1 +contourpy==1.3.0 # via matplotlib -cryptography==43.0.0 +cryptography==43.0.1 # via -r requirements/edx-sandbox/base.in cycler==0.12.1 # via matplotlib -fonttools==4.53.1 +fonttools==4.54.1 # via matplotlib joblib==1.4.2 # via nltk -kiwisolver==1.4.5 +kiwisolver==1.4.7 # via matplotlib lxml==4.9.4 # via @@ -61,7 +61,7 @@ pillow==10.4.0 # via matplotlib pycparser==2.22 # via cffi -pyparsing==3.1.2 +pyparsing==3.1.4 # via # -r requirements/edx-sandbox/base.in # chem @@ -71,9 +71,9 @@ python-dateutil==2.9.0.post0 # via matplotlib random2==1.0.2 # via -r requirements/edx-sandbox/base.in -regex==2024.7.24 +regex==2024.9.11 # via nltk -scipy==1.14.0 +scipy==1.14.1 # via # -r requirements/edx-sandbox/base.in # chem @@ -82,7 +82,7 @@ six==1.16.0 # via # codejail-includes # python-dateutil -sympy==1.13.2 +sympy==1.13.3 # via # -r requirements/edx-sandbox/base.in # openedx-calc diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 0b246816f55..1cc1b1bcdd1 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -10,7 +10,7 @@ acid-xblock==0.3.1 # via -r requirements/edx/kernel.in aiohappyeyeballs==2.4.0 # via aiohttp -aiohttp==3.10.5 +aiohttp==3.10.6 # via # geoip2 # openai @@ -58,7 +58,7 @@ bcrypt==4.2.0 # via paramiko beautifulsoup4==4.12.3 # via pynliner -billiard==4.2.0 +billiard==4.2.1 # via celery bleach[css]==6.1.0 # via @@ -70,13 +70,13 @@ bleach[css]==6.1.0 # xblock-poll boto==2.49.0 # via -r requirements/edx/kernel.in -boto3==1.35.1 +boto3==1.35.27 # via # -r requirements/edx/kernel.in # django-ses # fs-s3fs # ora2 -botocore==1.35.1 +botocore==1.35.27 # via # -r requirements/edx/kernel.in # boto3 @@ -99,14 +99,14 @@ celery==5.4.0 # edx-enterprise # event-tracking # openedx-learning -certifi==2024.7.4 +certifi==2024.8.30 # via # -r requirements/edx/paver.txt # elasticsearch # py2neo # requests # snowflake-connector-python -cffi==1.17.0 +cffi==1.17.1 # via # cryptography # pynacl @@ -146,7 +146,7 @@ codejail-includes==1.0.0 # via -r requirements/edx/kernel.in crowdsourcehinter-xblock==0.7 # via -r requirements/edx/bundled.in -cryptography==42.0.8 +cryptography==43.0.1 # via # -r requirements/edx/kernel.in # django-fernet-fields-v2 @@ -168,7 +168,7 @@ defusedxml==0.7.1 # ora2 # python3-openid # social-auth-core -django==4.2.15 +django==4.2.16 # via # -c requirements/edx/../common_constraints.txt # -c requirements/edx/../constraints.txt @@ -285,7 +285,7 @@ django-js-asset==2.2.0 # via django-mptt django-method-override==1.0.4 # via -r requirements/edx/kernel.in -django-model-utils==4.5.1 +django-model-utils==5.0.0 # via # -r requirements/edx/kernel.in # django-user-tasks @@ -316,7 +316,7 @@ django-oauth-toolkit==1.7.1 # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in # edx-enterprise -django-object-actions==4.2.0 +django-object-actions==4.3.0 # via edx-enterprise django-pipeline==3.1.0 # via -r requirements/edx/kernel.in @@ -328,7 +328,7 @@ django-sekizai==4.1.0 # via # -r requirements/edx/kernel.in # openedx-django-wiki -django-ses==4.1.0 +django-ses==4.1.1 # via -r requirements/edx/bundled.in django-simple-history==3.4.0 # via @@ -391,7 +391,7 @@ dnspython==2.6.1 # via # -r requirements/edx/paver.txt # pymongo -done-xblock==2.3.0 +done-xblock==2.4.0 # via -r requirements/edx/bundled.in drf-jwt==1.19.2 # via edx-drf-extensions @@ -403,11 +403,11 @@ drf-yasg==1.21.7 # edx-api-doc-tools edx-ace==1.11.2 # via -r requirements/edx/kernel.in -edx-api-doc-tools==1.8.0 +edx-api-doc-tools==2.0.0 # via # -r requirements/edx/kernel.in # edx-name-affirmation -edx-auth-backends==4.3.0 +edx-auth-backends==4.4.0 # via -r requirements/edx/kernel.in edx-braze-client==0.2.5 # via @@ -429,7 +429,7 @@ edx-celeryutils==1.3.0 # super-csv edx-codejail==3.4.1 # via -r requirements/edx/kernel.in -edx-completion==4.6.7 +edx-completion==4.7.1 # via -r requirements/edx/kernel.in edx-django-release-util==1.4.0 # via @@ -455,7 +455,7 @@ edx-django-utils==5.15.0 # openedx-events # ora2 # super-csv -edx-drf-extensions==10.3.0 +edx-drf-extensions==10.4.0 # via # -r requirements/edx/kernel.in # edx-completion @@ -467,7 +467,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.13 +edx-enterprise==4.25.19 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in @@ -482,7 +482,7 @@ edx-i18n-tools==1.5.0 # ora2 edx-milestones==0.6.0 # via -r requirements/edx/kernel.in -edx-name-affirmation==2.4.1 +edx-name-affirmation==2.4.2 # via -r requirements/edx/kernel.in edx-opaque-keys[django]==2.11.0 # via @@ -506,9 +506,9 @@ edx-proctoring==4.18.1 # via # -r requirements/edx/kernel.in # edx-proctoring-proctortrack -edx-rbac==1.9.0 +edx-rbac==1.10.0 # via edx-enterprise -edx-rest-api-client==5.7.1 +edx-rest-api-client==6.0.0 # via # -r requirements/edx/kernel.in # edx-enterprise @@ -517,7 +517,7 @@ edx-search==4.0.0 # via -r requirements/edx/kernel.in edx-sga==0.25.0 # via -r requirements/edx/bundled.in -edx-submissions==3.7.7 +edx-submissions==3.8.0 # via # -r requirements/edx/kernel.in # ora2 @@ -541,7 +541,7 @@ edx-when==2.5.0 # via # -r requirements/edx/kernel.in # edx-proctoring -edxval==2.5.0 +edxval==2.6.0 # via -r requirements/edx/kernel.in elasticsearch==7.13.4 # via @@ -558,9 +558,9 @@ event-tracking==3.0.0 # edx-completion # edx-proctoring # edx-search -fastavro==1.9.5 +fastavro==1.9.7 # via openedx-events -filelock==3.15.4 +filelock==3.16.1 # via snowflake-connector-python firebase-admin==6.5.0 # via edx-ace @@ -584,16 +584,16 @@ geoip2==4.8.0 # via -r requirements/edx/kernel.in glob2==0.7 # via -r requirements/edx/kernel.in -google-api-core[grpc]==2.19.1 +google-api-core[grpc]==2.20.0 # via # firebase-admin # google-api-python-client # google-cloud-core # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.141.0 +google-api-python-client==2.147.0 # via firebase-admin -google-auth==2.34.0 +google-auth==2.35.0 # via # google-api-core # google-api-python-client @@ -607,25 +607,25 @@ google-cloud-core==2.4.1 # via # google-cloud-firestore # google-cloud-storage -google-cloud-firestore==2.17.2 +google-cloud-firestore==2.19.0 # via firebase-admin google-cloud-storage==2.18.2 # via firebase-admin -google-crc32c==1.5.0 +google-crc32c==1.6.0 # via # google-cloud-storage # google-resumable-media google-resumable-media==2.7.2 # via google-cloud-storage -googleapis-common-protos==1.63.2 +googleapis-common-protos==1.65.0 # via # google-api-core # grpcio-status -grpcio==1.65.5 +grpcio==1.66.1 # via # google-api-core # grpcio-status -grpcio-status==1.65.5 +grpcio-status==1.66.1 # via google-api-core gunicorn==23.0.0 # via -r requirements/edx/kernel.in @@ -641,14 +641,14 @@ httplib2==0.22.0 # google-auth-httplib2 icalendar==5.0.13 # via -r requirements/edx/kernel.in -idna==3.7 +idna==3.10 # via # -r requirements/edx/paver.txt # optimizely-sdk # requests # snowflake-connector-python # yarl -importlib-metadata==8.3.0 +importlib-metadata==8.5.0 # via -r requirements/edx/kernel.in inflection==0.5.1 # via @@ -668,7 +668,7 @@ jmespath==1.0.1 # botocore joblib==1.4.2 # via nltk -jsondiff==2.2.0 +jsondiff==2.2.1 # via edx-enterprise jsonfield==3.1.0 # via @@ -689,7 +689,7 @@ jwcrypto==1.5.6 # via # django-oauth-toolkit # pylti1p3 -kombu==5.4.0 +kombu==5.4.2 # via celery laboratory==1.0.2 # via -r requirements/edx/kernel.in @@ -747,23 +747,23 @@ markupsafe==2.1.5 # xblock maxminddb==2.6.2 # via geoip2 -meilisearch==0.31.4 +meilisearch==0.31.5 # via -r requirements/edx/kernel.in mock==5.1.0 # via -r requirements/edx/paver.txt -mongoengine==0.28.2 +mongoengine==0.29.1 # via -r requirements/edx/kernel.in monotonic==1.6 # via # analytics-python # py2neo -more-itertools==10.4.0 +more-itertools==10.5.0 # via cssutils mpmath==1.3.0 # via sympy -msgpack==1.0.8 +msgpack==1.1.0 # via cachecontrol -multidict==6.0.5 +multidict==6.1.0 # via # aiohttp # yarl @@ -799,11 +799,11 @@ openai==0.28.1 # via # -c requirements/edx/../constraints.txt # edx-enterprise -openedx-atlas==0.6.1 +openedx-atlas==0.6.2 # via -r requirements/edx/kernel.in openedx-calc==3.1.0 # via -r requirements/edx/kernel.in -openedx-django-pyfs==3.6.0 +openedx-django-pyfs==3.7.0 # via # lti-consumer-xblock # xblock @@ -811,7 +811,7 @@ openedx-django-require==2.1.0 # via -r requirements/edx/kernel.in openedx-django-wiki==2.1.0 # via -r requirements/edx/kernel.in -openedx-events==9.14.0 +openedx-events==9.14.1 # via # -r requirements/edx/kernel.in # edx-enterprise @@ -819,12 +819,12 @@ openedx-events==9.14.0 # edx-event-bus-redis # event-tracking # ora2 -openedx-filters==1.9.0 +openedx-filters==1.10.0 # via # -r requirements/edx/kernel.in # lti-consumer-xblock # ora2 -openedx-learning==0.11.5 +openedx-learning==0.13.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in @@ -844,7 +844,7 @@ packaging==24.1 # snowflake-connector-python pansi==2020.7.3 # via py2neo -paramiko==3.4.1 +paramiko==3.5.0 # via edx-enterprise path==16.11.0 # via @@ -860,7 +860,7 @@ path-py==12.5.0 # staff-graded-xblock paver==1.3.4 # via -r requirements/edx/paver.txt -pbr==6.0.0 +pbr==6.1.0 # via # -r requirements/edx/paver.txt # stevedore @@ -874,17 +874,17 @@ pillow==10.4.0 # edx-enterprise # edx-organizations # edxval -platformdirs==4.2.2 +platformdirs==4.3.6 # via snowflake-connector-python polib==1.2.0 # via edx-i18n-tools -prompt-toolkit==3.0.47 +prompt-toolkit==3.0.48 # via click-repl proto-plus==1.24.0 # via # google-api-core # google-cloud-firestore -protobuf==5.27.3 +protobuf==5.28.2 # via # google-api-core # google-cloud-firestore @@ -899,12 +899,12 @@ py2neo @ https://github.com/overhangio/py2neo/releases/download/2021.2.3/py2neo- # via # -c requirements/edx/../constraints.txt # -r requirements/edx/bundled.in -pyasn1==0.6.0 +pyasn1==0.6.1 # via # pgpy # pyasn1-modules # rsa -pyasn1-modules==0.4.0 +pyasn1-modules==0.4.1 # via google-auth pycountry==24.6.1 # via -r requirements/edx/kernel.in @@ -916,9 +916,9 @@ pycryptodomex==3.20.0 # edx-proctoring # lti-consumer-xblock # pyjwkest -pydantic==2.8.2 +pydantic==2.9.2 # via camel-converter -pydantic-core==2.20.1 +pydantic-core==2.23.4 # via pydantic pygments==2.18.0 # via @@ -966,7 +966,7 @@ pyopenssl==24.2.1 # via # optimizely-sdk # snowflake-connector-python -pyparsing==3.1.2 +pyparsing==3.1.4 # via # chem # httplib2 @@ -1004,10 +1004,9 @@ python3-openid==3.2.0 ; python_version >= "3" # social-auth-core python3-saml==1.16.0 # via -r requirements/edx/kernel.in -pytz==2024.1 +pytz==2024.2 # via # -r requirements/edx/kernel.in - # django-ses # djangorestframework # drf-yasg # edx-completion @@ -1037,7 +1036,7 @@ pyyaml==6.0.2 # xblock random2==1.0.2 # via -r requirements/edx/kernel.in -recommender-xblock==2.2.0 +recommender-xblock==2.2.1 # via -r requirements/edx/bundled.in redis==5.0.8 # via @@ -1047,7 +1046,7 @@ referencing==0.35.1 # via # jsonschema # jsonschema-specifications -regex==2024.7.24 +regex==2024.9.11 # via nltk requests==2.32.3 # via @@ -1086,7 +1085,7 @@ rpds-py==0.20.0 # referencing rsa==4.9 # via google-auth -rules==3.4 +rules==3.5 # via # -r requirements/edx/kernel.in # edx-enterprise @@ -1096,7 +1095,7 @@ s3transfer==0.10.2 # via boto3 sailthru-client==2.2.3 # via edx-ace -scipy==1.14.0 +scipy==1.14.1 # via # chem # openedx-calc @@ -1144,8 +1143,7 @@ slumber==0.7.1 # -r requirements/edx/kernel.in # edx-bulk-grades # edx-enterprise - # edx-rest-api-client -snowflake-connector-python==3.12.0 +snowflake-connector-python==3.12.2 # via edx-enterprise social-auth-app-django==5.4.1 # via @@ -1157,7 +1155,7 @@ social-auth-core==4.5.4 # -r requirements/edx/kernel.in # edx-auth-backends # social-auth-app-django -sorl-thumbnail==12.10.0 +sorl-thumbnail==12.11.0 # via # -r requirements/edx/kernel.in # openedx-django-wiki @@ -1171,7 +1169,7 @@ sqlparse==0.5.1 # via django staff-graded-xblock==2.3.0 # via -r requirements/edx/bundled.in -stevedore==5.2.0 +stevedore==5.3.0 # via # -r requirements/edx/kernel.in # -r requirements/edx/paver.txt @@ -1182,7 +1180,7 @@ stevedore==5.2.0 # edx-opaque-keys super-csv==3.2.0 # via edx-bulk-grades -sympy==1.13.2 +sympy==1.13.3 # via openedx-calc testfixtures==8.3.0 # via edx-enterprise @@ -1206,8 +1204,10 @@ typing-extensions==4.12.2 # pydantic-core # pylti1p3 # snowflake-connector-python -tzdata==2024.1 - # via celery +tzdata==2024.2 + # via + # celery + # kombu unicodecsv==0.14.1 # via # -r requirements/edx/kernel.in @@ -1217,7 +1217,7 @@ uritemplate==4.1.1 # drf-spectacular # drf-yasg # google-api-python-client -urllib3==1.26.19 +urllib3==1.26.20 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/paver.txt @@ -1236,7 +1236,7 @@ voluptuous==0.15.2 # via ora2 walrus==0.9.4 # via edx-event-bus-redis -watchdog==4.0.2 +watchdog==5.0.2 # via -r requirements/edx/paver.txt wcwidth==0.2.13 # via prompt-toolkit @@ -1290,9 +1290,9 @@ xmlsec==1.3.13 # python3-saml xss-utils==0.6.0 # via -r requirements/edx/kernel.in -yarl==1.9.4 +yarl==1.12.1 # via aiohttp -zipp==3.20.0 +zipp==3.20.2 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/edx/coverage.txt b/requirements/edx/coverage.txt index a004eeeb9ff..a1faf5e7402 100644 --- a/requirements/edx/coverage.txt +++ b/requirements/edx/coverage.txt @@ -8,7 +8,7 @@ chardet==5.2.0 # via diff-cover coverage==7.6.1 # via -r requirements/edx/coverage.in -diff-cover==9.1.1 +diff-cover==9.2.0 # via -r requirements/edx/coverage.in jinja2==3.1.4 # via diff-cover diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index e16f83b049f..aa2e3e62e81 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -21,7 +21,7 @@ aiohappyeyeballs==2.4.0 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # aiohttp -aiohttp==3.10.5 +aiohttp==3.10.6 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -60,7 +60,7 @@ annotated-types==0.7.0 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # pydantic -anyio==4.4.0 +anyio==4.6.0 # via # -r requirements/edx/testing.txt # starlette @@ -121,7 +121,7 @@ beautifulsoup4==4.12.3 # -r requirements/edx/testing.txt # pydata-sphinx-theme # pynliner -billiard==4.2.0 +billiard==4.2.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -140,14 +140,14 @@ boto==2.49.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -boto3==1.35.1 +boto3==1.35.27 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # django-ses # fs-s3fs # ora2 -botocore==1.35.1 +botocore==1.35.27 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -157,7 +157,7 @@ bridgekeeper==0.9 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -build==1.2.1 +build==1.2.2 # via # -r requirements/edx/../pip-tools.txt # pip-tools @@ -188,7 +188,7 @@ celery==5.4.0 # edx-enterprise # event-tracking # openedx-learning -certifi==2024.7.4 +certifi==2024.8.30 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -196,7 +196,7 @@ certifi==2024.7.4 # py2neo # requests # snowflake-connector-python -cffi==1.17.0 +cffi==1.17.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -286,7 +286,7 @@ crowdsourcehinter-xblock==0.7 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -cryptography==42.0.8 +cryptography==43.0.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -311,7 +311,7 @@ cssutils==2.11.1 # pynliner ddt==1.7.2 # via -r requirements/edx/testing.txt -deepmerge==1.1.1 +deepmerge==2.0 # via # -r requirements/edx/doc.txt # sphinxcontrib-openapi @@ -323,7 +323,7 @@ defusedxml==0.7.1 # ora2 # python3-openid # social-auth-core -diff-cover==9.1.1 +diff-cover==9.2.0 # via -r requirements/edx/testing.txt dill==0.3.8 # via @@ -333,7 +333,7 @@ distlib==0.3.8 # via # -r requirements/edx/testing.txt # virtualenv -django==4.2.15 +django==4.2.16 # via # -c requirements/edx/../common_constraints.txt # -c requirements/edx/../constraints.txt @@ -482,7 +482,7 @@ django-method-override==1.0.4 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -django-model-utils==4.5.1 +django-model-utils==5.0.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -521,7 +521,7 @@ django-oauth-toolkit==1.7.1 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-enterprise -django-object-actions==4.2.0 +django-object-actions==4.3.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -544,7 +544,7 @@ django-sekizai==4.1.0 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # openedx-django-wiki -django-ses==4.1.0 +django-ses==4.1.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -576,7 +576,7 @@ django-stubs==1.16.0 # -c requirements/edx/../constraints.txt # -r requirements/edx/development.in # djangorestframework-stubs -django-stubs-ext==5.0.4 +django-stubs-ext==5.1.0 # via django-stubs django-user-tasks==3.2.0 # via @@ -638,7 +638,7 @@ docutils==0.21.2 # pydata-sphinx-theme # sphinx # sphinx-mdinclude -done-xblock==2.3.0 +done-xblock==2.4.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -661,12 +661,12 @@ edx-ace==1.11.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -edx-api-doc-tools==1.8.0 +edx-api-doc-tools==2.0.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-name-affirmation -edx-auth-backends==4.3.0 +edx-auth-backends==4.4.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -696,7 +696,7 @@ edx-codejail==3.4.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -edx-completion==4.6.7 +edx-completion==4.7.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -728,7 +728,7 @@ edx-django-utils==5.15.0 # openedx-events # ora2 # super-csv -edx-drf-extensions==10.3.0 +edx-drf-extensions==10.4.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -741,7 +741,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.13 +edx-enterprise==4.25.19 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt @@ -760,13 +760,13 @@ edx-i18n-tools==1.5.0 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # ora2 -edx-lint==5.3.7 +edx-lint==5.4.0 # via -r requirements/edx/testing.txt edx-milestones==0.6.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -edx-name-affirmation==2.4.1 +edx-name-affirmation==2.4.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -795,12 +795,12 @@ edx-proctoring==4.18.1 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-proctoring-proctortrack -edx-rbac==1.9.0 +edx-rbac==1.10.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-enterprise -edx-rest-api-client==5.7.1 +edx-rest-api-client==6.0.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -814,7 +814,7 @@ edx-sga==0.25.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -edx-submissions==3.7.7 +edx-submissions==3.8.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -846,7 +846,7 @@ edx-when==2.5.0 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-proctoring -edxval==2.5.0 +edxval==2.6.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -879,20 +879,20 @@ execnet==2.1.1 # pytest-xdist factory-boy==3.3.1 # via -r requirements/edx/testing.txt -faker==27.0.0 +faker==30.0.0 # via # -r requirements/edx/testing.txt # factory-boy -fastapi==0.112.1 +fastapi==0.115.0 # via # -r requirements/edx/testing.txt # pact-python -fastavro==1.9.5 +fastavro==1.9.7 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # openedx-events -filelock==3.15.4 +filelock==3.16.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -943,7 +943,7 @@ glob2==0.7 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -google-api-core[grpc]==2.19.1 +google-api-core[grpc]==2.20.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -952,12 +952,12 @@ google-api-core[grpc]==2.19.1 # google-cloud-core # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.141.0 +google-api-python-client==2.147.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # firebase-admin -google-auth==2.34.0 +google-auth==2.35.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -978,7 +978,7 @@ google-cloud-core==2.4.1 # -r requirements/edx/testing.txt # google-cloud-firestore # google-cloud-storage -google-cloud-firestore==2.17.2 +google-cloud-firestore==2.19.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -988,7 +988,7 @@ google-cloud-storage==2.18.2 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # firebase-admin -google-crc32c==1.5.0 +google-crc32c==1.6.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -999,7 +999,7 @@ google-resumable-media==2.7.2 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # google-cloud-storage -googleapis-common-protos==1.63.2 +googleapis-common-protos==1.65.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1009,13 +1009,13 @@ grimp==3.4.1 # via # -r requirements/edx/testing.txt # import-linter -grpcio==1.65.5 +grpcio==1.66.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # google-api-core # grpcio-status -grpcio-status==1.65.5 +grpcio-status==1.66.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1049,7 +1049,7 @@ icalendar==5.0.13 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -idna==3.7 +idna==3.10 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1064,7 +1064,7 @@ imagesize==1.4.1 # sphinx import-linter==2.0 # via -r requirements/edx/testing.txt -importlib-metadata==8.3.0 +importlib-metadata==8.5.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1114,7 +1114,7 @@ joblib==1.4.2 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # nltk -jsondiff==2.2.0 +jsondiff==2.2.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1147,7 +1147,7 @@ jwcrypto==1.5.6 # -r requirements/edx/testing.txt # django-oauth-toolkit # pylti1p3 -kombu==5.4.0 +kombu==5.4.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1236,7 +1236,7 @@ mccabe==0.7.0 # via # -r requirements/edx/testing.txt # pylint -meilisearch==0.31.4 +meilisearch==0.31.5 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1248,7 +1248,7 @@ mock==5.1.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -mongoengine==0.28.2 +mongoengine==0.29.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1258,7 +1258,7 @@ monotonic==1.6 # -r requirements/edx/testing.txt # analytics-python # py2neo -more-itertools==10.4.0 +more-itertools==10.5.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1268,18 +1268,18 @@ mpmath==1.3.0 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # sympy -msgpack==1.0.8 +msgpack==1.1.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # cachecontrol -multidict==6.0.5 +multidict==6.1.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # aiohttp # yarl -mypy==1.11.1 +mypy==1.11.2 # via # -r requirements/edx/development.in # django-stubs @@ -1336,7 +1336,7 @@ openai==0.28.1 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-enterprise -openedx-atlas==0.6.1 +openedx-atlas==0.6.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1344,7 +1344,7 @@ openedx-calc==3.1.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -openedx-django-pyfs==3.6.0 +openedx-django-pyfs==3.7.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1358,7 +1358,7 @@ openedx-django-wiki==2.1.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -openedx-events==9.14.0 +openedx-events==9.14.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1367,13 +1367,13 @@ openedx-events==9.14.0 # edx-event-bus-redis # event-tracking # ora2 -openedx-filters==1.9.0 +openedx-filters==1.10.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # lti-consumer-xblock # ora2 -openedx-learning==0.11.5 +openedx-learning==0.13.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt @@ -1413,7 +1413,7 @@ pansi==2020.7.3 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # py2neo -paramiko==3.4.1 +paramiko==3.5.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1436,7 +1436,7 @@ paver==1.3.4 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -pbr==6.0.0 +pbr==6.1.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1463,7 +1463,7 @@ pillow==10.4.0 # edxval pip-tools==7.4.1 # via -r requirements/edx/../pip-tools.txt -platformdirs==4.2.2 +platformdirs==4.3.6 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1482,7 +1482,7 @@ polib==1.2.0 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-i18n-tools -prompt-toolkit==3.0.47 +prompt-toolkit==3.0.48 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1493,7 +1493,7 @@ proto-plus==1.24.0 # -r requirements/edx/testing.txt # google-api-core # google-cloud-firestore -protobuf==5.27.3 +protobuf==5.28.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1516,14 +1516,14 @@ py2neo @ https://github.com/overhangio/py2neo/releases/download/2021.2.3/py2neo- # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -pyasn1==0.6.0 +pyasn1==0.6.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # pgpy # pyasn1-modules # rsa -pyasn1-modules==0.4.0 +pyasn1-modules==0.4.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1548,13 +1548,13 @@ pycryptodomex==3.20.0 # edx-proctoring # lti-consumer-xblock # pyjwkest -pydantic==2.8.2 +pydantic==2.9.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # camel-converter # fastapi -pydantic-core==2.20.1 +pydantic-core==2.23.4 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1654,14 +1654,14 @@ pyopenssl==24.2.1 # -r requirements/edx/testing.txt # optimizely-sdk # snowflake-connector-python -pyparsing==3.1.2 +pyparsing==3.1.4 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # chem # httplib2 # openedx-calc -pyproject-api==1.7.1 +pyproject-api==1.8.0 # via # -r requirements/edx/testing.txt # tox @@ -1670,7 +1670,7 @@ pyproject-hooks==1.1.0 # -r requirements/edx/../pip-tools.txt # build # pip-tools -pyquery==2.0.0 +pyquery==2.0.1 # via -r requirements/edx/testing.txt pyrsistent==0.20.0 # via @@ -1682,7 +1682,7 @@ pysrt==1.1.2 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edxval -pytest==8.3.2 +pytest==8.3.3 # via # -r requirements/edx/testing.txt # pylint-pytest @@ -1697,7 +1697,7 @@ pytest-attrib==0.1.3 # via -r requirements/edx/testing.txt pytest-cov==5.0.0 # via -r requirements/edx/testing.txt -pytest-django==4.8.0 +pytest-django==4.9.0 # via -r requirements/edx/testing.txt pytest-json-report==1.5.0 # via -r requirements/edx/testing.txt @@ -1753,11 +1753,10 @@ python3-saml==1.16.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -pytz==2024.1 +pytz==2024.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt - # django-ses # djangorestframework # drf-yasg # edx-completion @@ -1795,7 +1794,7 @@ random2==1.0.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -recommender-xblock==2.2.0 +recommender-xblock==2.2.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1810,7 +1809,7 @@ referencing==0.35.1 # -r requirements/edx/testing.txt # jsonschema # jsonschema-specifications -regex==2024.7.24 +regex==2024.9.11 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1862,7 +1861,7 @@ rsa==4.9 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # google-auth -rules==3.4 +rules==3.5 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1879,7 +1878,7 @@ sailthru-client==2.2.3 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-ace -scipy==1.14.0 +scipy==1.14.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1942,7 +1941,6 @@ slumber==0.7.1 # -r requirements/edx/testing.txt # edx-bulk-grades # edx-enterprise - # edx-rest-api-client smmap==5.0.1 # via # -r requirements/edx/doc.txt @@ -1955,7 +1953,7 @@ snowballstemmer==2.2.0 # via # -r requirements/edx/doc.txt # sphinx -snowflake-connector-python==3.12.0 +snowflake-connector-python==3.12.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1972,7 +1970,7 @@ social-auth-core==4.5.4 # -r requirements/edx/testing.txt # edx-auth-backends # social-auth-app-django -sorl-thumbnail==12.10.0 +sorl-thumbnail==12.11.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -2050,11 +2048,11 @@ staff-graded-xblock==2.3.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -starlette==0.38.2 +starlette==0.38.6 # via # -r requirements/edx/testing.txt # fastapi -stevedore==5.2.0 +stevedore==5.3.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -2068,7 +2066,7 @@ super-csv==3.2.0 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-bulk-grades -sympy==1.13.2 +sympy==1.13.3 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -2096,7 +2094,7 @@ tomlkit==0.13.2 # -r requirements/edx/testing.txt # pylint # snowflake-connector-python -tox==4.18.0 +tox==4.20.0 # via -r requirements/edx/testing.txt tqdm==4.66.5 # via @@ -2104,9 +2102,9 @@ tqdm==4.66.5 # -r requirements/edx/testing.txt # nltk # openai -types-pytz==2024.1.0.20240417 +types-pytz==2024.2.0.20240913 # via django-stubs -types-pyyaml==6.0.12.20240808 +types-pyyaml==6.0.12.20240917 # via # django-stubs # djangorestframework-stubs @@ -2133,11 +2131,12 @@ typing-extensions==4.12.2 # pydata-sphinx-theme # pylti1p3 # snowflake-connector-python -tzdata==2024.1 +tzdata==2024.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # celery + # kombu unicodecsv==0.14.1 # via # -r requirements/edx/doc.txt @@ -2152,7 +2151,7 @@ uritemplate==4.1.1 # drf-spectacular # drf-yasg # google-api-python-client -urllib3==1.26.19 +urllib3==1.26.20 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt @@ -2176,7 +2175,7 @@ vine==5.1.0 # amqp # celery # kombu -virtualenv==20.26.3 +virtualenv==20.26.5 # via # -r requirements/edx/testing.txt # tox @@ -2185,14 +2184,14 @@ voluptuous==0.15.2 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # ora2 -vulture==2.11 +vulture==2.12 # via -r requirements/edx/development.in walrus==0.9.4 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-event-bus-redis -watchdog==4.0.2 +watchdog==5.0.2 # via # -r requirements/edx/development.in # -r requirements/edx/doc.txt @@ -2276,13 +2275,13 @@ xss-utils==0.6.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -yarl==1.9.4 +yarl==1.12.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # aiohttp # pact-python -zipp==3.20.0 +zipp==3.20.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 2f916b40c53..f46a637a080 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -14,7 +14,7 @@ aiohappyeyeballs==2.4.0 # via # -r requirements/edx/base.txt # aiohttp -aiohttp==3.10.5 +aiohttp==3.10.6 # via # -r requirements/edx/base.txt # geoip2 @@ -87,7 +87,7 @@ beautifulsoup4==4.12.3 # -r requirements/edx/base.txt # pydata-sphinx-theme # pynliner -billiard==4.2.0 +billiard==4.2.1 # via # -r requirements/edx/base.txt # celery @@ -102,13 +102,13 @@ bleach[css]==6.1.0 # xblock-poll boto==2.49.0 # via -r requirements/edx/base.txt -boto3==1.35.1 +boto3==1.35.27 # via # -r requirements/edx/base.txt # django-ses # fs-s3fs # ora2 -botocore==1.35.1 +botocore==1.35.27 # via # -r requirements/edx/base.txt # boto3 @@ -137,14 +137,14 @@ celery==5.4.0 # edx-enterprise # event-tracking # openedx-learning -certifi==2024.7.4 +certifi==2024.8.30 # via # -r requirements/edx/base.txt # elasticsearch # py2neo # requests # snowflake-connector-python -cffi==1.17.0 +cffi==1.17.1 # via # -r requirements/edx/base.txt # cryptography @@ -196,7 +196,7 @@ codejail-includes==1.0.0 # via -r requirements/edx/base.txt crowdsourcehinter-xblock==0.7 # via -r requirements/edx/base.txt -cryptography==42.0.8 +cryptography==43.0.1 # via # -r requirements/edx/base.txt # django-fernet-fields-v2 @@ -213,7 +213,7 @@ cssutils==2.11.1 # via # -r requirements/edx/base.txt # pynliner -deepmerge==1.1.1 +deepmerge==2.0 # via sphinxcontrib-openapi defusedxml==0.7.1 # via @@ -222,7 +222,7 @@ defusedxml==0.7.1 # ora2 # python3-openid # social-auth-core -django==4.2.15 +django==4.2.16 # via # -c requirements/edx/../common_constraints.txt # -c requirements/edx/../constraints.txt @@ -349,7 +349,7 @@ django-js-asset==2.2.0 # django-mptt django-method-override==1.0.4 # via -r requirements/edx/base.txt -django-model-utils==4.5.1 +django-model-utils==5.0.0 # via # -r requirements/edx/base.txt # django-user-tasks @@ -382,7 +382,7 @@ django-oauth-toolkit==1.7.1 # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edx-enterprise -django-object-actions==4.2.0 +django-object-actions==4.3.0 # via # -r requirements/edx/base.txt # edx-enterprise @@ -398,7 +398,7 @@ django-sekizai==4.1.0 # via # -r requirements/edx/base.txt # openedx-django-wiki -django-ses==4.1.0 +django-ses==4.1.1 # via -r requirements/edx/base.txt django-simple-history==3.4.0 # via @@ -468,7 +468,7 @@ docutils==0.21.2 # pydata-sphinx-theme # sphinx # sphinx-mdinclude -done-xblock==2.3.0 +done-xblock==2.4.0 # via -r requirements/edx/base.txt drf-jwt==1.19.2 # via @@ -483,11 +483,11 @@ drf-yasg==1.21.7 # edx-api-doc-tools edx-ace==1.11.2 # via -r requirements/edx/base.txt -edx-api-doc-tools==1.8.0 +edx-api-doc-tools==2.0.0 # via # -r requirements/edx/base.txt # edx-name-affirmation -edx-auth-backends==4.3.0 +edx-auth-backends==4.4.0 # via -r requirements/edx/base.txt edx-braze-client==0.2.5 # via @@ -509,7 +509,7 @@ edx-celeryutils==1.3.0 # super-csv edx-codejail==3.4.1 # via -r requirements/edx/base.txt -edx-completion==4.6.7 +edx-completion==4.7.1 # via -r requirements/edx/base.txt edx-django-release-util==1.4.0 # via @@ -535,7 +535,7 @@ edx-django-utils==5.15.0 # openedx-events # ora2 # super-csv -edx-drf-extensions==10.3.0 +edx-drf-extensions==10.4.0 # via # -r requirements/edx/base.txt # edx-completion @@ -547,7 +547,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.13 +edx-enterprise==4.25.19 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -562,7 +562,7 @@ edx-i18n-tools==1.5.0 # ora2 edx-milestones==0.6.0 # via -r requirements/edx/base.txt -edx-name-affirmation==2.4.1 +edx-name-affirmation==2.4.2 # via -r requirements/edx/base.txt edx-opaque-keys[django]==2.11.0 # via @@ -585,11 +585,11 @@ edx-proctoring==4.18.1 # via # -r requirements/edx/base.txt # edx-proctoring-proctortrack -edx-rbac==1.9.0 +edx-rbac==1.10.0 # via # -r requirements/edx/base.txt # edx-enterprise -edx-rest-api-client==5.7.1 +edx-rest-api-client==6.0.0 # via # -r requirements/edx/base.txt # edx-enterprise @@ -598,7 +598,7 @@ edx-search==4.0.0 # via -r requirements/edx/base.txt edx-sga==0.25.0 # via -r requirements/edx/base.txt -edx-submissions==3.7.7 +edx-submissions==3.8.0 # via # -r requirements/edx/base.txt # ora2 @@ -624,7 +624,7 @@ edx-when==2.5.0 # via # -r requirements/edx/base.txt # edx-proctoring -edxval==2.5.0 +edxval==2.6.0 # via -r requirements/edx/base.txt elasticsearch==7.13.4 # via @@ -644,11 +644,11 @@ event-tracking==3.0.0 # edx-completion # edx-proctoring # edx-search -fastavro==1.9.5 +fastavro==1.9.7 # via # -r requirements/edx/base.txt # openedx-events -filelock==3.15.4 +filelock==3.16.1 # via # -r requirements/edx/base.txt # snowflake-connector-python @@ -683,7 +683,7 @@ gitpython==3.1.43 # via -r requirements/edx/doc.in glob2==0.7 # via -r requirements/edx/base.txt -google-api-core[grpc]==2.19.1 +google-api-core[grpc]==2.20.0 # via # -r requirements/edx/base.txt # firebase-admin @@ -691,11 +691,11 @@ google-api-core[grpc]==2.19.1 # google-cloud-core # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.141.0 +google-api-python-client==2.147.0 # via # -r requirements/edx/base.txt # firebase-admin -google-auth==2.34.0 +google-auth==2.35.0 # via # -r requirements/edx/base.txt # google-api-core @@ -713,7 +713,7 @@ google-cloud-core==2.4.1 # -r requirements/edx/base.txt # google-cloud-firestore # google-cloud-storage -google-cloud-firestore==2.17.2 +google-cloud-firestore==2.19.0 # via # -r requirements/edx/base.txt # firebase-admin @@ -721,7 +721,7 @@ google-cloud-storage==2.18.2 # via # -r requirements/edx/base.txt # firebase-admin -google-crc32c==1.5.0 +google-crc32c==1.6.0 # via # -r requirements/edx/base.txt # google-cloud-storage @@ -730,17 +730,17 @@ google-resumable-media==2.7.2 # via # -r requirements/edx/base.txt # google-cloud-storage -googleapis-common-protos==1.63.2 +googleapis-common-protos==1.65.0 # via # -r requirements/edx/base.txt # google-api-core # grpcio-status -grpcio==1.65.5 +grpcio==1.66.1 # via # -r requirements/edx/base.txt # google-api-core # grpcio-status -grpcio-status==1.65.5 +grpcio-status==1.66.1 # via # -r requirements/edx/base.txt # google-api-core @@ -759,7 +759,7 @@ httplib2==0.22.0 # google-auth-httplib2 icalendar==5.0.13 # via -r requirements/edx/base.txt -idna==3.7 +idna==3.10 # via # -r requirements/edx/base.txt # optimizely-sdk @@ -768,7 +768,7 @@ idna==3.7 # yarl imagesize==1.4.1 # via sphinx -importlib-metadata==8.3.0 +importlib-metadata==8.5.0 # via -r requirements/edx/base.txt inflection==0.5.1 # via @@ -799,7 +799,7 @@ joblib==1.4.2 # via # -r requirements/edx/base.txt # nltk -jsondiff==2.2.0 +jsondiff==2.2.1 # via # -r requirements/edx/base.txt # edx-enterprise @@ -827,7 +827,7 @@ jwcrypto==1.5.6 # -r requirements/edx/base.txt # django-oauth-toolkit # pylti1p3 -kombu==5.4.0 +kombu==5.4.2 # via # -r requirements/edx/base.txt # celery @@ -891,20 +891,20 @@ maxminddb==2.6.2 # via # -r requirements/edx/base.txt # geoip2 -meilisearch==0.31.4 +meilisearch==0.31.5 # via -r requirements/edx/base.txt mistune==3.0.2 # via sphinx-mdinclude mock==5.1.0 # via -r requirements/edx/base.txt -mongoengine==0.28.2 +mongoengine==0.29.1 # via -r requirements/edx/base.txt monotonic==1.6 # via # -r requirements/edx/base.txt # analytics-python # py2neo -more-itertools==10.4.0 +more-itertools==10.5.0 # via # -r requirements/edx/base.txt # cssutils @@ -912,11 +912,11 @@ mpmath==1.3.0 # via # -r requirements/edx/base.txt # sympy -msgpack==1.0.8 +msgpack==1.1.0 # via # -r requirements/edx/base.txt # cachecontrol -multidict==6.0.5 +multidict==6.1.0 # via # -r requirements/edx/base.txt # aiohttp @@ -957,11 +957,11 @@ openai==0.28.1 # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edx-enterprise -openedx-atlas==0.6.1 +openedx-atlas==0.6.2 # via -r requirements/edx/base.txt openedx-calc==3.1.0 # via -r requirements/edx/base.txt -openedx-django-pyfs==3.6.0 +openedx-django-pyfs==3.7.0 # via # -r requirements/edx/base.txt # lti-consumer-xblock @@ -970,7 +970,7 @@ openedx-django-require==2.1.0 # via -r requirements/edx/base.txt openedx-django-wiki==2.1.0 # via -r requirements/edx/base.txt -openedx-events==9.14.0 +openedx-events==9.14.1 # via # -r requirements/edx/base.txt # edx-enterprise @@ -978,12 +978,12 @@ openedx-events==9.14.0 # edx-event-bus-redis # event-tracking # ora2 -openedx-filters==1.9.0 +openedx-filters==1.10.0 # via # -r requirements/edx/base.txt # lti-consumer-xblock # ora2 -openedx-learning==0.11.5 +openedx-learning==0.13.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -1008,7 +1008,7 @@ pansi==2020.7.3 # via # -r requirements/edx/base.txt # py2neo -paramiko==3.4.1 +paramiko==3.5.0 # via # -r requirements/edx/base.txt # edx-enterprise @@ -1026,7 +1026,7 @@ path-py==12.5.0 # staff-graded-xblock paver==1.3.4 # via -r requirements/edx/base.txt -pbr==6.0.0 +pbr==6.1.0 # via # -r requirements/edx/base.txt # stevedore @@ -1044,7 +1044,7 @@ pillow==10.4.0 # edx-enterprise # edx-organizations # edxval -platformdirs==4.2.2 +platformdirs==4.3.6 # via # -r requirements/edx/base.txt # snowflake-connector-python @@ -1052,7 +1052,7 @@ polib==1.2.0 # via # -r requirements/edx/base.txt # edx-i18n-tools -prompt-toolkit==3.0.47 +prompt-toolkit==3.0.48 # via # -r requirements/edx/base.txt # click-repl @@ -1061,7 +1061,7 @@ proto-plus==1.24.0 # -r requirements/edx/base.txt # google-api-core # google-cloud-firestore -protobuf==5.27.3 +protobuf==5.28.2 # via # -r requirements/edx/base.txt # google-api-core @@ -1077,13 +1077,13 @@ py2neo @ https://github.com/overhangio/py2neo/releases/download/2021.2.3/py2neo- # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt -pyasn1==0.6.0 +pyasn1==0.6.1 # via # -r requirements/edx/base.txt # pgpy # pyasn1-modules # rsa -pyasn1-modules==0.4.0 +pyasn1-modules==0.4.1 # via # -r requirements/edx/base.txt # google-auth @@ -1099,11 +1099,11 @@ pycryptodomex==3.20.0 # edx-proctoring # lti-consumer-xblock # pyjwkest -pydantic==2.8.2 +pydantic==2.9.2 # via # -r requirements/edx/base.txt # camel-converter -pydantic-core==2.20.1 +pydantic-core==2.23.4 # via # -r requirements/edx/base.txt # pydantic @@ -1162,7 +1162,7 @@ pyopenssl==24.2.1 # -r requirements/edx/base.txt # optimizely-sdk # snowflake-connector-python -pyparsing==3.1.2 +pyparsing==3.1.4 # via # -r requirements/edx/base.txt # chem @@ -1209,10 +1209,9 @@ python3-openid==3.2.0 ; python_version >= "3" # social-auth-core python3-saml==1.16.0 # via -r requirements/edx/base.txt -pytz==2024.1 +pytz==2024.2 # via # -r requirements/edx/base.txt - # django-ses # djangorestframework # drf-yasg # edx-completion @@ -1243,7 +1242,7 @@ pyyaml==6.0.2 # xblock random2==1.0.2 # via -r requirements/edx/base.txt -recommender-xblock==2.2.0 +recommender-xblock==2.2.1 # via -r requirements/edx/base.txt redis==5.0.8 # via @@ -1254,7 +1253,7 @@ referencing==0.35.1 # -r requirements/edx/base.txt # jsonschema # jsonschema-specifications -regex==2024.7.24 +regex==2024.9.11 # via # -r requirements/edx/base.txt # nltk @@ -1299,7 +1298,7 @@ rsa==4.9 # via # -r requirements/edx/base.txt # google-auth -rules==3.4 +rules==3.5 # via # -r requirements/edx/base.txt # edx-enterprise @@ -1313,7 +1312,7 @@ sailthru-client==2.2.3 # via # -r requirements/edx/base.txt # edx-ace -scipy==1.14.0 +scipy==1.14.1 # via # -r requirements/edx/base.txt # chem @@ -1364,12 +1363,11 @@ slumber==0.7.1 # -r requirements/edx/base.txt # edx-bulk-grades # edx-enterprise - # edx-rest-api-client smmap==5.0.1 # via gitdb snowballstemmer==2.2.0 # via sphinx -snowflake-connector-python==3.12.0 +snowflake-connector-python==3.12.2 # via # -r requirements/edx/base.txt # edx-enterprise @@ -1383,7 +1381,7 @@ social-auth-core==4.5.4 # -r requirements/edx/base.txt # edx-auth-backends # social-auth-app-django -sorl-thumbnail==12.10.0 +sorl-thumbnail==12.11.0 # via # -r requirements/edx/base.txt # openedx-django-wiki @@ -1438,7 +1436,7 @@ sqlparse==0.5.1 # django staff-graded-xblock==2.3.0 # via -r requirements/edx/base.txt -stevedore==5.2.0 +stevedore==5.3.0 # via # -r requirements/edx/base.txt # code-annotations @@ -1450,7 +1448,7 @@ super-csv==3.2.0 # via # -r requirements/edx/base.txt # edx-bulk-grades -sympy==1.13.2 +sympy==1.13.3 # via # -r requirements/edx/base.txt # openedx-calc @@ -1486,10 +1484,11 @@ typing-extensions==4.12.2 # pydata-sphinx-theme # pylti1p3 # snowflake-connector-python -tzdata==2024.1 +tzdata==2024.2 # via # -r requirements/edx/base.txt # celery + # kombu unicodecsv==0.14.1 # via # -r requirements/edx/base.txt @@ -1500,7 +1499,7 @@ uritemplate==4.1.1 # drf-spectacular # drf-yasg # google-api-python-client -urllib3==1.26.19 +urllib3==1.26.20 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -1524,7 +1523,7 @@ walrus==0.9.4 # via # -r requirements/edx/base.txt # edx-event-bus-redis -watchdog==4.0.2 +watchdog==5.0.2 # via -r requirements/edx/base.txt wcwidth==0.2.13 # via @@ -1583,11 +1582,11 @@ xmlsec==1.3.13 # python3-saml xss-utils==0.6.0 # via -r requirements/edx/base.txt -yarl==1.9.4 +yarl==1.12.1 # via # -r requirements/edx/base.txt # aiohttp -zipp==3.20.0 +zipp==3.20.2 # via # -r requirements/edx/base.txt # importlib-metadata diff --git a/requirements/edx/paver.txt b/requirements/edx/paver.txt index d86acae05f4..a0b1896919d 100644 --- a/requirements/edx/paver.txt +++ b/requirements/edx/paver.txt @@ -4,7 +4,7 @@ # # make upgrade # -certifi==2024.7.4 +certifi==2024.8.30 # via requests charset-normalizer==2.0.12 # via @@ -14,7 +14,7 @@ dnspython==2.6.1 # via pymongo edx-opaque-keys==2.11.0 # via -r requirements/edx/paver.in -idna==3.7 +idna==3.10 # via requests lazy==1.6 # via -r requirements/edx/paver.in @@ -32,7 +32,7 @@ path==16.11.0 # -r requirements/edx/paver.in paver==1.3.4 # via -r requirements/edx/paver.in -pbr==6.0.0 +pbr==6.1.0 # via stevedore psutil==6.0.0 # via -r requirements/edx/paver.in @@ -51,17 +51,17 @@ six==1.16.0 # via # libsass # paver -stevedore==5.2.0 +stevedore==5.3.0 # via # -r requirements/edx/paver.in # edx-opaque-keys typing-extensions==4.12.2 # via edx-opaque-keys -urllib3==1.26.19 +urllib3==1.26.20 # via # -c requirements/edx/../constraints.txt # requests -watchdog==4.0.2 +watchdog==5.0.2 # via -r requirements/edx/paver.in wrapt==1.16.0 # via -r requirements/edx/paver.in diff --git a/requirements/edx/semgrep.txt b/requirements/edx/semgrep.txt index 292f1319048..102289def27 100644 --- a/requirements/edx/semgrep.txt +++ b/requirements/edx/semgrep.txt @@ -17,7 +17,7 @@ boltons==21.0.0 # semgrep bracex==2.5 # via wcmatch -certifi==2024.7.4 +certifi==2024.8.30 # via requests charset-normalizer==2.0.12 # via @@ -38,7 +38,7 @@ face==22.0.0 # via glom glom==22.1.0 # via semgrep -idna==3.7 +idna==3.10 # via requests jsonschema==4.23.0 # via semgrep @@ -60,7 +60,7 @@ referencing==0.35.1 # jsonschema-specifications requests==2.32.3 # via semgrep -rich==13.7.1 +rich==13.8.1 # via semgrep rpds-py==0.20.0 # via @@ -76,7 +76,7 @@ tomli==2.0.1 # via semgrep typing-extensions==4.12.2 # via semgrep -urllib3==1.26.19 +urllib3==1.26.20 # via # -c requirements/edx/../constraints.txt # requests diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 93a0313a112..96bfd286198 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -12,7 +12,7 @@ aiohappyeyeballs==2.4.0 # via # -r requirements/edx/base.txt # aiohttp -aiohttp==3.10.5 +aiohttp==3.10.6 # via # -r requirements/edx/base.txt # geoip2 @@ -39,7 +39,7 @@ annotated-types==0.7.0 # via # -r requirements/edx/base.txt # pydantic -anyio==4.4.0 +anyio==4.6.0 # via starlette appdirs==1.4.4 # via @@ -87,7 +87,7 @@ beautifulsoup4==4.12.3 # -r requirements/edx/base.txt # -r requirements/edx/testing.in # pynliner -billiard==4.2.0 +billiard==4.2.1 # via # -r requirements/edx/base.txt # celery @@ -102,13 +102,13 @@ bleach[css]==6.1.0 # xblock-poll boto==2.49.0 # via -r requirements/edx/base.txt -boto3==1.35.1 +boto3==1.35.27 # via # -r requirements/edx/base.txt # django-ses # fs-s3fs # ora2 -botocore==1.35.1 +botocore==1.35.27 # via # -r requirements/edx/base.txt # boto3 @@ -138,14 +138,14 @@ celery==5.4.0 # edx-enterprise # event-tracking # openedx-learning -certifi==2024.7.4 +certifi==2024.8.30 # via # -r requirements/edx/base.txt # elasticsearch # py2neo # requests # snowflake-connector-python -cffi==1.17.0 +cffi==1.17.1 # via # -r requirements/edx/base.txt # cryptography @@ -215,7 +215,7 @@ coverage[toml]==7.6.1 # pytest-cov crowdsourcehinter-xblock==0.7 # via -r requirements/edx/base.txt -cryptography==42.0.8 +cryptography==43.0.1 # via # -r requirements/edx/base.txt # django-fernet-fields-v2 @@ -245,13 +245,13 @@ defusedxml==0.7.1 # ora2 # python3-openid # social-auth-core -diff-cover==9.1.1 +diff-cover==9.2.0 # via -r requirements/edx/coverage.txt dill==0.3.8 # via pylint distlib==0.3.8 # via virtualenv -django==4.2.15 +django==4.2.16 # via # -c requirements/edx/../common_constraints.txt # -c requirements/edx/../constraints.txt @@ -378,7 +378,7 @@ django-js-asset==2.2.0 # django-mptt django-method-override==1.0.4 # via -r requirements/edx/base.txt -django-model-utils==4.5.1 +django-model-utils==5.0.0 # via # -r requirements/edx/base.txt # django-user-tasks @@ -411,7 +411,7 @@ django-oauth-toolkit==1.7.1 # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edx-enterprise -django-object-actions==4.2.0 +django-object-actions==4.3.0 # via # -r requirements/edx/base.txt # edx-enterprise @@ -427,7 +427,7 @@ django-sekizai==4.1.0 # via # -r requirements/edx/base.txt # openedx-django-wiki -django-ses==4.1.0 +django-ses==4.1.1 # via -r requirements/edx/base.txt django-simple-history==3.4.0 # via @@ -492,7 +492,7 @@ dnspython==2.6.1 # via # -r requirements/edx/base.txt # pymongo -done-xblock==2.3.0 +done-xblock==2.4.0 # via -r requirements/edx/base.txt drf-jwt==1.19.2 # via @@ -507,11 +507,11 @@ drf-yasg==1.21.7 # edx-api-doc-tools edx-ace==1.11.2 # via -r requirements/edx/base.txt -edx-api-doc-tools==1.8.0 +edx-api-doc-tools==2.0.0 # via # -r requirements/edx/base.txt # edx-name-affirmation -edx-auth-backends==4.3.0 +edx-auth-backends==4.4.0 # via -r requirements/edx/base.txt edx-braze-client==0.2.5 # via @@ -533,7 +533,7 @@ edx-celeryutils==1.3.0 # super-csv edx-codejail==3.4.1 # via -r requirements/edx/base.txt -edx-completion==4.6.7 +edx-completion==4.7.1 # via -r requirements/edx/base.txt edx-django-release-util==1.4.0 # via @@ -559,7 +559,7 @@ edx-django-utils==5.15.0 # openedx-events # ora2 # super-csv -edx-drf-extensions==10.3.0 +edx-drf-extensions==10.4.0 # via # -r requirements/edx/base.txt # edx-completion @@ -571,7 +571,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.13 +edx-enterprise==4.25.19 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -584,11 +584,11 @@ edx-i18n-tools==1.5.0 # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # ora2 -edx-lint==5.3.7 +edx-lint==5.4.0 # via -r requirements/edx/testing.in edx-milestones==0.6.0 # via -r requirements/edx/base.txt -edx-name-affirmation==2.4.1 +edx-name-affirmation==2.4.2 # via -r requirements/edx/base.txt edx-opaque-keys[django]==2.11.0 # via @@ -611,11 +611,11 @@ edx-proctoring==4.18.1 # via # -r requirements/edx/base.txt # edx-proctoring-proctortrack -edx-rbac==1.9.0 +edx-rbac==1.10.0 # via # -r requirements/edx/base.txt # edx-enterprise -edx-rest-api-client==5.7.1 +edx-rest-api-client==6.0.0 # via # -r requirements/edx/base.txt # edx-enterprise @@ -624,7 +624,7 @@ edx-search==4.0.0 # via -r requirements/edx/base.txt edx-sga==0.25.0 # via -r requirements/edx/base.txt -edx-submissions==3.7.7 +edx-submissions==3.8.0 # via # -r requirements/edx/base.txt # ora2 @@ -650,7 +650,7 @@ edx-when==2.5.0 # via # -r requirements/edx/base.txt # edx-proctoring -edxval==2.5.0 +edxval==2.6.0 # via -r requirements/edx/base.txt elasticsearch==7.13.4 # via @@ -674,15 +674,15 @@ execnet==2.1.1 # via pytest-xdist factory-boy==3.3.1 # via -r requirements/edx/testing.in -faker==27.0.0 +faker==30.0.0 # via factory-boy -fastapi==0.112.1 +fastapi==0.115.0 # via pact-python -fastavro==1.9.5 +fastavro==1.9.7 # via # -r requirements/edx/base.txt # openedx-events -filelock==3.15.4 +filelock==3.16.1 # via # -r requirements/edx/base.txt # snowflake-connector-python @@ -717,7 +717,7 @@ geoip2==4.8.0 # via -r requirements/edx/base.txt glob2==0.7 # via -r requirements/edx/base.txt -google-api-core[grpc]==2.19.1 +google-api-core[grpc]==2.20.0 # via # -r requirements/edx/base.txt # firebase-admin @@ -725,11 +725,11 @@ google-api-core[grpc]==2.19.1 # google-cloud-core # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.141.0 +google-api-python-client==2.147.0 # via # -r requirements/edx/base.txt # firebase-admin -google-auth==2.34.0 +google-auth==2.35.0 # via # -r requirements/edx/base.txt # google-api-core @@ -747,7 +747,7 @@ google-cloud-core==2.4.1 # -r requirements/edx/base.txt # google-cloud-firestore # google-cloud-storage -google-cloud-firestore==2.17.2 +google-cloud-firestore==2.19.0 # via # -r requirements/edx/base.txt # firebase-admin @@ -755,7 +755,7 @@ google-cloud-storage==2.18.2 # via # -r requirements/edx/base.txt # firebase-admin -google-crc32c==1.5.0 +google-crc32c==1.6.0 # via # -r requirements/edx/base.txt # google-cloud-storage @@ -764,19 +764,19 @@ google-resumable-media==2.7.2 # via # -r requirements/edx/base.txt # google-cloud-storage -googleapis-common-protos==1.63.2 +googleapis-common-protos==1.65.0 # via # -r requirements/edx/base.txt # google-api-core # grpcio-status grimp==3.4.1 # via import-linter -grpcio==1.65.5 +grpcio==1.66.1 # via # -r requirements/edx/base.txt # google-api-core # grpcio-status -grpcio-status==1.65.5 +grpcio-status==1.66.1 # via # -r requirements/edx/base.txt # google-api-core @@ -799,7 +799,7 @@ httpretty==1.1.4 # via -r requirements/edx/testing.in icalendar==5.0.13 # via -r requirements/edx/base.txt -idna==3.7 +idna==3.10 # via # -r requirements/edx/base.txt # anyio @@ -809,7 +809,7 @@ idna==3.7 # yarl import-linter==2.0 # via -r requirements/edx/testing.in -importlib-metadata==8.3.0 +importlib-metadata==8.5.0 # via -r requirements/edx/base.txt inflection==0.5.1 # via @@ -847,7 +847,7 @@ joblib==1.4.2 # via # -r requirements/edx/base.txt # nltk -jsondiff==2.2.0 +jsondiff==2.2.1 # via # -r requirements/edx/base.txt # edx-enterprise @@ -874,7 +874,7 @@ jwcrypto==1.5.6 # -r requirements/edx/base.txt # django-oauth-toolkit # pylti1p3 -kombu==5.4.0 +kombu==5.4.2 # via # -r requirements/edx/base.txt # celery @@ -944,18 +944,18 @@ maxminddb==2.6.2 # geoip2 mccabe==0.7.0 # via pylint -meilisearch==0.31.4 +meilisearch==0.31.5 # via -r requirements/edx/base.txt mock==5.1.0 # via -r requirements/edx/base.txt -mongoengine==0.28.2 +mongoengine==0.29.1 # via -r requirements/edx/base.txt monotonic==1.6 # via # -r requirements/edx/base.txt # analytics-python # py2neo -more-itertools==10.4.0 +more-itertools==10.5.0 # via # -r requirements/edx/base.txt # cssutils @@ -963,11 +963,11 @@ mpmath==1.3.0 # via # -r requirements/edx/base.txt # sympy -msgpack==1.0.8 +msgpack==1.1.0 # via # -r requirements/edx/base.txt # cachecontrol -multidict==6.0.5 +multidict==6.1.0 # via # -r requirements/edx/base.txt # aiohttp @@ -1008,11 +1008,11 @@ openai==0.28.1 # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edx-enterprise -openedx-atlas==0.6.1 +openedx-atlas==0.6.2 # via -r requirements/edx/base.txt openedx-calc==3.1.0 # via -r requirements/edx/base.txt -openedx-django-pyfs==3.6.0 +openedx-django-pyfs==3.7.0 # via # -r requirements/edx/base.txt # lti-consumer-xblock @@ -1021,7 +1021,7 @@ openedx-django-require==2.1.0 # via -r requirements/edx/base.txt openedx-django-wiki==2.1.0 # via -r requirements/edx/base.txt -openedx-events==9.14.0 +openedx-events==9.14.1 # via # -r requirements/edx/base.txt # edx-enterprise @@ -1029,12 +1029,12 @@ openedx-events==9.14.0 # edx-event-bus-redis # event-tracking # ora2 -openedx-filters==1.9.0 +openedx-filters==1.10.0 # via # -r requirements/edx/base.txt # lti-consumer-xblock # ora2 -openedx-learning==0.11.5 +openedx-learning==0.13.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -1062,7 +1062,7 @@ pansi==2020.7.3 # via # -r requirements/edx/base.txt # py2neo -paramiko==3.4.1 +paramiko==3.5.0 # via # -r requirements/edx/base.txt # edx-enterprise @@ -1080,7 +1080,7 @@ path-py==12.5.0 # staff-graded-xblock paver==1.3.4 # via -r requirements/edx/base.txt -pbr==6.0.0 +pbr==6.1.0 # via # -r requirements/edx/base.txt # stevedore @@ -1096,7 +1096,7 @@ pillow==10.4.0 # edx-enterprise # edx-organizations # edxval -platformdirs==4.2.2 +platformdirs==4.3.6 # via # -r requirements/edx/base.txt # pylint @@ -1114,7 +1114,7 @@ polib==1.2.0 # -r requirements/edx/base.txt # -r requirements/edx/testing.in # edx-i18n-tools -prompt-toolkit==3.0.47 +prompt-toolkit==3.0.48 # via # -r requirements/edx/base.txt # click-repl @@ -1123,7 +1123,7 @@ proto-plus==1.24.0 # -r requirements/edx/base.txt # google-api-core # google-cloud-firestore -protobuf==5.27.3 +protobuf==5.28.2 # via # -r requirements/edx/base.txt # google-api-core @@ -1143,13 +1143,13 @@ py2neo @ https://github.com/overhangio/py2neo/releases/download/2021.2.3/py2neo- # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt -pyasn1==0.6.0 +pyasn1==0.6.1 # via # -r requirements/edx/base.txt # pgpy # pyasn1-modules # rsa -pyasn1-modules==0.4.0 +pyasn1-modules==0.4.1 # via # -r requirements/edx/base.txt # google-auth @@ -1169,12 +1169,12 @@ pycryptodomex==3.20.0 # edx-proctoring # lti-consumer-xblock # pyjwkest -pydantic==2.8.2 +pydantic==2.9.2 # via # -r requirements/edx/base.txt # camel-converter # fastapi -pydantic-core==2.20.1 +pydantic-core==2.23.4 # via # -r requirements/edx/base.txt # pydantic @@ -1247,15 +1247,15 @@ pyopenssl==24.2.1 # -r requirements/edx/base.txt # optimizely-sdk # snowflake-connector-python -pyparsing==3.1.2 +pyparsing==3.1.4 # via # -r requirements/edx/base.txt # chem # httplib2 # openedx-calc -pyproject-api==1.7.1 +pyproject-api==1.8.0 # via tox -pyquery==2.0.0 +pyquery==2.0.1 # via -r requirements/edx/testing.in pyrsistent==0.20.0 # via @@ -1265,7 +1265,7 @@ pysrt==1.1.2 # via # -r requirements/edx/base.txt # edxval -pytest==8.3.2 +pytest==8.3.3 # via # -r requirements/edx/testing.in # pylint-pytest @@ -1280,7 +1280,7 @@ pytest-attrib==0.1.3 # via -r requirements/edx/testing.in pytest-cov==5.0.0 # via -r requirements/edx/testing.in -pytest-django==4.8.0 +pytest-django==4.9.0 # via -r requirements/edx/testing.in pytest-json-report==1.5.0 # via -r requirements/edx/testing.in @@ -1327,10 +1327,9 @@ python3-openid==3.2.0 ; python_version >= "3" # social-auth-core python3-saml==1.16.0 # via -r requirements/edx/base.txt -pytz==2024.1 +pytz==2024.2 # via # -r requirements/edx/base.txt - # django-ses # djangorestframework # drf-yasg # edx-completion @@ -1360,7 +1359,7 @@ pyyaml==6.0.2 # xblock random2==1.0.2 # via -r requirements/edx/base.txt -recommender-xblock==2.2.0 +recommender-xblock==2.2.1 # via -r requirements/edx/base.txt redis==5.0.8 # via @@ -1371,7 +1370,7 @@ referencing==0.35.1 # -r requirements/edx/base.txt # jsonschema # jsonschema-specifications -regex==2024.7.24 +regex==2024.9.11 # via # -r requirements/edx/base.txt # nltk @@ -1416,7 +1415,7 @@ rsa==4.9 # via # -r requirements/edx/base.txt # google-auth -rules==3.4 +rules==3.5 # via # -r requirements/edx/base.txt # edx-enterprise @@ -1430,7 +1429,7 @@ sailthru-client==2.2.3 # via # -r requirements/edx/base.txt # edx-ace -scipy==1.14.0 +scipy==1.14.1 # via # -r requirements/edx/base.txt # chem @@ -1484,10 +1483,9 @@ slumber==0.7.1 # -r requirements/edx/base.txt # edx-bulk-grades # edx-enterprise - # edx-rest-api-client sniffio==1.3.1 # via anyio -snowflake-connector-python==3.12.0 +snowflake-connector-python==3.12.2 # via # -r requirements/edx/base.txt # edx-enterprise @@ -1501,7 +1499,7 @@ social-auth-core==4.5.4 # -r requirements/edx/base.txt # edx-auth-backends # social-auth-app-django -sorl-thumbnail==12.10.0 +sorl-thumbnail==12.11.0 # via # -r requirements/edx/base.txt # openedx-django-wiki @@ -1519,9 +1517,9 @@ sqlparse==0.5.1 # django staff-graded-xblock==2.3.0 # via -r requirements/edx/base.txt -starlette==0.38.2 +starlette==0.38.6 # via fastapi -stevedore==5.2.0 +stevedore==5.3.0 # via # -r requirements/edx/base.txt # code-annotations @@ -1533,7 +1531,7 @@ super-csv==3.2.0 # via # -r requirements/edx/base.txt # edx-bulk-grades -sympy==1.13.2 +sympy==1.13.3 # via # -r requirements/edx/base.txt # openedx-calc @@ -1555,7 +1553,7 @@ tomlkit==0.13.2 # -r requirements/edx/base.txt # pylint # snowflake-connector-python -tox==4.18.0 +tox==4.20.0 # via -r requirements/edx/testing.in tqdm==4.66.5 # via @@ -1575,10 +1573,11 @@ typing-extensions==4.12.2 # pydantic-core # pylti1p3 # snowflake-connector-python -tzdata==2024.1 +tzdata==2024.2 # via # -r requirements/edx/base.txt # celery + # kombu unicodecsv==0.14.1 # via # -r requirements/edx/base.txt @@ -1591,7 +1590,7 @@ uritemplate==4.1.1 # drf-spectacular # drf-yasg # google-api-python-client -urllib3==1.26.19 +urllib3==1.26.20 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -1609,7 +1608,7 @@ vine==5.1.0 # amqp # celery # kombu -virtualenv==20.26.3 +virtualenv==20.26.5 # via tox voluptuous==0.15.2 # via @@ -1619,7 +1618,7 @@ walrus==0.9.4 # via # -r requirements/edx/base.txt # edx-event-bus-redis -watchdog==4.0.2 +watchdog==5.0.2 # via -r requirements/edx/base.txt wcwidth==0.2.13 # via @@ -1680,12 +1679,12 @@ xmlsec==1.3.13 # python3-saml xss-utils==0.6.0 # via -r requirements/edx/base.txt -yarl==1.9.4 +yarl==1.12.1 # via # -r requirements/edx/base.txt # aiohttp # pact-python -zipp==3.20.0 +zipp==3.20.2 # via # -r requirements/edx/base.txt # importlib-metadata diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt index f7b35489c35..5bcb2aa5508 100644 --- a/requirements/pip-tools.txt +++ b/requirements/pip-tools.txt @@ -4,7 +4,7 @@ # # make upgrade # -build==1.2.1 +build==1.2.2 # via pip-tools click==8.1.6 # via diff --git a/requirements/pip.txt b/requirements/pip.txt index f0cf3d10999..36c777e2165 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -10,5 +10,5 @@ wheel==0.44.0 # The following packages are considered to be unsafe in a requirements file: pip==24.2 # via -r requirements/pip.in -setuptools==73.0.0 +setuptools==75.1.0 # via -r requirements/pip.in diff --git a/scripts/structures_pruning/requirements/base.txt b/scripts/structures_pruning/requirements/base.txt index 828a81a8d4e..b80c660b874 100644 --- a/scripts/structures_pruning/requirements/base.txt +++ b/scripts/structures_pruning/requirements/base.txt @@ -13,16 +13,16 @@ click-log==0.4.0 # via -r scripts/structures_pruning/requirements/base.in dnspython==2.6.1 # via pymongo -edx-opaque-keys==2.10.0 +edx-opaque-keys==2.11.0 # via -r scripts/structures_pruning/requirements/base.in -pbr==6.0.0 +pbr==6.1.0 # via stevedore pymongo==4.4.0 # via # -c scripts/structures_pruning/requirements/../../../requirements/constraints.txt # -r scripts/structures_pruning/requirements/base.in # edx-opaque-keys -stevedore==5.2.0 +stevedore==5.3.0 # via edx-opaque-keys typing-extensions==4.12.2 # via edx-opaque-keys diff --git a/scripts/structures_pruning/requirements/testing.txt b/scripts/structures_pruning/requirements/testing.txt index 47648d50fdd..8be2e15973d 100644 --- a/scripts/structures_pruning/requirements/testing.txt +++ b/scripts/structures_pruning/requirements/testing.txt @@ -16,13 +16,13 @@ dnspython==2.6.1 # via # -r scripts/structures_pruning/requirements/base.txt # pymongo -edx-opaque-keys==2.10.0 +edx-opaque-keys==2.11.0 # via -r scripts/structures_pruning/requirements/base.txt iniconfig==2.0.0 # via pytest packaging==24.1 # via pytest -pbr==6.0.0 +pbr==6.1.0 # via # -r scripts/structures_pruning/requirements/base.txt # stevedore @@ -32,9 +32,9 @@ pymongo==4.4.0 # via # -r scripts/structures_pruning/requirements/base.txt # edx-opaque-keys -pytest==8.3.2 +pytest==8.3.3 # via -r scripts/structures_pruning/requirements/testing.in -stevedore==5.2.0 +stevedore==5.3.0 # via # -r scripts/structures_pruning/requirements/base.txt # edx-opaque-keys diff --git a/scripts/user_retirement/requirements/base.txt b/scripts/user_retirement/requirements/base.txt index 3e5ed473807..2fb9d4543e2 100644 --- a/scripts/user_retirement/requirements/base.txt +++ b/scripts/user_retirement/requirements/base.txt @@ -10,17 +10,17 @@ attrs==24.2.0 # via zeep backoff==2.2.1 # via -r scripts/user_retirement/requirements/base.in -boto3==1.35.1 +boto3==1.35.27 # via -r scripts/user_retirement/requirements/base.in -botocore==1.35.1 +botocore==1.35.27 # via # boto3 # s3transfer cachetools==5.5.0 # via google-auth -certifi==2024.7.4 +certifi==2024.8.30 # via requests -cffi==1.17.0 +cffi==1.17.1 # via # cryptography # pynacl @@ -33,9 +33,9 @@ click==8.1.6 # -c scripts/user_retirement/requirements/../../../requirements/constraints.txt # -r scripts/user_retirement/requirements/base.in # edx-django-utils -cryptography==43.0.0 +cryptography==43.0.1 # via pyjwt -django==4.2.15 +django==4.2.16 # via # -c scripts/user_retirement/requirements/../../../requirements/common_constraints.txt # -c scripts/user_retirement/requirements/../../../requirements/constraints.txt @@ -48,26 +48,26 @@ django-waffle==4.1.0 # via edx-django-utils edx-django-utils==5.15.0 # via edx-rest-api-client -edx-rest-api-client==5.7.1 +edx-rest-api-client==6.0.0 # via -r scripts/user_retirement/requirements/base.in -google-api-core==2.19.1 +google-api-core==2.20.0 # via google-api-python-client -google-api-python-client==2.141.0 +google-api-python-client==2.147.0 # via -r scripts/user_retirement/requirements/base.in -google-auth==2.34.0 +google-auth==2.35.0 # via # google-api-core # google-api-python-client # google-auth-httplib2 google-auth-httplib2==0.2.0 # via google-api-python-client -googleapis-common-protos==1.63.2 +googleapis-common-protos==1.65.0 # via google-api-core httplib2==0.22.0 # via # google-api-python-client # google-auth-httplib2 -idna==3.7 +idna==3.10 # via requests isodate==0.6.1 # via zeep @@ -81,28 +81,28 @@ lxml==4.9.4 # via # -c scripts/user_retirement/requirements/../../../requirements/constraints.txt # zeep -more-itertools==10.4.0 +more-itertools==10.5.0 # via simple-salesforce newrelic==9.13.0 # via edx-django-utils -pbr==6.0.0 +pbr==6.1.0 # via stevedore -platformdirs==4.2.2 +platformdirs==4.3.6 # via zeep proto-plus==1.24.0 # via google-api-core -protobuf==5.27.3 +protobuf==5.28.2 # via # google-api-core # googleapis-common-protos # proto-plus psutil==6.0.0 # via edx-django-utils -pyasn1==0.6.0 +pyasn1==0.6.1 # via # pyasn1-modules # rsa -pyasn1-modules==0.4.0 +pyasn1-modules==0.4.1 # via google-auth pycparser==2.22 # via cffi @@ -112,11 +112,11 @@ pyjwt[crypto]==2.9.0 # simple-salesforce pynacl==1.5.0 # via edx-django-utils -pyparsing==3.1.2 +pyparsing==3.1.4 # via httplib2 python-dateutil==2.9.0.post0 # via botocore -pytz==2024.1 +pytz==2024.2 # via # jenkinsapi # zeep @@ -131,7 +131,6 @@ requests==2.32.3 # requests-file # requests-toolbelt # simple-salesforce - # slumber # zeep requests-file==2.1.0 # via zeep @@ -150,11 +149,9 @@ six==1.16.0 # isodate # jenkinsapi # python-dateutil -slumber==0.7.1 - # via edx-rest-api-client sqlparse==0.5.1 # via django -stevedore==5.2.0 +stevedore==5.3.0 # via edx-django-utils typing-extensions==4.12.2 # via simple-salesforce @@ -162,7 +159,7 @@ unicodecsv==0.14.1 # via -r scripts/user_retirement/requirements/base.in uritemplate==4.1.1 # via google-api-python-client -urllib3==1.26.19 +urllib3==1.26.20 # via # -c scripts/user_retirement/requirements/../../../requirements/constraints.txt # botocore diff --git a/scripts/user_retirement/requirements/testing.txt b/scripts/user_retirement/requirements/testing.txt index f7464dfa060..73795fc6223 100644 --- a/scripts/user_retirement/requirements/testing.txt +++ b/scripts/user_retirement/requirements/testing.txt @@ -14,11 +14,11 @@ attrs==24.2.0 # zeep backoff==2.2.1 # via -r scripts/user_retirement/requirements/base.txt -boto3==1.35.1 +boto3==1.35.27 # via # -r scripts/user_retirement/requirements/base.txt # moto -botocore==1.35.1 +botocore==1.35.27 # via # -r scripts/user_retirement/requirements/base.txt # boto3 @@ -28,11 +28,11 @@ cachetools==5.5.0 # via # -r scripts/user_retirement/requirements/base.txt # google-auth -certifi==2024.7.4 +certifi==2024.8.30 # via # -r scripts/user_retirement/requirements/base.txt # requests -cffi==1.17.0 +cffi==1.17.1 # via # -r scripts/user_retirement/requirements/base.txt # cryptography @@ -45,14 +45,14 @@ click==8.1.6 # via # -r scripts/user_retirement/requirements/base.txt # edx-django-utils -cryptography==43.0.0 +cryptography==43.0.1 # via # -r scripts/user_retirement/requirements/base.txt # moto # pyjwt ddt==1.7.2 # via -r scripts/user_retirement/requirements/testing.in -django==4.2.15 +django==4.2.16 # via # -r scripts/user_retirement/requirements/base.txt # django-crum @@ -70,15 +70,15 @@ edx-django-utils==5.15.0 # via # -r scripts/user_retirement/requirements/base.txt # edx-rest-api-client -edx-rest-api-client==5.7.1 +edx-rest-api-client==6.0.0 # via -r scripts/user_retirement/requirements/base.txt -google-api-core==2.19.1 +google-api-core==2.20.0 # via # -r scripts/user_retirement/requirements/base.txt # google-api-python-client -google-api-python-client==2.141.0 +google-api-python-client==2.147.0 # via -r scripts/user_retirement/requirements/base.txt -google-auth==2.34.0 +google-auth==2.35.0 # via # -r scripts/user_retirement/requirements/base.txt # google-api-core @@ -88,7 +88,7 @@ google-auth-httplib2==0.2.0 # via # -r scripts/user_retirement/requirements/base.txt # google-api-python-client -googleapis-common-protos==1.63.2 +googleapis-common-protos==1.65.0 # via # -r scripts/user_retirement/requirements/base.txt # google-api-core @@ -97,7 +97,7 @@ httplib2==0.22.0 # -r scripts/user_retirement/requirements/base.txt # google-api-python-client # google-auth-httplib2 -idna==3.7 +idna==3.10 # via # -r scripts/user_retirement/requirements/base.txt # requests @@ -126,7 +126,7 @@ markupsafe==2.1.5 # werkzeug mock==5.1.0 # via -r scripts/user_retirement/requirements/testing.in -more-itertools==10.4.0 +more-itertools==10.5.0 # via # -r scripts/user_retirement/requirements/base.txt # simple-salesforce @@ -138,11 +138,11 @@ newrelic==9.13.0 # edx-django-utils packaging==24.1 # via pytest -pbr==6.0.0 +pbr==6.1.0 # via # -r scripts/user_retirement/requirements/base.txt # stevedore -platformdirs==4.2.2 +platformdirs==4.3.6 # via # -r scripts/user_retirement/requirements/base.txt # zeep @@ -152,7 +152,7 @@ proto-plus==1.24.0 # via # -r scripts/user_retirement/requirements/base.txt # google-api-core -protobuf==5.27.3 +protobuf==5.28.2 # via # -r scripts/user_retirement/requirements/base.txt # google-api-core @@ -162,12 +162,12 @@ psutil==6.0.0 # via # -r scripts/user_retirement/requirements/base.txt # edx-django-utils -pyasn1==0.6.0 +pyasn1==0.6.1 # via # -r scripts/user_retirement/requirements/base.txt # pyasn1-modules # rsa -pyasn1-modules==0.4.0 +pyasn1-modules==0.4.1 # via # -r scripts/user_retirement/requirements/base.txt # google-auth @@ -184,18 +184,18 @@ pynacl==1.5.0 # via # -r scripts/user_retirement/requirements/base.txt # edx-django-utils -pyparsing==3.1.2 +pyparsing==3.1.4 # via # -r scripts/user_retirement/requirements/base.txt # httplib2 -pytest==8.3.2 +pytest==8.3.3 # via -r scripts/user_retirement/requirements/testing.in python-dateutil==2.9.0.post0 # via # -r scripts/user_retirement/requirements/base.txt # botocore # moto -pytz==2024.1 +pytz==2024.2 # via # -r scripts/user_retirement/requirements/base.txt # jenkinsapi @@ -216,7 +216,6 @@ requests==2.32.3 # requests-toolbelt # responses # simple-salesforce - # slumber # zeep requests-file==2.1.0 # via @@ -250,15 +249,11 @@ six==1.16.0 # isodate # jenkinsapi # python-dateutil -slumber==0.7.1 - # via - # -r scripts/user_retirement/requirements/base.txt - # edx-rest-api-client sqlparse==0.5.1 # via # -r scripts/user_retirement/requirements/base.txt # django -stevedore==5.2.0 +stevedore==5.3.0 # via # -r scripts/user_retirement/requirements/base.txt # edx-django-utils @@ -272,13 +267,13 @@ uritemplate==4.1.1 # via # -r scripts/user_retirement/requirements/base.txt # google-api-python-client -urllib3==1.26.19 +urllib3==1.26.20 # via # -r scripts/user_retirement/requirements/base.txt # botocore # requests # responses -werkzeug==3.0.3 +werkzeug==3.0.4 # via moto xmltodict==0.13.0 # via moto diff --git a/scripts/xblock/requirements.txt b/scripts/xblock/requirements.txt index 5b7f07242e6..81ed56ea694 100644 --- a/scripts/xblock/requirements.txt +++ b/scripts/xblock/requirements.txt @@ -4,17 +4,17 @@ # # make upgrade # -certifi==2024.7.4 +certifi==2024.8.30 # via requests charset-normalizer==2.0.12 # via # -c scripts/xblock/../../requirements/constraints.txt # requests -idna==3.7 +idna==3.10 # via requests requests==2.32.3 # via -r scripts/xblock/requirements.in -urllib3==1.26.19 +urllib3==1.26.20 # via # -c scripts/xblock/../../requirements/constraints.txt # requests diff --git a/xmodule/capa_block.py b/xmodule/capa_block.py index 54ca0cbc312..c1c650144b0 100644 --- a/xmodule/capa_block.py +++ b/xmodule/capa_block.py @@ -616,11 +616,15 @@ def index_dictionary(self): "", capa_content ) + # Strip out all other tags, leaving their content. But we want spaces between adjacent tags, so that + #
Option A
Option B
+ # becomes "Option A Option B" not "Option AOption B" (these will appear in search results) + capa_content = re.sub(r"<([^>]+)>", r" <\2>", capa_content) capa_content = re.sub( r"(\s| |//)+", " ", nh3.clean(capa_content, tags=set()) - ) + ).strip() capa_body = { "capa_content": capa_content, diff --git a/xmodule/partitions/tests/test_partitions.py b/xmodule/partitions/tests/test_partitions.py index fde6afd3141..41f26b14db5 100644 --- a/xmodule/partitions/tests/test_partitions.py +++ b/xmodule/partitions/tests/test_partitions.py @@ -462,21 +462,6 @@ class TestPartitionService(PartitionServiceBaseClass): Test getting a user's group out of a partition """ - def test_get_user_group_id_for_partition(self): - # assign the first group to be returned - user_partition_id = self.user_partition.id - groups = self.user_partition.groups - self.user_partition.scheme.current_group = groups[0] - - # get a group assigned to the user - group1_id = self.partition_service.get_user_group_id_for_partition(self.user, user_partition_id) - assert group1_id == groups[0].id - - # switch to the second group and verify that it is returned for the user - self.user_partition.scheme.current_group = groups[1] - group2_id = self.partition_service.get_user_group_id_for_partition(self.user, user_partition_id) - assert group2_id == groups[1].id - def test_caching(self): username = "psvc_cache_user" user_partition_id = self.user_partition.id diff --git a/xmodule/tests/test_capa_block.py b/xmodule/tests/test_capa_block.py index d1c01e10971..c81b137f1b2 100644 --- a/xmodule/tests/test_capa_block.py +++ b/xmodule/tests/test_capa_block.py @@ -3290,7 +3290,7 @@ def test_response_types_ignores_non_response_tags(self): assert block.index_dictionary() ==\ {'content_type': ProblemBlock.INDEX_CONTENT_TYPE, 'problem_types': ['multiplechoiceresponse'], - 'content': {'display_name': name, 'capa_content': ' Label Some comment Apple Banana Chocolate Donut '}} + 'content': {'display_name': name, 'capa_content': 'Label Some comment Apple Banana Chocolate Donut'}} def test_response_types_multiple_tags(self): xml = textwrap.dedent(""" @@ -3328,7 +3328,7 @@ def test_response_types_multiple_tags(self): 'problem_types': {"optionresponse", "multiplechoiceresponse"}, 'content': { 'display_name': name, - 'capa_content': " Label Some comment Donut Buggy '1','2' " + 'capa_content': "Label Some comment Donut Buggy '1','2'" }, } ) @@ -3369,7 +3369,7 @@ def test_solutions_not_indexed(self): assert block.index_dictionary() ==\ {'content_type': ProblemBlock.INDEX_CONTENT_TYPE, 'problem_types': [], - 'content': {'display_name': name, 'capa_content': ' '}} + 'content': {'display_name': name, 'capa_content': ''}} def test_indexing_checkboxes(self): name = "Checkboxes" @@ -3390,7 +3390,7 @@ def test_indexing_checkboxes(self): assert block.index_dictionary() ==\ {'content_type': ProblemBlock.INDEX_CONTENT_TYPE, 'problem_types': ['choiceresponse'], - 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}} + 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}} def test_indexing_dropdown(self): name = "Dropdown" @@ -3405,7 +3405,7 @@ def test_indexing_dropdown(self): assert block.index_dictionary() ==\ {'content_type': ProblemBlock.INDEX_CONTENT_TYPE, 'problem_types': ['optionresponse'], - 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}} + 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}} def test_indexing_multiple_choice(self): name = "Multiple Choice" @@ -3424,7 +3424,7 @@ def test_indexing_multiple_choice(self): assert block.index_dictionary() ==\ {'content_type': ProblemBlock.INDEX_CONTENT_TYPE, 'problem_types': ['multiplechoiceresponse'], - 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}} + 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}} def test_indexing_numerical_input(self): name = "Numerical Input" @@ -3446,7 +3446,7 @@ def test_indexing_numerical_input(self): assert block.index_dictionary() ==\ {'content_type': ProblemBlock.INDEX_CONTENT_TYPE, 'problem_types': ['numericalresponse'], - 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}} + 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}} def test_indexing_text_input(self): name = "Text Input" @@ -3465,7 +3465,7 @@ def test_indexing_text_input(self): assert block.index_dictionary() ==\ {'content_type': ProblemBlock.INDEX_CONTENT_TYPE, 'problem_types': ['stringresponse'], - 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}} + 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}} def test_indexing_non_latin_problem(self): sample_text_input_problem_xml = textwrap.dedent(""" @@ -3476,7 +3476,7 @@ def test_indexing_non_latin_problem(self): """) name = "Non latin Input" block = self._create_block(sample_text_input_problem_xml, name=name) - capa_content = " Δοκιμή με μεταβλητές με Ελληνικούς χαρακτήρες μέσα σε python: $FX1_VAL " + capa_content = "Δοκιμή με μεταβλητές με Ελληνικούς χαρακτήρες μέσα σε python: $FX1_VAL" block_dict = block.index_dictionary() assert block_dict['content']['capa_content'] == smart_str(capa_content) @@ -3503,7 +3503,7 @@ def test_indexing_checkboxes_with_hints_and_feedback(self): assert block.index_dictionary() ==\ {'content_type': ProblemBlock.INDEX_CONTENT_TYPE, 'problem_types': ['choiceresponse'], - 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}} + 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}} def test_indexing_dropdown_with_hints_and_feedback(self): name = "Dropdown with Hints and Feedback" @@ -3523,7 +3523,7 @@ def test_indexing_dropdown_with_hints_and_feedback(self): assert block.index_dictionary() ==\ {'content_type': ProblemBlock.INDEX_CONTENT_TYPE, 'problem_types': ['optionresponse'], - 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}} + 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}} def test_indexing_multiple_choice_with_hints_and_feedback(self): name = "Multiple Choice with Hints and Feedback" @@ -3543,7 +3543,7 @@ def test_indexing_multiple_choice_with_hints_and_feedback(self): assert block.index_dictionary() ==\ {'content_type': ProblemBlock.INDEX_CONTENT_TYPE, 'problem_types': ['multiplechoiceresponse'], - 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}} + 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}} def test_indexing_numerical_input_with_hints_and_feedback(self): name = "Numerical Input with Hints and Feedback" @@ -3561,7 +3561,7 @@ def test_indexing_numerical_input_with_hints_and_feedback(self): assert block.index_dictionary() ==\ {'content_type': ProblemBlock.INDEX_CONTENT_TYPE, 'problem_types': ['numericalresponse'], - 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}} + 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}} def test_indexing_text_input_with_hints_and_feedback(self): name = "Text Input with Hints and Feedback" @@ -3579,7 +3579,7 @@ def test_indexing_text_input_with_hints_and_feedback(self): assert block.index_dictionary() ==\ {'content_type': ProblemBlock.INDEX_CONTENT_TYPE, 'problem_types': ['stringresponse'], - 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}} + 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ').strip()}} def test_indexing_problem_with_html_tags(self): sample_problem_xml = textwrap.dedent(""" @@ -3598,14 +3598,33 @@ def test_indexing_problem_with_html_tags(self): """) name = "Mixed business" block = self._create_block(sample_problem_xml, name=name) - capa_content = textwrap.dedent(""" - This has HTML comment in it. - HTML end. - """) + capa_content = "This has HTML comment in it. HTML end." assert block.index_dictionary() ==\ {'content_type': ProblemBlock.INDEX_CONTENT_TYPE, 'problem_types': [], - 'content': {'display_name': name, 'capa_content': capa_content.replace('\n', ' ')}} + 'content': {'display_name': name, 'capa_content': capa_content}} + + def test_indexing_problem_with_no_whitespace_between_tags(self): + """ + The new (MFE) visual editor for capa problems renders the OLX without spaces between the tags. + We want to make sure the index description is still readable and has whitespace. + """ + sample_problem_xml = ( + "" + "
Question text here.
" + "
Option A
" + "
Option B
" + "
" + "
" + ) + name = "No spaces" + block = self._create_block(sample_problem_xml, name=name) + capa_content = "Question text here. Option A Option B" + assert block.index_dictionary() == { + 'content_type': ProblemBlock.INDEX_CONTENT_TYPE, + 'problem_types': ['choiceresponse'], + 'content': {'display_name': name, 'capa_content': capa_content}, + } def test_invalid_xml_handling(self): """ diff --git a/xmodule/video_block/transcripts_utils.py b/xmodule/video_block/transcripts_utils.py index e41f925295f..132b8cff1e1 100644 --- a/xmodule/video_block/transcripts_utils.py +++ b/xmodule/video_block/transcripts_utils.py @@ -1074,7 +1074,7 @@ def get_transcript_from_learning_core(video_block, language, output_format, tran """ # TODO: Update to use Learning Core data models once static assets support # has been added. - raise NotImplementedError("Transcripts not supported.") + raise NotFoundError("No transcript - transcripts not supported yet by learning core components.") def get_transcript(video, lang=None, output_format=Transcript.SRT, youtube_id=None): diff --git a/xmodule/video_block/video_block.py b/xmodule/video_block/video_block.py index b4fddb63fa7..782645c373b 100644 --- a/xmodule/video_block/video_block.py +++ b/xmodule/video_block/video_block.py @@ -482,7 +482,7 @@ def get_html(self, view=STUDENT_VIEW, context=None): # lint-amnesty, pylint: di 'hide_downloads': is_public_view or is_embed, 'id': self.location.html_id(), 'block_id': str(self.location), - 'course_id': str(self.location.course_key), + 'course_id': str(self.context_key), 'video_id': str(self.edx_video_id), 'user_id': self.get_user_id(), 'is_embed': is_embed, @@ -510,8 +510,10 @@ def get_course_video_sharing_override(self): """ Return course video sharing options override or None """ + if not self.context_key.is_course: + return False # Only courses support this feature at all (not libraries) try: - course = get_course_by_id(self.course_id) + course = get_course_by_id(self.context_key) return getattr(course, 'video_sharing_options', None) # In case the course / modulestore does something weird @@ -523,11 +525,13 @@ def is_public_sharing_enabled(self): """ Is public sharing enabled for this video? """ + if not self.context_key.is_course: + return False # Only courses support this feature at all (not libraries) try: # Video share feature must be enabled for sharing settings to take effect - feature_enabled = PUBLIC_VIDEO_SHARE.is_enabled(self.location.course_key) + feature_enabled = PUBLIC_VIDEO_SHARE.is_enabled(self.context_key) except Exception as err: # pylint: disable=broad-except - log.exception(f"Error retrieving course for course ID: {self.location.course_key}") + log.exception(f"Error retrieving course for course ID: {self.context_key}") return False if not feature_enabled: return False @@ -552,11 +556,13 @@ def is_transcript_feedback_enabled(self): """ Is transcript feedback enabled for this video? """ + if not self.context_key.is_course: + return False # Only courses support this feature at all (not libraries) try: # Video transcript feedback must be enabled in order to show the widget - feature_enabled = TRANSCRIPT_FEEDBACK.is_enabled(self.location.course_key) + feature_enabled = TRANSCRIPT_FEEDBACK.is_enabled(self.context_key) except Exception as err: # pylint: disable=broad-except - log.exception(f"Error retrieving course for course ID: {self.location.course_key}") + log.exception(f"Error retrieving course for course ID: {self.context_key}") return False return feature_enabled