From 73364712ba5e424c845e5d724b9e2c2c4e6f5733 Mon Sep 17 00:00:00 2001 From: cef Date: Wed, 4 Sep 2024 09:48:22 -0500 Subject: [PATCH 01/39] feat: set links for CourseAuthoring discussion alert --- .../rest_api/v1/views/tests/test_course_index.py | 5 +++-- cms/envs/common.py | 8 +++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_index.py b/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_index.py index eafc2b37aa0c..189f2496a427 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_index.py +++ b/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_index.py @@ -1,6 +1,7 @@ """ Unit tests for course index outline. """ +from django.conf import settings from django.test import RequestFactory from django.urls import reverse from rest_framework import status @@ -62,7 +63,7 @@ def test_course_index_response(self): "advance_settings_url": f"/settings/advanced/{self.course.id}" }, "discussions_incontext_feedback_url": "", - "discussions_incontext_learnmore_url": "", + "discussions_incontext_learnmore_url": settings.DISCUSSIONS_INCONTEXT_LEARNMORE_URL, "is_custom_relative_dates_active": True, "initial_state": None, "initial_user_clipboard": { @@ -103,7 +104,7 @@ def test_course_index_response_with_show_locators(self): "advance_settings_url": f"/settings/advanced/{self.course.id}" }, "discussions_incontext_feedback_url": "", - "discussions_incontext_learnmore_url": "", + "discussions_incontext_learnmore_url": settings.DISCUSSIONS_INCONTEXT_LEARNMORE_URL, "is_custom_relative_dates_active": False, "initial_state": { "expanded_locators": [ diff --git a/cms/envs/common.py b/cms/envs/common.py index 8daa08aeb1c8..46f7d2a3d2a5 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -2814,8 +2814,14 @@ BRAZE_COURSE_ENROLLMENT_CANVAS_ID = '' +######################## Discussion Forum settings ######################## + +# Feedback link in upgraded discussion notification alert DISCUSSIONS_INCONTEXT_FEEDBACK_URL = '' -DISCUSSIONS_INCONTEXT_LEARNMORE_URL = '' + +# Learn More link in upgraded discussion notification alert +# pylint: disable=line-too-long +DISCUSSIONS_INCONTEXT_LEARNMORE_URL = "https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/manage_discussions/discussions.html" #### django-simple-history## # disable indexing on date field its coming django-simple-history. From c71414a247b5eebc50ac846ecdb4a327a707bff4 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Fri, 20 Sep 2024 22:42:41 +0530 Subject: [PATCH 02/39] feat: add block_id field to filterable attributes of meilisearch (#35493) --- openedx/core/djangoapps/content/search/api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openedx/core/djangoapps/content/search/api.py b/openedx/core/djangoapps/content/search/api.py index 71d09590d003..7fe964128e5a 100644 --- a/openedx/core/djangoapps/content/search/api.py +++ b/openedx/core/djangoapps/content/search/api.py @@ -315,6 +315,8 @@ def rebuild_index(status_cb: Callable[[str], None] | None = None) -> None: client.index(temp_index_name).update_distinct_attribute(Fields.usage_key) # Mark which attributes can be used for filtering/faceted search: client.index(temp_index_name).update_filterable_attributes([ + # Get specific block/collection using combination of block_id and context_key + Fields.block_id, Fields.block_type, Fields.context_key, Fields.org, From 4cd36d85b5e93504bbeac199cae25b4a3950f269 Mon Sep 17 00:00:00 2001 From: Muhammad Adeel Tajamul <77053848+muhammadadeeltajamul@users.noreply.github.com> Date: Mon, 23 Sep 2024 01:35:49 -0700 Subject: [PATCH 03/39] feat: added sender in bulk_email event (#35504) --- lms/djangoapps/bulk_email/tasks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/djangoapps/bulk_email/tasks.py b/lms/djangoapps/bulk_email/tasks.py index 2b96af786a97..184dfd0e6869 100644 --- a/lms/djangoapps/bulk_email/tasks.py +++ b/lms/djangoapps/bulk_email/tasks.py @@ -474,6 +474,7 @@ def _send_course_email(entry_id, email_id, to_list, global_email_context, subtas 'course_id': str(course_email.course_id), 'to_list': [user_obj.get('email', '') for user_obj in to_list], 'total_recipients': total_recipients, + 'ace_enabled_for_bulk_email': is_bulk_email_edx_ace_enabled(), } ) # Exclude optouts (if not a retry): From 87771e76ce92e12ae7500657e178a659cccf7701 Mon Sep 17 00:00:00 2001 From: Muhammad Adeel Tajamul <77053848+muhammadadeeltajamul@users.noreply.github.com> Date: Mon, 23 Sep 2024 01:37:54 -0700 Subject: [PATCH 04/39] feat: replaced button and heading tags in email digest content (#35518) --- .../rest_api/discussions_notifications.py | 14 ++++++++++ .../tests/test_discussions_notifications.py | 26 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/lms/djangoapps/discussion/rest_api/discussions_notifications.py b/lms/djangoapps/discussion/rest_api/discussions_notifications.py index 25abcf80d486..f65faf7f2a67 100644 --- a/lms/djangoapps/discussion/rest_api/discussions_notifications.py +++ b/lms/djangoapps/discussion/rest_api/discussions_notifications.py @@ -399,4 +399,18 @@ def clean_thread_html_body(html_body): for match in html_body.find_all(tag): match.unwrap() + # Replace tags that are not allowed in email + tags_to_update = [ + {"source": "button", "target": "span"}, + {"source": "h1", "target": "h4"}, + {"source": "h2", "target": "h4"}, + {"source": "h3", "target": "h4"}, + ] + for tag_dict in tags_to_update: + for source_tag in html_body.find_all(tag_dict['source']): + target_tag = html_body.new_tag(tag_dict['target'], **source_tag.attrs) + if source_tag.string: + target_tag.string = source_tag.string + source_tag.replace_with(target_tag) + return str(html_body) diff --git a/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py b/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py index f1a71fd1239e..d92e1000feb5 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py @@ -168,3 +168,29 @@ def test_only_script_tag(self): result = clean_thread_html_body(html_body) self.assertEqual(result.strip(), expected_output) + + def test_button_tag_replace(self): + """ + Tests that the clean_thread_html_body function replaces the button tag with span tag + """ + # Tests for button replacement tag with text + html_body = '' + expected_output = 'Button' + result = clean_thread_html_body(html_body) + self.assertEqual(result, expected_output) + + # Tests button tag replacement without text + html_body = '' + expected_output = '' + result = clean_thread_html_body(html_body) + self.assertEqual(result, expected_output) + + def test_heading_tag_replace(self): + """ + Tests that the clean_thread_html_body function replaces the h1, h2 and h3 tags with h4 tag + """ + for tag in ['h1', 'h2', 'h3']: + html_body = f'<{tag}>Heading' + expected_output = '

Heading

' + result = clean_thread_html_body(html_body) + self.assertEqual(result, expected_output) From 1a92009bd2d316de829fe126a52b18b0a4d3df93 Mon Sep 17 00:00:00 2001 From: Maxwell Frank <92897870+MaxFrank13@users.noreply.github.com> Date: Mon, 23 Sep 2024 09:03:41 -0400 Subject: [PATCH 05/39] chore: Aperture code ownership update --- .github/CODEOWNERS | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3f9abcc671fb..a05b78e8837a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -17,6 +17,7 @@ lms/djangoapps/instructor_task/ lms/djangoapps/mobile_api/ openedx/core/djangoapps/credentials @openedx/2U-aperture openedx/core/djangoapps/credit @openedx/2U-aperture +openedx/core/djangoapps/enrollments/ @openedx/2U-aperture openedx/core/djangoapps/heartbeat/ openedx/core/djangoapps/oauth_dispatch openedx/core/djangoapps/user_api/ @openedx/2U-aperture @@ -37,8 +38,9 @@ lms/djangoapps/certificates/ @openedx/2U- # Discovery common/djangoapps/course_modes/ common/djangoapps/enrollment/ +lms/djangoapps/branding/ @openedx/2U-aperture lms/djangoapps/commerce/ -lms/djangoapps/experiments/ +lms/djangoapps/experiments/ @openedx/2U-aperture lms/djangoapps/learner_dashboard/ @openedx/2U-aperture lms/djangoapps/learner_home/ @openedx/2U-aperture openedx/features/content_type_gating/ From a2e29596177904d92d91bf9a5927ed295520a443 Mon Sep 17 00:00:00 2001 From: Eemaan Amir <57627710+eemaanamir@users.noreply.github.com> Date: Mon, 23 Sep 2024 18:06:03 +0500 Subject: [PATCH 06/39] chore: update default notification preference for ora_grade_assigned (#35522) * chore: update default notification preference for ora_grade_assigned * test: updated tests --- openedx/core/djangoapps/notifications/base_notification.py | 4 ++-- openedx/core/djangoapps/notifications/tests/test_views.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openedx/core/djangoapps/notifications/base_notification.py b/openedx/core/djangoapps/notifications/base_notification.py index 02b49df89444..b57d88cea616 100644 --- a/openedx/core/djangoapps/notifications/base_notification.py +++ b/openedx/core/djangoapps/notifications/base_notification.py @@ -212,8 +212,8 @@ 'name': 'ora_grade_assigned', 'is_core': False, 'info': '', - 'web': False, - 'email': False, + 'web': True, + 'email': True, 'push': False, 'email_cadence': EmailCadence.DAILY, 'non_editable': [], diff --git a/openedx/core/djangoapps/notifications/tests/test_views.py b/openedx/core/djangoapps/notifications/tests/test_views.py index 27b369d925af..b7bd0414a27f 100644 --- a/openedx/core/djangoapps/notifications/tests/test_views.py +++ b/openedx/core/djangoapps/notifications/tests/test_views.py @@ -315,8 +315,8 @@ def _expected_api_response(self, course=None): 'info': 'Notifications for submission grading.' }, 'ora_grade_assigned': { - 'web': False, - 'email': False, + 'web': True, + 'email': True, 'push': False, 'email_cadence': 'Daily', 'info': '' From 872174e28d92556a2384525e674f0f809d2b2cc9 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Mon, 9 Sep 2024 09:58:19 -0400 Subject: [PATCH 07/39] build: Switch to ubuntu-latest for builds This code does not have any dependencies that are specific to any specific version of ubuntu. So instead of testing on a specific version and then needing to do work to keep the versions up-to-date, we switch to the ubuntu-latest target which should be sufficient for testing purposes. This work is being done as a part of https://github.com/openedx/platform-roadmap/issues/377 closes https://github.com/openedx/edx-platform/issues/35314 --- .github/workflows/ci-static-analysis.yml | 2 +- .github/workflows/compile-python-requirements.yml | 2 +- .github/workflows/js-tests.yml | 2 +- .github/workflows/lint-imports.yml | 2 +- .github/workflows/migrations-check.yml | 2 +- .github/workflows/publish-ci-docker-image.yml | 2 +- .github/workflows/pylint-checks.yml | 2 +- .github/workflows/quality-checks.yml | 2 +- .github/workflows/semgrep.yml | 2 +- .github/workflows/static-assets-check.yml | 2 +- .github/workflows/unit-tests.yml | 12 ++++++------ .github/workflows/upgrade-one-python-dependency.yml | 2 +- .github/workflows/verify-dunder-init.yml | 2 +- 13 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci-static-analysis.yml b/.github/workflows/ci-static-analysis.yml index 7e768a456463..a3b0527aad72 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 0ff99b9c685a..21cb80083f1d 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 4d025e540163..c9d2d7ab1191 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 8ead8396bf39..e3c59ec09304 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 183b90effa29..ec3ff21e60bc 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 diff --git a/.github/workflows/publish-ci-docker-image.yml b/.github/workflows/publish-ci-docker-image.yml index 0a9f50f6daf9..6a0f3768b7e6 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 eeb53c24ed98..58560bf3073f 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: diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index cf8ffd5d2910..5445d70e3b4b 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 7f2b4925af8e..d880d7351766 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 7bbfd3369b6b..177416770400 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] diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 3e442b75d4e7..a697700898de 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: @@ -142,7 +142,7 @@ jobs: overwrite: true collect-and-verify: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Python @@ -207,7 +207,7 @@ jobs: # https://github.com/orgs/community/discussions/33579 success: name: Unit tests successful - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest if: always() needs: [run-tests] steps: @@ -218,7 +218,7 @@ jobs: jobs: ${{ toJSON(needs) }} compile-warnings-report: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest needs: [run-tests] steps: - uses: actions/checkout@v4 @@ -246,7 +246,7 @@ jobs: overwrite: true merge-artifacts: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest needs: [compile-warnings-report] steps: - name: Merge Pytest Warnings JSON Artifacts @@ -266,7 +266,7 @@ jobs: # Combine and upload coverage reports. coverage: if: (github.repository == 'edx/edx-platform-private') || (github.repository == 'openedx/edx-platform' && (startsWith(github.base_ref, 'open-release') == false)) - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest needs: [run-tests] strategy: matrix: diff --git a/.github/workflows/upgrade-one-python-dependency.yml b/.github/workflows/upgrade-one-python-dependency.yml index 6ca5dfcb355e..84a00266e99f 100644 --- a/.github/workflows/upgrade-one-python-dependency.yml +++ b/.github/workflows/upgrade-one-python-dependency.yml @@ -28,7 +28,7 @@ defaults: jobs: upgrade-one-python-dependency: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Check out target branch diff --git a/.github/workflows/verify-dunder-init.yml b/.github/workflows/verify-dunder-init.yml index 611fc0afc6e3..9d920238ebd4 100644 --- a/.github/workflows/verify-dunder-init.yml +++ b/.github/workflows/verify-dunder-init.yml @@ -8,7 +8,7 @@ on: jobs: verify_dunder_init: name: Verify __init__.py Files - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Check out branch From d8eef6e347b36c61221c455fdf1931290939e298 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Wed, 11 Sep 2024 09:26:57 -0400 Subject: [PATCH 08/39] build: Run mongosh commands within the container. This is no longer installed by default on ubuntu and so we have to either manually install it or just run the relevant commands in the container here it's already available. This lets us do some of the test setup in a more robust way. --- .github/workflows/migrations-check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/migrations-check.yml b/.github/workflows/migrations-check.yml index ec3ff21e60bc..f253d48e4f41 100644 --- a/.github/workflows/migrations-check.yml +++ b/.github/workflows/migrations-check.yml @@ -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 From e6e5bedf63fc393b95fc74501298fba72099d5bb Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Wed, 11 Sep 2024 10:05:23 -0400 Subject: [PATCH 09/39] fix: Don't start the mongo service. We stopped using mongo on the runner directly a while ago so this is just an errant start that should have been removed. --- .github/workflows/static-assets-check.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/static-assets-check.yml b/.github/workflows/static-assets-check.yml index 177416770400..0a417f9b1c79 100644 --- a/.github/workflows/static-assets-check.yml +++ b/.github/workflows/static-assets-check.yml @@ -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 From 1804fbb13107bd9681c5c51c5c20e90edf9e09d2 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Tue, 17 Sep 2024 15:10:12 -0400 Subject: [PATCH 10/39] build!: enable md4 for testing. Operators Note: In newer versions of ubuntu the MD4 hashing algorithm is disabled by default. To enable it the openssl config needs to be updated in a manner similar to what's being done here. Alternatively, you can set the `FEATURES['ENABLE_BLAKE2B_HASHING']` setting to `True` which will switch to a newer hashing algorithm where MD4 was previously used. Because this hashing is being used as a part of the edx-platform caching mechanism, this will effectively clear the cache for the items that use this hash. The will impact any items where the cache key might have been too big to store in memcache so it's hard to predict exactly which items will be impacted. BREAKING CHANGE: See the operator note above for more details as this may break for users transitioning from Ubuntu 20.04 to newer versions. --- .github/workflows/unit-tests.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index a697700898de..5fef1c8352ce 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -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 < Date: Tue, 24 Sep 2024 02:31:20 -0700 Subject: [PATCH 11/39] fix: updated edx.ace.message_sent event (#35498) * fix: updated edx.ace.message_sent event * fix: fixed pylint checks --- lms/djangoapps/bulk_email/signals.py | 30 ++++++++++++---------------- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 17 insertions(+), 21 deletions(-) diff --git a/lms/djangoapps/bulk_email/signals.py b/lms/djangoapps/bulk_email/signals.py index 9f6540651eeb..fb8749bf45a9 100644 --- a/lms/djangoapps/bulk_email/signals.py +++ b/lms/djangoapps/bulk_email/signals.py @@ -1,7 +1,6 @@ """ Signal handlers for the bulk_email app """ -from django.contrib.auth import get_user_model from django.dispatch import receiver from eventtracking import tracker @@ -32,29 +31,26 @@ def ace_email_sent_handler(sender, **kwargs): """ When an email is sent using ACE, this method will create an event to detect ace email success status """ - # Fetch the message object from kwargs, defaulting to None if not present - message = kwargs.get('message', None) - - user_model = get_user_model() - try: - user_id = user_model.objects.get(email=message.recipient.email_address).id - except user_model.DoesNotExist: - user_id = None - course_email = message.context.get('course_email', None) - course_id = message.context.get('course_id') + # Fetch the message dictionary from kwargs, defaulting to {} if not present + message = kwargs.get('message', {}) + recipient = message.get('recipient', {}) + message_name = message.get('name', None) + context = message.get('context', {}) + email_address = recipient.get('email', None) + user_id = recipient.get('user_id', None) + channel = message.get('channel', None) + course_id = context.get('course_id', None) if not course_id: + course_email = context.get('course_email', None) course_id = course_email.course_id if course_email else None - try: - channel = sender.__class__.__name__ - except AttributeError: - channel = 'Other' + tracker.emit( 'edx.ace.message_sent', { - 'message_type': message.name, + 'message_type': message_name, 'channel': channel, 'course_id': course_id, 'user_id': user_id, - 'user_email': message.recipient.email_address, + 'user_email': email_address, } ) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 4a65af081ce4..0b246816f559 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -401,7 +401,7 @@ drf-yasg==1.21.7 # via # django-user-tasks # edx-api-doc-tools -edx-ace==1.11.1 +edx-ace==1.11.2 # via -r requirements/edx/kernel.in edx-api-doc-tools==1.8.0 # via diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index c5db40448d94..e16f83b049f7 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -657,7 +657,7 @@ drf-yasg==1.21.7 # -r requirements/edx/testing.txt # django-user-tasks # edx-api-doc-tools -edx-ace==1.11.1 +edx-ace==1.11.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 ade1d06afbfe..2f916b40c535 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -481,7 +481,7 @@ drf-yasg==1.21.7 # -r requirements/edx/base.txt # django-user-tasks # edx-api-doc-tools -edx-ace==1.11.1 +edx-ace==1.11.2 # via -r requirements/edx/base.txt edx-api-doc-tools==1.8.0 # via diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 527c82dce45a..93a0313a1123 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -505,7 +505,7 @@ drf-yasg==1.21.7 # -r requirements/edx/base.txt # django-user-tasks # edx-api-doc-tools -edx-ace==1.11.1 +edx-ace==1.11.2 # via -r requirements/edx/base.txt edx-api-doc-tools==1.8.0 # via From ed59e79417c713aa1a3fcb447df5268b7654daf1 Mon Sep 17 00:00:00 2001 From: Varsha Menon Date: Tue, 24 Sep 2024 09:23:57 -0400 Subject: [PATCH 12/39] fix: fix broken proctoring settings link --- cms/templates/course_outline.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/templates/course_outline.html b/cms/templates/course_outline.html index 16d9ccbd4ca5..f44fdcfc8055 100644 --- a/cms/templates/course_outline.html +++ b/cms/templates/course_outline.html @@ -162,7 +162,7 @@

${_("This course has proctored exam settings that are % if mfe_proctored_exam_settings_url: <% url_encoded_course_id = quote(str(context_course.id).encode('utf-8'), safe='') %> ${Text(_("To update these settings go to the {link_start}Proctored Exam Settings page{link_end}.")).format( - link_start=HTML('').format( + link_start=HTML('').format( mfe_proctored_exam_settings_url=mfe_proctored_exam_settings_url ), link_end=HTML("") From 84d2ad95151d11960d76fb97bf484a070d6dfc5a Mon Sep 17 00:00:00 2001 From: Agrendalath Date: Mon, 31 May 2021 17:45:03 +0200 Subject: [PATCH 13/39] fix: increase grades rounding precision Enabling the rounding in #16837 has been causing noticeable (up to 1 percentage point) differences between non-rounded subsection grades and a total grade for a course. This increases the grade precision to reduce the negative implications of double rounding. --- lms/djangoapps/grades/rest_api/v1/tests/test_views.py | 8 ++++---- lms/djangoapps/grades/scores.py | 4 ++-- lms/djangoapps/grades/tests/test_course_grade_factory.py | 8 ++++---- lms/djangoapps/grades/tests/test_subsection_grade.py | 2 +- xmodule/graders.py | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lms/djangoapps/grades/rest_api/v1/tests/test_views.py b/lms/djangoapps/grades/rest_api/v1/tests/test_views.py index cd2107ec7c29..656e7e6b4396 100644 --- a/lms/djangoapps/grades/rest_api/v1/tests/test_views.py +++ b/lms/djangoapps/grades/rest_api/v1/tests/test_views.py @@ -302,7 +302,7 @@ def setUpClass(cls): + [ { 'category': 'Homework', - 'detail': 'Homework Average = 0%', + 'detail': 'Homework Average = 0.00%', 'label': 'HW Avg', 'percent': 0.0, 'prominent': True } @@ -332,21 +332,21 @@ def setUpClass(cls): }, { 'category': 'Lab', - 'detail': 'Lab Average = 0%', + 'detail': 'Lab Average = 0.00%', 'label': 'Lab Avg', 'percent': 0.0, 'prominent': True }, { 'category': 'Midterm Exam', - 'detail': 'Midterm Exam = 0%', + 'detail': 'Midterm Exam = 0.00%', 'label': 'Midterm', 'percent': 0.0, 'prominent': True }, { 'category': 'Final Exam', - 'detail': 'Final Exam = 0%', + 'detail': 'Final Exam = 0.00%', 'label': 'Final', 'percent': 0.0, 'prominent': True diff --git a/lms/djangoapps/grades/scores.py b/lms/djangoapps/grades/scores.py index f621d85ea17b..38dd0dc18926 100644 --- a/lms/djangoapps/grades/scores.py +++ b/lms/djangoapps/grades/scores.py @@ -162,8 +162,8 @@ def compute_percent(earned, possible): Returns the percentage of the given earned and possible values. """ if possible > 0: - # Rounds to two decimal places. - return around(earned / possible, decimals=2) + # Rounds to four decimal places. + return around(earned / possible, decimals=4) else: return 0.0 diff --git a/lms/djangoapps/grades/tests/test_course_grade_factory.py b/lms/djangoapps/grades/tests/test_course_grade_factory.py index 4e3bcde0ad95..2066b6cd0d7c 100644 --- a/lms/djangoapps/grades/tests/test_course_grade_factory.py +++ b/lms/djangoapps/grades/tests/test_course_grade_factory.py @@ -185,26 +185,26 @@ def test_course_grade_summary(self): 'section_breakdown': [ { 'category': 'Homework', - 'detail': 'Homework 1 - Test Sequential X with an & Ampersand - 50% (1/2)', + 'detail': 'Homework 1 - Test Sequential X with an & Ampersand - 50.00% (1/2)', 'label': 'HW 01', 'percent': 0.5 }, { 'category': 'Homework', - 'detail': 'Homework 2 - Test Sequential A - 0% (0/1)', + 'detail': 'Homework 2 - Test Sequential A - 0.00% (0/1)', 'label': 'HW 02', 'percent': 0.0 }, { 'category': 'Homework', - 'detail': 'Homework Average = 25%', + 'detail': 'Homework Average = 25.00%', 'label': 'HW Avg', 'percent': 0.25, 'prominent': True }, { 'category': 'NoCredit', - 'detail': 'NoCredit Average = 0%', + 'detail': 'NoCredit Average = 0.00%', 'label': 'NC Avg', 'percent': 0, 'prominent': True diff --git a/lms/djangoapps/grades/tests/test_subsection_grade.py b/lms/djangoapps/grades/tests/test_subsection_grade.py index 7dd39af4ece2..2398e7a71000 100644 --- a/lms/djangoapps/grades/tests/test_subsection_grade.py +++ b/lms/djangoapps/grades/tests/test_subsection_grade.py @@ -14,7 +14,7 @@ @ddt class SubsectionGradeTest(GradeTestBase): # lint-amnesty, pylint: disable=missing-class-docstring - @data((50, 100, .50), (59.49, 100, .59), (59.51, 100, .60), (59.50, 100, .60), (60.5, 100, .60)) + @data((50, 100, .5), (.5949, 100, .0059), (.5951, 100, .006), (.595, 100, .0059), (.605, 100, .006)) @unpack def test_create_and_read(self, mock_earned, mock_possible, expected_result): with mock_get_score(mock_earned, mock_possible): diff --git a/xmodule/graders.py b/xmodule/graders.py index a587204d682e..5261b9f4479a 100644 --- a/xmodule/graders.py +++ b/xmodule/graders.py @@ -387,7 +387,7 @@ def grade(self, grade_sheet, generate_random_scores=False): section_name = scores[i].display_name percentage = scores[i].percent_graded - summary_format = "{section_type} {index} - {name} - {percent:.0%} ({earned:.3n}/{possible:.3n})" + summary_format = "{section_type} {index} - {name} - {percent:.2%} ({earned:.3n}/{possible:.3n})" summary = summary_format.format( index=i + self.starting_index, section_type=self.section_type, @@ -421,7 +421,7 @@ def grade(self, grade_sheet, generate_random_scores=False): if len(breakdown) == 1: # if there is only one entry in a section, suppress the existing individual entry and the average, # and just display a single entry for the section. - total_detail = "{section_type} = {percent:.0%}".format( + total_detail = "{section_type} = {percent:.2%}".format( percent=total_percent, section_type=self.section_type, ) @@ -430,7 +430,7 @@ def grade(self, grade_sheet, generate_random_scores=False): 'detail': total_detail, 'category': self.category, 'prominent': True}, ] else: # Translators: "Homework Average = 0%" - total_detail = _("{section_type} Average = {percent:.0%}").format( + total_detail = _("{section_type} Average = {percent:.2%}").format( percent=total_percent, section_type=self.section_type ) From aeebac97baf7fe3828f7684272aaadfd43ae7ea8 Mon Sep 17 00:00:00 2001 From: Varsha Menon Date: Tue, 24 Sep 2024 10:52:31 -0400 Subject: [PATCH 14/39] feat: add verification attempt django admin --- lms/djangoapps/verify_student/admin.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/verify_student/admin.py b/lms/djangoapps/verify_student/admin.py index 6de066e6f797..1d3124580ad1 100644 --- a/lms/djangoapps/verify_student/admin.py +++ b/lms/djangoapps/verify_student/admin.py @@ -2,14 +2,14 @@ Admin site configurations for verify_student. """ - from django.contrib import admin from lms.djangoapps.verify_student.models import ( ManualVerification, SoftwareSecurePhotoVerification, SSOVerification, - SSPVerificationRetryConfig + SSPVerificationRetryConfig, + VerificationAttempt ) @@ -50,3 +50,13 @@ class SSPVerificationRetryAdmin(admin.ModelAdmin): Admin for the SSPVerificationRetryConfig table. """ pass # lint-amnesty, pylint: disable=unnecessary-pass + + +@admin.register(VerificationAttempt) +class VerificationAttemptAdmin(admin.ModelAdmin): + """ + Admin for the VerificationAttempt table. + """ + list_display = ('id', 'user', 'name', 'status', 'expiration_datetime',) + raw_id_fields = ('user',) + search_fields = ('user__username', 'name',) From 47021e9ae406967f111577d5e389d608408a9cf9 Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Tue, 24 Sep 2024 13:04:14 -0400 Subject: [PATCH 15/39] chore: add frontend-app-learner-portal-enteprise localhost to trusted origin (#35529) --- lms/envs/devstack.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py index 7a06f717996c..01100e924059 100644 --- a/lms/envs/devstack.py +++ b/lms/envs/devstack.py @@ -546,6 +546,7 @@ def should_show_debug_toolbar(request): # lint-amnesty, pylint: disable=missing 'http://localhost:1992', # frontend-app-ora 'http://localhost:2002', # frontend-app-discussions 'http://localhost:1991', # frontend-app-admin-portal + 'http://localhost:8734', # frontend-app-learner-portal-enterprise 'http://localhost:1999', # frontend-app-authn 'http://localhost:18450', # frontend-app-support-tools 'http://localhost:1994', # frontend-app-gradebook From b50c42318c427dd8e4459c2141a51445e7b230d6 Mon Sep 17 00:00:00 2001 From: Ahtisham Shahid Date: Wed, 25 Sep 2024 14:40:08 +0500 Subject: [PATCH 16/39] feat: added country disabling feature (#35451) * feat: added country disabling feature --- cms/envs/common.py | 7 +++ cms/envs/production.py | 7 +++ lms/envs/common.py | 8 ++++ lms/envs/production.py | 7 +++ .../core/djangoapps/user_api/accounts/api.py | 7 ++- .../user_api/accounts/tests/test_api.py | 31 ++++++++++--- .../djangoapps/user_authn/config/waffle.py | 1 - .../user_authn/views/registration_form.py | 14 +++++- .../user_authn/views/tests/test_register.py | 46 ++++++++++++++++++- .../core/djangoapps/user_authn/views/utils.py | 17 +++++++ 10 files changed, 133 insertions(+), 12 deletions(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index 34dd8503f35e..79186248414d 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -2936,3 +2936,10 @@ def _should_send_learning_badge_events(settings): # See https://www.meilisearch.com/docs/learn/security/tenant_tokens MEILISEARCH_INDEX_PREFIX = "" MEILISEARCH_API_KEY = "devkey" + +# .. setting_name: DISABLED_COUNTRIES +# .. setting_default: [] +# .. setting_description: List of country codes that should be disabled +# .. for now it wil impact country listing in auth flow and user profile. +# .. eg ['US', 'CA'] +DISABLED_COUNTRIES = [] diff --git a/cms/envs/production.py b/cms/envs/production.py index 50519b55229b..ad7667772f9a 100644 --- a/cms/envs/production.py +++ b/cms/envs/production.py @@ -689,3 +689,10 @@ def get_env_setting(setting): } BEAMER_PRODUCT_ID = ENV_TOKENS.get('BEAMER_PRODUCT_ID', BEAMER_PRODUCT_ID) + +# .. setting_name: DISABLED_COUNTRIES +# .. setting_default: [] +# .. setting_description: List of country codes that should be disabled +# .. for now it wil impact country listing in auth flow and user profile. +# .. eg ['US', 'CA'] +DISABLED_COUNTRIES = ENV_TOKENS.get('DISABLED_COUNTRIES', []) diff --git a/lms/envs/common.py b/lms/envs/common.py index 334669215397..f2bcfa822b6e 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -5544,3 +5544,11 @@ def _should_send_learning_badge_events(settings): # .. setting_default: empty dictionary # .. setting_description: Dictionary with additional information that you want to share in the report. SURVEY_REPORT_EXTRA_DATA = {} + + +# .. setting_name: DISABLED_COUNTRIES +# .. setting_default: [] +# .. setting_description: List of country codes that should be disabled +# .. for now it wil impact country listing in auth flow and user profile. +# .. eg ['US', 'CA'] +DISABLED_COUNTRIES = [] diff --git a/lms/envs/production.py b/lms/envs/production.py index a1acd692f4e1..6dc6be634178 100644 --- a/lms/envs/production.py +++ b/lms/envs/production.py @@ -1124,3 +1124,10 @@ def get_env_setting(setting): EVENT_BUS_PRODUCER_CONFIG = merge_producer_configs(EVENT_BUS_PRODUCER_CONFIG, ENV_TOKENS.get('EVENT_BUS_PRODUCER_CONFIG', {})) BEAMER_PRODUCT_ID = ENV_TOKENS.get('BEAMER_PRODUCT_ID', BEAMER_PRODUCT_ID) + +# .. setting_name: DISABLED_COUNTRIES +# .. setting_default: [] +# .. setting_description: List of country codes that should be disabled +# .. for now it wil impact country listing in auth flow and user profile. +# .. eg ['US', 'CA'] +DISABLED_COUNTRIES = ENV_TOKENS.get('DISABLED_COUNTRIES', []) diff --git a/openedx/core/djangoapps/user_api/accounts/api.py b/openedx/core/djangoapps/user_api/accounts/api.py index 6e7a21118852..6cc466ba0038 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 81a3158eb5bb..5123c4cf41c2 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_authn/config/waffle.py b/openedx/core/djangoapps/user_authn/config/waffle.py index c34c81e1d063..3cbb0cb2e18b 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 1d1089ce0214..7a0207f8b93c 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 02d5a72074f5..16f7da8010d6 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 9b4054bd4037..b9fb096621f2 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 From 9ae65bbe9dd0292f87090e6dda684ceef743e756 Mon Sep 17 00:00:00 2001 From: Awais Qureshi Date: Wed, 25 Sep 2024 16:01:05 +0500 Subject: [PATCH 17/39] feat: upgrade get_student_enrollment_status api with drf (22nd) (#35464) * feat!: upgrading simple api with DRF. --- lms/djangoapps/instructor/views/api.py | 87 +++++++++---------- lms/djangoapps/instructor/views/api_urls.py | 3 +- lms/djangoapps/instructor/views/serializer.py | 28 +++--- 3 files changed, 60 insertions(+), 58 deletions(-) diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 45c460d32908..cae166e7b9be 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -108,7 +108,7 @@ from lms.djangoapps.instructor_task.models import ReportStore from lms.djangoapps.instructor.views.serializer import ( AccessSerializer, BlockDueDateSerializer, RoleNameSerializer, ShowStudentExtensionSerializer, UserSerializer, - SendEmailSerializer, StudentAttemptsSerializer, ListInstructorTaskInputSerializer + SendEmailSerializer, StudentAttemptsSerializer, ListInstructorTaskInputSerializer, UniqueStudentIdentifierSerializer ) from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort, is_course_cohorted @@ -1703,60 +1703,55 @@ def post(self, request, course_id): return JsonResponse({"status": success_status}) -@require_POST -@ensure_csrf_cookie -@cache_control(no_cache=True, no_store=True, must_revalidate=True) -@require_course_permission(permissions.VIEW_ENROLLMENTS) -@require_post_params( - unique_student_identifier="email or username of student for whom to get enrollment status" -) -def get_student_enrollment_status(request, course_id): +@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True), name='dispatch') +class GetStudentEnrollmentStatus(APIView): """ Get the enrollment status of a student. - Limited to staff access. - - Takes query parameter unique_student_identifier """ + permission_classes = (IsAuthenticated, permissions.InstructorPermission) + permission_name = permissions.VIEW_ENROLLMENTS - error = '' - user = None - mode = None - is_active = None + @method_decorator(ensure_csrf_cookie) + def post(self, request, course_id): + """ + Permission: Limited to staff access. + Takes query parameter unique_student_identifier + """ + error = '' + mode = None + is_active = None - course_id = CourseKey.from_string(course_id) - unique_student_identifier = request.POST.get('unique_student_identifier') + course_id = CourseKey.from_string(course_id) + unique_student_identifier = request.data.get("unique_student_identifier") - try: - user = get_student_from_identifier(unique_student_identifier) - mode, is_active = CourseEnrollment.enrollment_mode_for_user(user, course_id) - except User.DoesNotExist: - # The student could have been invited to enroll without having - # registered. We'll also look at CourseEnrollmentAllowed - # records, so let the lack of a User slide. - pass - - enrollment_status = _('Enrollment status for {student}: unknown').format(student=unique_student_identifier) - - if user and mode: - if is_active: - enrollment_status = _('Enrollment status for {student}: active').format(student=user) - else: - enrollment_status = _('Enrollment status for {student}: inactive').format(student=user) - else: - email = user.email if user else unique_student_identifier - allowed = CourseEnrollmentAllowed.may_enroll_and_unenrolled(course_id) - if allowed and email in [cea.email for cea in allowed]: - enrollment_status = _('Enrollment status for {student}: pending').format(student=email) + serializer_data = UniqueStudentIdentifierSerializer(data=request.data) + if not serializer_data.is_valid(): + return HttpResponseBadRequest(reason=serializer_data.errors) + + user = serializer_data.validated_data.get('unique_student_identifier') + if user: + mode, is_active = CourseEnrollment.enrollment_mode_for_user(user, course_id) + + if user and mode: + if is_active: + enrollment_status = _('Enrollment status for {student}: active').format(student=user) + else: + enrollment_status = _('Enrollment status for {student}: inactive').format(student=user) else: - enrollment_status = _('Enrollment status for {student}: never enrolled').format(student=email) + email = user.email if user else unique_student_identifier + allowed = CourseEnrollmentAllowed.may_enroll_and_unenrolled(course_id) + if allowed and email in [cea.email for cea in allowed]: + enrollment_status = _('Enrollment status for {student}: pending').format(student=email) + else: + enrollment_status = _('Enrollment status for {student}: never enrolled').format(student=email) - response_payload = { - 'course_id': str(course_id), - 'error': error, - 'enrollment_status': enrollment_status - } + response_payload = { + 'course_id': str(course_id), + 'error': error, + 'enrollment_status': enrollment_status + } - return JsonResponse(response_payload) + return JsonResponse(response_payload) class StudentProgressUrlSerializer(serializers.Serializer): diff --git a/lms/djangoapps/instructor/views/api_urls.py b/lms/djangoapps/instructor/views/api_urls.py index a248b46ae531..92d5f46bc70e 100644 --- a/lms/djangoapps/instructor/views/api_urls.py +++ b/lms/djangoapps/instructor/views/api_urls.py @@ -32,7 +32,8 @@ path('get_issued_certificates/', api.get_issued_certificates, name='get_issued_certificates'), path('get_students_who_may_enroll', api.GetStudentsWhoMayEnroll.as_view(), name='get_students_who_may_enroll'), path('get_anon_ids', api.GetAnonIds.as_view(), name='get_anon_ids'), - path('get_student_enrollment_status', api.get_student_enrollment_status, name="get_student_enrollment_status"), + path('get_student_enrollment_status', api.GetStudentEnrollmentStatus.as_view(), + name="get_student_enrollment_status"), path('get_student_progress_url', api.StudentProgressUrl.as_view(), name='get_student_progress_url'), path('reset_student_attempts', api.ResetStudentAttempts.as_view(), name='reset_student_attempts'), path('rescore_problem', api.rescore_problem, name='rescore_problem'), diff --git a/lms/djangoapps/instructor/views/serializer.py b/lms/djangoapps/instructor/views/serializer.py index 5d123ad66c81..59ac66ab838b 100644 --- a/lms/djangoapps/instructor/views/serializer.py +++ b/lms/djangoapps/instructor/views/serializer.py @@ -31,23 +31,14 @@ class Meta: fields = ['username', 'email', 'first_name', 'last_name'] -class AccessSerializer(serializers.Serializer): +class UniqueStudentIdentifierSerializer(serializers.Serializer): """ - Serializer for managing user access changes. - This serializer validates and processes the data required to modify - user access within a system. + Serializer for identifying unique_student. """ unique_student_identifier = serializers.CharField( max_length=255, help_text="Email or username of user to change access" ) - rolename = serializers.CharField( - help_text="Role name to assign to the user" - ) - action = serializers.ChoiceField( - choices=['allow', 'revoke'], - help_text="Action to perform on the user's access" - ) def validate_unique_student_identifier(self, value): """ @@ -61,6 +52,21 @@ def validate_unique_student_identifier(self, value): return user +class AccessSerializer(UniqueStudentIdentifierSerializer): + """ + Serializer for managing user access changes. + This serializer validates and processes the data required to modify + user access within a system. + """ + rolename = serializers.CharField( + help_text="Role name to assign to the user" + ) + action = serializers.ChoiceField( + choices=['allow', 'revoke'], + help_text="Action to perform on the user's access" + ) + + class ListInstructorTaskInputSerializer(serializers.Serializer): # pylint: disable=abstract-method """ Serializer for handling the input data for the problem response report generation API. From f65403975a5a07a1ade5ed5ddd37f8d194c792f0 Mon Sep 17 00:00:00 2001 From: Kyle McCormick Date: Wed, 25 Sep 2024 10:32:25 -0400 Subject: [PATCH 18/39] fix: don't wrap HTML data with newlines when serializing for LC (#35532) When serializing to OLX, the Learning Core runtime wraps HTML content in CDATA to avoid having to escape every individual `<`, `>`, and `&`. The runtime also puts newlines around the content within the CDATA, So, given HTML content `...`, we get ``. The problem is that every time you serialize an HTML block to OLX, it adds another pair of newlines. These newlines aren't visible to the end users, but they do make it so that importing and exporting content never reached a stable, aka "canonical" form. It also makes unit testing difficult, because the value of `html_block.data` becomes a moving target. We do not believe these newlines are necessary, so we have removed them from the `CDATA` block, and added a unit test to ensure that HTML blocks having a canonical serialization. Closes: https://github.com/openedx/edx-platform/issues/35525 --- .../content_libraries/tests/test_runtime.py | 82 +++++++++++++++++++ .../content_staging/tests/test_clipboard.py | 2 +- .../lib/xblock_serializer/block_serializer.py | 2 +- 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/openedx/core/djangoapps/content_libraries/tests/test_runtime.py b/openedx/core/djangoapps/content_libraries/tests/test_runtime.py index 89b8cdefd86b..f79808a7ec9a 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_staging/tests/test_clipboard.py b/openedx/core/djangoapps/content_staging/tests/test_clipboard.py index 00c4466b7d48..551f94e90e1a 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/lib/xblock_serializer/block_serializer.py b/openedx/core/lib/xblock_serializer/block_serializer.py index 966380f25061..f12bf5336af5 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 From 465e0af23002dc982f9273c2fffa41902fd6d6b1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 14:33:57 +0000 Subject: [PATCH 19/39] fix(deps): update dependency underscore to v1.13.0 [security] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 87c75af69044..edcf2a776e8d 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.0", "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.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.0.tgz", + "integrity": "sha512-sCs4H3pCytsb5K7i072FAEC9YlSYFIbosvM0tAKAlpSSUgD7yC1iXSEGdl5XrDKQ1YUB+p/HDzYrSG2H2Vl36g==", "license": "MIT" }, "node_modules/underscore.string": { diff --git a/package.json b/package.json index 3bd526c3b481..66da2b612830 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.0", "underscore.string": "3.3.6", "webpack": "^5.90.3", "webpack-bundle-tracker": "0.4.3", From cf614cb9c28052b5fb31ab78e1a4eddb6227b3d3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:00:40 +0000 Subject: [PATCH 20/39] feat: Upgrade Python dependency edx-enterprise (#35538) Bump the version to drop references to edx-rest-api-client that don't exist in the latest version. Commit generated by workflow `openedx/edx-platform/.github/workflows/upgrade-one-python-dependency.yml@refs/heads/master` Co-authored-by: feanil <781561+feanil@users.noreply.github.com> --- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index fffc9ac163b7..5afbe5f94e40 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.15 # Stay on LTS version, remove once this is added to common constraint Django<5.0 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 0b246816f559..097d21524190 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -467,7 +467,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.13 +edx-enterprise==4.25.15 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index e16f83b049f7..3fa131288294 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.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.15 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 2f916b40c535..1c12d28955c6 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -547,7 +547,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.13 +edx-enterprise==4.25.15 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 93a0313a1123..6e36d98f04d8 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -571,7 +571,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.13 +edx-enterprise==4.25.15 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt From 3768efa4142f15d032b0b677502b871b5fd59a7c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:18:18 +0000 Subject: [PATCH 21/39] fix(deps): update dependency underscore to v1.13.1 [security] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index edcf2a776e8d..82a6611d3abb 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.13.0", + "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.13.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.0.tgz", - "integrity": "sha512-sCs4H3pCytsb5K7i072FAEC9YlSYFIbosvM0tAKAlpSSUgD7yC1iXSEGdl5XrDKQ1YUB+p/HDzYrSG2H2Vl36g==", + "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 66da2b612830..1f48500e6e27 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.13.0", + "underscore": "1.13.1", "underscore.string": "3.3.6", "webpack": "^5.90.3", "webpack-bundle-tracker": "0.4.3", From 5446877a864f648fd42b79d82542ef9ae1172470 Mon Sep 17 00:00:00 2001 From: Jillian Date: Thu, 26 Sep 2024 03:59:18 +0930 Subject: [PATCH 22/39] Soft delete collections (#35496) * refactor: use django signals to trigger LIBRARY_COLLECTION events * refactor: use collection usage_key as search document id This change standardises the search document "id" to be a meilisearch ID generated from the usage key, for all types of indexed objects. This is important for collections so we can locate the collection document in the search index solely from the data provided by the LIBRARY_COLLECTION_DELETED event (library_key + collection_key), even if the collection has been deleted from the database. * refactor: avoid fetching more data than we have to. * get_library_collection_usage_key and searchable_doc_tags_for_collection do not need a Collection object; the usage key can be created from the library_key and collection_key. * updated searchable_doc_for_collection to require the parts of the collection usage key + an optional collection. This allows us to identify the collection's search document from its usage key without requiring an existing Collection object (in case it's been deleted). Also removes the edge case for indexing Collections not associated with a ContentLibrary -- this won't ever really happen. * feat: remove soft- and hard-deleted collections from search index * feat: adds library_component_usage_key to content_libraries.api * refactor: send CONTENT_OBJECT_ASSOCIATON_CHANGED on django model signals so that added/removed collections are removed/re-added to component documents. Special case: When a collection is soft-deleted/restored, we detect this in the search index and update the collection's component documents directly, without a CONTENT_OBJECT_ASSOCIATON_CHANGED signal. * chore: bumps openedx-learning to 0.13.0 --- openedx/core/djangoapps/content/search/api.py | 126 +++++++++++--- .../djangoapps/content/search/documents.py | 109 +++++++----- .../djangoapps/content/search/handlers.py | 2 + .../core/djangoapps/content/search/tasks.py | 15 +- .../content/search/tests/test_api.py | 116 ++++++++++++- .../content/search/tests/test_documents.py | 37 +---- .../core/djangoapps/content_libraries/api.py | 70 ++------ .../content_libraries/signal_handlers.py | 157 +++++++++++++++++- .../content_libraries/tests/test_api.py | 44 ++++- .../tests/test_views_collections.py | 27 ++- .../content_libraries/views_collections.py | 30 +++- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 16 files changed, 556 insertions(+), 187 deletions(-) diff --git a/openedx/core/djangoapps/content/search/api.py b/openedx/core/djangoapps/content/search/api.py index 7fe964128e5a..b5ed1bde78e1 100644 --- a/openedx/core/djangoapps/content/search/api.py +++ b/openedx/core/djangoapps/content/search/api.py @@ -15,7 +15,7 @@ from django.core.cache import cache from django.core.paginator import Paginator from meilisearch import Client as MeilisearchClient -from meilisearch.errors import MeilisearchError +from meilisearch.errors import MeilisearchApiError, MeilisearchError from meilisearch.models.task import TaskInfo from opaque_keys.edx.keys import UsageKey from opaque_keys.edx.locator import LibraryLocatorV2, LibraryCollectionLocator @@ -34,6 +34,7 @@ searchable_doc_for_course_block, searchable_doc_for_collection, searchable_doc_for_library_block, + searchable_doc_for_usage_key, searchable_doc_collections, searchable_doc_tags, searchable_doc_tags_for_collection, @@ -402,8 +403,8 @@ def index_collection_batch(batch, num_done, library_key) -> 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 f9041468c296..eabeab9654ca 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 f50dead8474a..085387d336b1 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 d9dad834db29..98390a12f3b3 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 4aa41a156dab..2f43896c197e 100644 --- a/openedx/core/djangoapps/content/search/tests/test_api.py +++ b/openedx/core/djangoapps/content/search/tests/test_api.py @@ -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 9d51bd127bb4..755ab4d19ad3 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_libraries/api.py b/openedx/core/djangoapps/content_libraries/api.py index 3dc33aec9616..a9601a4e70a7 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 @@ -242,10 +237,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 +781,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 +1111,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 +1132,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 +1145,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 +1163,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 +1233,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/signal_handlers.py b/openedx/core/djangoapps/content_libraries/signal_handlers.py index 768b49d55f22..fedee045a9f6 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 b02e71b002a3..8041c508dc31 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_views_collections.py b/openedx/core/djangoapps/content_libraries/tests/test_views_collections.py index bc600759b5b3..43c1627c2c76 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 2f40a1788628..b6c1c999ba94 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/requirements/constraints.txt b/requirements/constraints.txt index 5afbe5f94e40..43f000a1e8ba 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -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.0 # 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/base.txt b/requirements/edx/base.txt index 097d21524190..b725f8e0c2a4 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -824,7 +824,7 @@ openedx-filters==1.9.0 # -r requirements/edx/kernel.in # lti-consumer-xblock # ora2 -openedx-learning==0.11.5 +openedx-learning==0.13.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 3fa131288294..058214c647b7 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -1373,7 +1373,7 @@ openedx-filters==1.9.0 # -r requirements/edx/testing.txt # lti-consumer-xblock # ora2 -openedx-learning==0.11.5 +openedx-learning==0.13.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 1c12d28955c6..c20c28c2e443 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -983,7 +983,7 @@ openedx-filters==1.9.0 # -r requirements/edx/base.txt # lti-consumer-xblock # ora2 -openedx-learning==0.11.5 +openedx-learning==0.13.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 6e36d98f04d8..ab0d190b5d8e 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -1034,7 +1034,7 @@ openedx-filters==1.9.0 # -r requirements/edx/base.txt # lti-consumer-xblock # ora2 -openedx-learning==0.11.5 +openedx-learning==0.13.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt From d4dbc354bc8aeb62fe229c9c14236ffd8b4f655e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Wed, 25 Sep 2024 16:32:35 -0300 Subject: [PATCH 23/39] feat: return modified field on get component endpoint (#35508) --- .../content_libraries/serializers.py | 1 + .../tests/test_content_libraries.py | 25 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/openedx/core/djangoapps/content_libraries/serializers.py b/openedx/core/djangoapps/content_libraries/serializers.py index e9e04646ace4..51ba55cd6b48 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/tests/test_content_libraries.py b/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py index 677178bb3b31..d995a2c79683 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}", }) From ae0c295ead308820964f600cf49fe975907af6f7 Mon Sep 17 00:00:00 2001 From: Zachary Hancock Date: Wed, 25 Sep 2024 16:09:10 -0400 Subject: [PATCH 24/39] feat: pluggable url for idv location (#35494) * Adds an extension point when generating the url for id verification --- lms/djangoapps/verify_student/services.py | 6 +++- .../verify_student/tests/test_services.py | 33 ++++++++++++++++++- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 6 files changed, 41 insertions(+), 6 deletions(-) diff --git a/lms/djangoapps/verify_student/services.py b/lms/djangoapps/verify_student/services.py index f1c5543e8536..1a2d145e892a 100644 --- a/lms/djangoapps/verify_student/services.py +++ b/lms/djangoapps/verify_student/services.py @@ -11,6 +11,7 @@ from django.core.exceptions import ObjectDoesNotExist from django.utils.timezone import now from django.utils.translation import gettext as _ +from openedx_filters.learning.filters import IDVPageURLRequested from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.models import User @@ -244,7 +245,10 @@ def get_verify_location(cls, course_id=None): location = f'{settings.ACCOUNT_MICROFRONTEND_URL}/id-verification' if course_id: location += f'?course_id={quote(str(course_id))}' - return location + + # .. filter_implemented_name: IDVPageURLRequested + # .. filter_type: org.openedx.learning.idv.page.url.requested.v1 + return IDVPageURLRequested.run_filter(location) @classmethod def get_verification_details_by_id(cls, attempt_id): diff --git a/lms/djangoapps/verify_student/tests/test_services.py b/lms/djangoapps/verify_student/tests/test_services.py index 5351e3ede699..d57993d368af 100644 --- a/lms/djangoapps/verify_student/tests/test_services.py +++ b/lms/djangoapps/verify_student/tests/test_services.py @@ -9,10 +9,11 @@ import ddt from django.conf import settings -from django.test import TestCase +from django.test import TestCase, override_settings from django.utils.timezone import now from django.utils.translation import gettext as _ from freezegun import freeze_time +from openedx_filters import PipelineStep from pytz import utc from common.djangoapps.student.tests.factories import UserFactory @@ -33,6 +34,16 @@ } +class TestIdvPageUrlRequestedPipelineStep(PipelineStep): + """ Utility function to test a configured pipeline step """ + TEST_URL = 'example.com/verify' + + def run_filter(self, url): # pylint: disable=arguments-differ + return { + "url": self.TEST_URL + } + + @patch.dict(settings.VERIFY_STUDENT, FAKE_SETTINGS) @ddt.ddt class TestIDVerificationService(ModuleStoreTestCase): @@ -167,6 +178,26 @@ def test_get_verify_location_from_string(self): expected_path = f'{settings.ACCOUNT_MICROFRONTEND_URL}/id-verification' assert path == (expected_path + '?course_id=course-v1%3AedX%2BDemoX%2BDemo_Course') + @override_settings( + OPEN_EDX_FILTERS_CONFIG={ + "org.openedx.learning.idv.page.url.requested.v1": { + "pipeline": [ + "lms.djangoapps.verify_student.tests.test_services.TestIdvPageUrlRequestedPipelineStep", + ], + "fail_silently": False, + }, + }, + ) + def test_get_verify_location_with_filter_step(self): + """ + Test IDV flow location can be customized with an openedx filter + """ + url = IDVerificationService.get_verify_location() + assert url == TestIdvPageUrlRequestedPipelineStep.TEST_URL + + url = IDVerificationService.get_verify_location('course-v1:edX+DemoX+Demo_Course') + assert url == TestIdvPageUrlRequestedPipelineStep.TEST_URL + def test_get_expiration_datetime(self): """ Test that the latest expiration datetime is returned if there are multiple records diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index b725f8e0c2a4..d58ef131b3fd 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -819,7 +819,7 @@ 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 diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 058214c647b7..f7bf535c7e52 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -1367,7 +1367,7 @@ 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 diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index c20c28c2e443..a038ab52471b 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -978,7 +978,7 @@ 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 diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index ab0d190b5d8e..022feca9fcb3 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -1029,7 +1029,7 @@ 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 From b708f90ee00e0a1154bc57104e4c67cf97caefa1 Mon Sep 17 00:00:00 2001 From: katrinan029 Date: Wed, 25 Sep 2024 20:57:21 +0000 Subject: [PATCH 25/39] chore: version bump --- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 43f000a1e8ba..3b73c62955f6 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.15 +edx-enterprise==4.25.16 # Stay on LTS version, remove once this is added to common constraint Django<5.0 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index d58ef131b3fd..791ee21c359c 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -467,7 +467,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.15 +edx-enterprise==4.25.16 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index f7bf535c7e52..5a8bb199cc62 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -741,7 +741,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.15 +edx-enterprise==4.25.16 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index a038ab52471b..4beb084733d6 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -547,7 +547,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.15 +edx-enterprise==4.25.16 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 022feca9fcb3..04bc237de44d 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -571,7 +571,7 @@ edx-drf-extensions==10.3.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.15 +edx-enterprise==4.25.16 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt From 45d328f9fd75b0d8a761de419df1e25dd32a435a Mon Sep 17 00:00:00 2001 From: Tim McCormack Date: Thu, 26 Sep 2024 13:15:28 +0000 Subject: [PATCH 26/39] fix: Delete flaky test `test_get_user_group_id_for_partition` (#35545) This test failed on 2024-08-06 and 2024-09-24 but passed on re-run. Deleted according to flaky test process: https://openedx.atlassian.net/wiki/spaces/AC/pages/4306337795/Flaky+Test+Process Flaky test ticket: https://2u-internal.atlassian.net/browse/CR-7071 --- xmodule/partitions/tests/test_partitions.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/xmodule/partitions/tests/test_partitions.py b/xmodule/partitions/tests/test_partitions.py index fde6afd3141b..41f26b14db52 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 From f5e3635504c6390f764a503a6ee5e3858e1613d9 Mon Sep 17 00:00:00 2001 From: edX requirements bot Date: Thu, 26 Sep 2024 10:13:02 -0400 Subject: [PATCH 27/39] chore: Upgrade Python requirements --- requirements/edx-sandbox/base.txt | 18 +- requirements/edx/base.txt | 140 +++++++------- requirements/edx/coverage.txt | 2 +- requirements/edx/development.txt | 175 +++++++++--------- requirements/edx/doc.txt | 139 +++++++------- requirements/edx/paver.txt | 12 +- requirements/edx/semgrep.txt | 8 +- requirements/edx/testing.txt | 161 ++++++++-------- requirements/pip-tools.txt | 2 +- requirements/pip.txt | 2 +- .../structures_pruning/requirements/base.txt | 6 +- .../requirements/testing.txt | 8 +- scripts/user_retirement/requirements/base.txt | 47 +++-- .../user_retirement/requirements/testing.txt | 53 +++--- scripts/xblock/requirements.txt | 6 +- 15 files changed, 384 insertions(+), 395 deletions(-) diff --git a/requirements/edx-sandbox/base.txt b/requirements/edx-sandbox/base.txt index 4a7b0c0a7d35..1d94a4649a5f 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 791ee21c359c..5b98763a2e0f 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 @@ -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 @@ -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 a004eeeb9ffa..a1faf5e74025 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 5a8bb199cc62..5fc874dcf9f3 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 @@ -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 @@ -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 4beb084733d6..271ee7fe4d92 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 @@ -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 @@ -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 d86acae05f4f..a0b1896919d4 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 292f1319048d..102289def277 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 04bc237de44d..9ffe0fed904d 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 @@ -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 @@ -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 f7b35489c353..5bcb2aa55084 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 f0cf3d109992..36c777e21656 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 828a81a8d4ed..b80c660b8749 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 47648d50fddb..8be2e15973d0 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 3e5ed4738070..2fb9d4543e21 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 f7464dfa0602..73795fc6223e 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 5b7f07242e6d..81ed56ea694a 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 From 740921ae21b158acbd18ebbfc3f7bc8c5fe674ab Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Wed, 25 Sep 2024 12:48:49 -0400 Subject: [PATCH 28/39] build: Manually pull some RTD Context. See https://about.readthedocs.com/blog/2024/07/addons-by-default/ for details but essentially RTD is changing how it's building docs and this will let us handle the change gracefully. --- docs/conf.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index f37fc32f6160..01280c6cd214 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -258,6 +258,16 @@ epub_exclude_files = ['search.html'] +# -- Read the Docs Specific Configuration +# Define the canonical URL if you are using a custom domain on Read the Docs +html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "") + +# Tell Jinja2 templates the build is running on Read the Docs +if os.environ.get("READTHEDOCS", "") == "True": + if "html_context" not in globals(): + html_context = {} + html_context["READTHEDOCS"] = True + # -- Extension configuration ------------------------------------------------- # -- Options for intersphinx extension --------------------------------------- From 8f47c0b2744801b667d752ab5f066253b8f74582 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Thu, 26 Sep 2024 09:34:58 -0700 Subject: [PATCH 29/39] fix: whitespace issues in some capa problem index_dictionary content (#35543) --- .../content/search/tests/test_api.py | 4 +- .../content/search/tests/test_handlers.py | 2 +- xmodule/capa_block.py | 6 +- xmodule/tests/test_capa_block.py | 57 ++++++++++++------- 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/openedx/core/djangoapps/content/search/tests/test_api.py b/openedx/core/djangoapps/content/search/tests/test_api.py index 2f43896c197e..4c6227af309f 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, diff --git a/openedx/core/djangoapps/content/search/tests/test_handlers.py b/openedx/core/djangoapps/content/search/tests/test_handlers.py index 8a6627e3902d..bdc4814d1c8f 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/xmodule/capa_block.py b/xmodule/capa_block.py index 54ca0cbc312f..c1c650144b05 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/tests/test_capa_block.py b/xmodule/tests/test_capa_block.py index d1c01e109718..c81b137f1b23 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): """ From 67b490cab46c6725528af2d8b0e53be8a45e79e0 Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Thu, 26 Sep 2024 09:35:17 -0700 Subject: [PATCH 30/39] fix: suppress errors+warnings when video is used in a content library (#35544) --- .../contentstore/views/transcripts_ajax.py | 3 +++ xmodule/video_block/transcripts_utils.py | 2 +- xmodule/video_block/video_block.py | 18 ++++++++++++------ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/cms/djangoapps/contentstore/views/transcripts_ajax.py b/cms/djangoapps/contentstore/views/transcripts_ajax.py index 892b76caae72..8cb7f455013b 100644 --- a/cms/djangoapps/contentstore/views/transcripts_ajax.py +++ b/cms/djangoapps/contentstore/views/transcripts_ajax.py @@ -649,6 +649,9 @@ def _get_item(request, data): Returns the item. """ usage_key = UsageKey.from_string(data.get('locator')) + if not usage_key.context_key.is_course: + # TODO: implement transcript support for learning core / content libraries. + raise TranscriptsRequestValidationException(_('Transcripts are not yet supported in content libraries.')) # This is placed before has_course_author_access() to validate the location, # because has_course_author_access() raises r if location is invalid. item = modulestore().get_item(usage_key) diff --git a/xmodule/video_block/transcripts_utils.py b/xmodule/video_block/transcripts_utils.py index e41f925295f0..132b8cff1e14 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 b4fddb63fa7a..782645c373b0 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 From ad78699605a7923e00c1d125b08b20379e1c4d5e Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Thu, 26 Sep 2024 12:46:10 -0400 Subject: [PATCH 31/39] test: Use the correct exception. This test doesn't actually care about the type of the exception but use the Requests exception that you're likely to get instead of the edx-restapi-client/slumber one from before we dropped them. --- .../djangoapps/user_api/accounts/tests/test_settings_views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 874497664d9d..badee6e87554 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 From 2a3a0c489d81fc42356dae7e143be860f1c887dd Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Thu, 26 Sep 2024 12:59:15 -0400 Subject: [PATCH 32/39] test: Update a test based on changes to pytz. pytz dropped the Asia/Almaty timezone according to IANA https://github.com/stub42/pytz/commit/640c9bd426a3e62f12e7d5424d936b91dc442d93#diff-16061815f611262054e469307ca063a4ef47e158a97784f1e91d254f074324bfR72 --- openedx/core/djangoapps/user_api/tests/test_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openedx/core/djangoapps/user_api/tests/test_views.py b/openedx/core/djangoapps/user_api/tests/test_views.py index 981cc52dfd8f..75740cf5d2b6 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): From dc27196f17573c8d2e35b8bdaf0b9054ddf4daa8 Mon Sep 17 00:00:00 2001 From: Jillian Date: Fri, 27 Sep 2024 03:36:03 +0930 Subject: [PATCH 33/39] chore: bumps openedx-learning to 0.13.1 (#35547) --- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 3b73c62955f6..a81772df2ada 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -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.13.0 +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/base.txt b/requirements/edx/base.txt index 791ee21c359c..19aa21d6e795 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -824,7 +824,7 @@ openedx-filters==1.10.0 # -r requirements/edx/kernel.in # lti-consumer-xblock # ora2 -openedx-learning==0.13.0 +openedx-learning==0.13.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 5a8bb199cc62..b6067a6aa092 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -1373,7 +1373,7 @@ openedx-filters==1.10.0 # -r requirements/edx/testing.txt # lti-consumer-xblock # ora2 -openedx-learning==0.13.0 +openedx-learning==0.13.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 4beb084733d6..420266fb6ad3 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -983,7 +983,7 @@ openedx-filters==1.10.0 # -r requirements/edx/base.txt # lti-consumer-xblock # ora2 -openedx-learning==0.13.0 +openedx-learning==0.13.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 04bc237de44d..717c8fda2aad 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -1034,7 +1034,7 @@ openedx-filters==1.10.0 # -r requirements/edx/base.txt # lti-consumer-xblock # ora2 -openedx-learning==0.13.0 +openedx-learning==0.13.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt From 338d58dd537cce9da83992991e2ab4925f7a787f Mon Sep 17 00:00:00 2001 From: katrinan029 Date: Thu, 26 Sep 2024 18:30:03 +0000 Subject: [PATCH 34/39] chore: version bump --- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index a81772df2ada..71e09f532a52 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.16 +edx-enterprise==4.25.17 # Stay on LTS version, remove once this is added to common constraint Django<5.0 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index ad07106dda39..67facfbfb742 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -467,7 +467,7 @@ edx-drf-extensions==10.4.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.16 +edx-enterprise==4.25.17 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 647bc64c466d..837fade8889d 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -741,7 +741,7 @@ edx-drf-extensions==10.4.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.16 +edx-enterprise==4.25.17 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index bb761307f461..8444539edf4f 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -547,7 +547,7 @@ edx-drf-extensions==10.4.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.16 +edx-enterprise==4.25.17 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index ce4850b78292..35d00344feb9 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -571,7 +571,7 @@ edx-drf-extensions==10.4.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.16 +edx-enterprise==4.25.17 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt From 624bef547c66da17048e29ff5d80126a665f1a0d Mon Sep 17 00:00:00 2001 From: katrinan029 Date: Thu, 26 Sep 2024 19:25:05 +0000 Subject: [PATCH 35/39] fix: bump version --- requirements/constraints.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 71e09f532a52..32516e724108 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.17 +edx-enterprise==4.25.17 # Stay on LTS version, remove once this is added to common constraint Django<5.0 From a2bc9e8d806e2ff81f1eacaabec73d5483511ba4 Mon Sep 17 00:00:00 2001 From: katrinan029 Date: Thu, 26 Sep 2024 19:25:52 +0000 Subject: [PATCH 36/39] chore: version bump --- requirements/constraints.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 32516e724108..71e09f532a52 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.17 +edx-enterprise==4.25.17 # Stay on LTS version, remove once this is added to common constraint Django<5.0 From dac0309b0ff590ebaba5cf52e8fe518bd0c38270 Mon Sep 17 00:00:00 2001 From: Isaac Lee <124631592+ilee2u@users.noreply.github.com> Date: Thu, 26 Sep 2024 16:58:01 -0400 Subject: [PATCH 37/39] fix: add should_display_status_to_user method and status_changed field to VerificationAttempt model (#35514) * fix: add placeholder should_display_status_to_user * fix: have VerificationAttempt inherit StatusModel - should_display_status_to_user now returns False * chore: makemigrations * feat: status_changed field added * temp: idea to add should_display_status_to_user * feat: add should_display_status_to_user * fix: correct call in helpers+services * chore: lint+test fix * fix: default hide_status_user as False * chore: rename field call to STATUS * chore: remove extra status field - comment cleanup * temp: lint + comment out created status for now * fix: revamp status_changed for back-compat * fix: override save for status_changed * fix: replace created/updated instead of status - also made migrations * fix: squash commits - also remove extra updated_at property * chore: nits --- lms/djangoapps/verify_student/api.py | 11 +++- ...ve_verificationattempt_created_and_more.py | 50 +++++++++++++++++++ lms/djangoapps/verify_student/models.py | 24 ++++++--- lms/djangoapps/verify_student/services.py | 4 +- 4 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 lms/djangoapps/verify_student/migrations/0016_remove_verificationattempt_created_and_more.py diff --git a/lms/djangoapps/verify_student/api.py b/lms/djangoapps/verify_student/api.py index 941dd60453d4..7b8310fde030 100644 --- a/lms/djangoapps/verify_student/api.py +++ b/lms/djangoapps/verify_student/api.py @@ -54,7 +54,13 @@ def send_approval_email(attempt): send_verification_approved_email(context=email_context) -def create_verification_attempt(user: User, name: str, status: str, expiration_datetime: Optional[datetime] = None): +def create_verification_attempt( + user: User, + name: str, + status: str, + expiration_datetime: Optional[datetime] = None, + hide_status_from_user: Optional[bool] = False, +): """ Create a verification attempt. @@ -74,6 +80,7 @@ def create_verification_attempt(user: User, name: str, status: str, expiration_d name=name, status=status, expiration_datetime=expiration_datetime, + hide_status_from_user=hide_status_from_user, ) emit_idv_attempt_created_event( @@ -129,7 +136,7 @@ def update_verification_attempt( 'Status must be one of: %(status_list)s', { 'status': status, - 'status_list': VerificationAttempt.STATUS_CHOICES, + 'status_list': VerificationAttempt.STATUS, }, ) raise VerificationAttemptInvalidStatus diff --git a/lms/djangoapps/verify_student/migrations/0016_remove_verificationattempt_created_and_more.py b/lms/djangoapps/verify_student/migrations/0016_remove_verificationattempt_created_and_more.py new file mode 100644 index 000000000000..d972dba3dbbd --- /dev/null +++ b/lms/djangoapps/verify_student/migrations/0016_remove_verificationattempt_created_and_more.py @@ -0,0 +1,50 @@ +# Generated by Django 4.2.15 on 2024-09-26 20:08 + +from django.db import migrations, models +import django.utils.timezone +import lms.djangoapps.verify_student.statuses +import model_utils.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('verify_student', '0015_verificationattempt'), + ] + + operations = [ + migrations.RemoveField( + model_name='verificationattempt', + name='created', + ), + migrations.RemoveField( + model_name='verificationattempt', + name='modified', + ), + migrations.AddField( + model_name='verificationattempt', + name='created_at', + field=models.DateTimeField(auto_now_add=True, db_index=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='verificationattempt', + name='hide_status_from_user', + field=models.BooleanField(default=False, null=True), + ), + migrations.AddField( + model_name='verificationattempt', + name='status_changed', + field=model_utils.fields.MonitorField(default=django.utils.timezone.now, monitor='status', verbose_name='status changed'), + ), + migrations.AddField( + model_name='verificationattempt', + name='updated_at', + field=models.DateTimeField(auto_now=True, db_index=True), + ), + migrations.AlterField( + model_name='verificationattempt', + name='status', + field=model_utils.fields.StatusField(choices=[(lms.djangoapps.verify_student.statuses.VerificationAttemptStatus['CREATED'], lms.djangoapps.verify_student.statuses.VerificationAttemptStatus['CREATED']), (lms.djangoapps.verify_student.statuses.VerificationAttemptStatus['PENDING'], lms.djangoapps.verify_student.statuses.VerificationAttemptStatus['PENDING']), (lms.djangoapps.verify_student.statuses.VerificationAttemptStatus['APPROVED'], lms.djangoapps.verify_student.statuses.VerificationAttemptStatus['APPROVED']), (lms.djangoapps.verify_student.statuses.VerificationAttemptStatus['DENIED'], lms.djangoapps.verify_student.statuses.VerificationAttemptStatus['DENIED'])], default=lms.djangoapps.verify_student.statuses.VerificationAttemptStatus['CREATED'], max_length=100, no_check_for_status=True, verbose_name='status'), + ), + ] diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py index 23729c99a0b9..9a0ac369640a 100644 --- a/lms/djangoapps/verify_student/models.py +++ b/lms/djangoapps/verify_student/models.py @@ -15,9 +15,11 @@ import logging import os.path import uuid + from datetime import timedelta from email.utils import formatdate + import requests from config_models.models import ConfigurationModel from django.conf import settings @@ -1214,7 +1216,7 @@ def __str__(self): return str(self.arguments) -class VerificationAttempt(TimeStampedModel): +class VerificationAttempt(StatusModel): """ The model represents impelementation-agnostic information about identity verification (IDV) attempts. @@ -1224,23 +1226,29 @@ class VerificationAttempt(TimeStampedModel): user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE) name = models.CharField(blank=True, max_length=255) - STATUS_CHOICES = [ + STATUS = Choices( VerificationAttemptStatus.CREATED, VerificationAttemptStatus.PENDING, VerificationAttemptStatus.APPROVED, VerificationAttemptStatus.DENIED, - ] - status = models.CharField(max_length=64, choices=[(status, status) for status in STATUS_CHOICES]) + ) expiration_datetime = models.DateTimeField( null=True, blank=True, ) - @property - def updated_at(self): - """Backwards compatibility with existing IDVerification models""" - return self.modified + hide_status_from_user = models.BooleanField( + default=False, + null=True, + ) + + created_at = models.DateTimeField(auto_now_add=True, db_index=True) + updated_at = models.DateTimeField(auto_now=True, db_index=True) + + def should_display_status_to_user(self): + """When called, returns true or false based on the type of VerificationAttempt""" + return not self.hide_status_from_user @classmethod def retire_user(cls, user_id): diff --git a/lms/djangoapps/verify_student/services.py b/lms/djangoapps/verify_student/services.py index 1a2d145e892a..f0d8a8631482 100644 --- a/lms/djangoapps/verify_student/services.py +++ b/lms/djangoapps/verify_student/services.py @@ -76,7 +76,7 @@ def verifications_for_user(cls, user): Return a list of all verifications associated with the given user. """ verifications = [] - for verification in chain(VerificationAttempt.objects.filter(user=user).order_by('-created'), + for verification in chain(VerificationAttempt.objects.filter(user=user).order_by('-created_at'), SoftwareSecurePhotoVerification.objects.filter(user=user).order_by('-created_at'), SSOVerification.objects.filter(user=user).order_by('-created_at'), ManualVerification.objects.filter(user=user).order_by('-created_at')): @@ -97,7 +97,7 @@ def get_verified_user_ids(cls, users): VerificationAttempt.objects.filter(**{ 'user__in': users, 'status': 'approved', - 'created__gt': now() - timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]) + 'created_at__gt': now() - timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"]) }).values_list('user_id', flat=True), SoftwareSecurePhotoVerification.objects.filter(**filter_kwargs).values_list('user_id', flat=True), SSOVerification.objects.filter(**filter_kwargs).values_list('user_id', flat=True), From 19b03ddf181f9bcf5627fac00c64e044bb6d3e68 Mon Sep 17 00:00:00 2001 From: sameeramin <35958006+sameeramin@users.noreply.github.com> Date: Fri, 27 Sep 2024 14:36:21 +0000 Subject: [PATCH 38/39] feat: Upgrade Python dependency edx-enterprise Removed loggings for SAPSF channel Commit generated by workflow `openedx/edx-platform/.github/workflows/upgrade-one-python-dependency.yml@refs/heads/master` --- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 71e09f532a52..5ae1118f09b6 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.17 +edx-enterprise==4.25.19 # Stay on LTS version, remove once this is added to common constraint Django<5.0 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 67facfbfb742..1cc1b1bcdd1c 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -467,7 +467,7 @@ edx-drf-extensions==10.4.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.17 +edx-enterprise==4.25.19 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 837fade8889d..aa2e3e62e814 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -741,7 +741,7 @@ edx-drf-extensions==10.4.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.17 +edx-enterprise==4.25.19 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 8444539edf4f..f46a637a0804 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -547,7 +547,7 @@ edx-drf-extensions==10.4.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.17 +edx-enterprise==4.25.19 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 35d00344feb9..96bfd2861986 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -571,7 +571,7 @@ edx-drf-extensions==10.4.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.25.17 +edx-enterprise==4.25.19 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt From de7fa096673d2b38df1ae48e4b1e8e3de294f0b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Ch=C3=A1vez?= Date: Fri, 27 Sep 2024 12:10:05 -0500 Subject: [PATCH 39/39] chore: delete unused tags_count from LibraryXBlockMetadata (#35510) --- openedx/core/djangoapps/content_libraries/api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openedx/core/djangoapps/content_libraries/api.py b/openedx/core/djangoapps/content_libraries/api.py index a9601a4e70a7..b9f3779af539 100644 --- a/openedx/core/djangoapps/content_libraries/api.py +++ b/openedx/core/djangoapps/content_libraries/api.py @@ -218,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