From 03e43c71dc09471a9c7bcebf24be0644b529bd6c Mon Sep 17 00:00:00 2001 From: edX requirements bot Date: Sun, 8 Dec 2024 21:34:34 -0500 Subject: [PATCH 01/12] chore: Upgrade Python requirements --- requirements/ci.txt | 4 +- requirements/dev.txt | 60 ++++----- requirements/django.txt | 2 +- requirements/doc.txt | 48 ++++---- requirements/edx-platform-constraints.txt | 143 ++++++++++++---------- requirements/js_test.txt | 4 +- requirements/test-master.txt | 44 +++---- requirements/test.txt | 46 +++---- 8 files changed, 179 insertions(+), 172 deletions(-) diff --git a/requirements/ci.txt b/requirements/ci.txt index eaa5c1ca4..f6c3374e6 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -18,11 +18,11 @@ pluggy==1.5.0 # via tox py==1.11.0 # via tox -six==1.16.0 +six==1.17.0 # via tox tox==3.28.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r requirements/ci.in -virtualenv==20.27.1 +virtualenv==20.28.0 # via tox diff --git a/requirements/dev.txt b/requirements/dev.txt index af722c36a..a463e0d4a 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -8,13 +8,13 @@ accessible-pygments==0.0.5 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # pydata-sphinx-theme -aiohappyeyeballs==2.4.3 +aiohappyeyeballs==2.4.4 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # aiohttp -aiohttp==3.10.10 +aiohttp==3.11.9 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -55,7 +55,7 @@ asn1crypto==1.5.1 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # snowflake-connector-python -astroid==3.3.5 +astroid==3.3.6 # via # pylint # pylint-celery @@ -72,7 +72,7 @@ babel==2.16.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # pydata-sphinx-theme # sphinx -bcrypt==4.2.0 +bcrypt==4.2.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -88,7 +88,7 @@ billiard==4.2.1 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # celery -bleach==6.1.0 +bleach==6.2.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -161,18 +161,18 @@ click-repl==0.3.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # celery -code-annotations==1.8.0 +code-annotations==2.0.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # edx-lint # edx-toggles -coverage[toml]==7.6.7 +coverage[toml]==7.6.9 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # pytest-cov -cryptography==43.0.3 +cryptography==44.0.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -198,7 +198,7 @@ dill==0.3.9 # via pylint distlib==0.3.9 # via virtualenv -django==4.2.16 +django==4.2.17 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/common_constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt @@ -288,7 +288,7 @@ django-simple-history==3.1.1 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt -django-waffle==4.1.0 +django-waffle==4.2.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -349,13 +349,13 @@ edx-braze-client==0.2.5 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt -edx-ccx-keys==1.3.0 +edx-ccx-keys==2.0.2 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # openedx-events -edx-django-utils==7.0.0 +edx-django-utils==7.1.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -408,7 +408,7 @@ factory-boy==3.3.1 # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt -faker==33.0.0 +faker==33.1.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt @@ -520,13 +520,13 @@ multidict==6.1.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # aiohttp # yarl -newrelic==10.2.0 +newrelic==10.3.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # edx-django-utils -nh3==0.2.18 +nh3==0.2.19 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # readme-renderer @@ -546,7 +546,7 @@ openedx-events==9.15.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt -packaging==24.1 +packaging==24.2 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -592,7 +592,7 @@ pillow==11.0.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt pip-tools==7.4.1 # via -r requirements/dev.in -pkginfo==1.11.2 +pkginfo==1.12.0 # via twine platformdirs==4.3.6 # via @@ -617,11 +617,12 @@ prompt-toolkit==3.0.48 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # click-repl -propcache==0.2.0 +propcache==0.2.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt + # aiohttp # yarl psutil==6.1.0 # via @@ -665,7 +666,7 @@ pygments==2.18.0 # pydata-sphinx-theme # readme-renderer # sphinx -pyjwt[crypto]==2.9.0 +pyjwt[crypto]==2.10.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -674,7 +675,7 @@ pyjwt[crypto]==2.9.0 # edx-drf-extensions # edx-rest-api-client # snowflake-connector-python -pylint==3.3.1 +pylint==3.3.2 # via # edx-lint # pylint-celery @@ -701,7 +702,7 @@ pynacl==1.5.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # edx-django-utils # paramiko -pyopenssl==24.2.1 +pyopenssl==24.3.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -798,12 +799,11 @@ semantic-version==2.10.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # edx-drf-extensions -six==1.16.0 +six==1.17.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt - # bleach # edx-ccx-keys # edx-lint # edx-rbac @@ -822,7 +822,7 @@ snowballstemmer==2.2.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # pydocstyle # sphinx -snowflake-connector-python==3.12.3 +snowflake-connector-python==3.12.4 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -868,13 +868,13 @@ sphinxcontrib-serializinghtml==2.0.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # sphinx -sqlparse==0.5.1 +sqlparse==0.5.2 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # django -stevedore==5.3.0 +stevedore==5.4.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -911,7 +911,7 @@ tox==3.28.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r requirements/dev.in -tqdm==4.66.6 +tqdm==4.67.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -963,7 +963,7 @@ vine==5.1.0 # amqp # celery # kombu -virtualenv==20.27.1 +virtualenv==20.28.0 # via tox wcwidth==0.2.13 # via @@ -977,11 +977,11 @@ webencodings==0.5.1 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # bleach -wheel==0.45.0 +wheel==0.45.1 # via # -r requirements/dev.in # pip-tools -yarl==1.17.0 +yarl==1.18.3 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt diff --git a/requirements/django.txt b/requirements/django.txt index 64aaf996f..ebf97308f 100644 --- a/requirements/django.txt +++ b/requirements/django.txt @@ -1 +1 @@ -django==4.2.16 +django==4.2.17 diff --git a/requirements/doc.txt b/requirements/doc.txt index b28df822e..b0f8c51f5 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -6,11 +6,11 @@ # accessible-pygments==0.0.5 # via pydata-sphinx-theme -aiohappyeyeballs==2.4.3 +aiohappyeyeballs==2.4.4 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp -aiohttp==3.10.10 +aiohttp==3.11.9 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # openai @@ -47,7 +47,7 @@ babel==2.16.0 # via # pydata-sphinx-theme # sphinx -bcrypt==4.2.0 +bcrypt==4.2.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # paramiko @@ -57,7 +57,7 @@ billiard==4.2.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # celery -bleach==6.1.0 +bleach==6.2.0 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt celery==5.4.0 # via @@ -100,11 +100,11 @@ click-repl==0.3.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # celery -code-annotations==1.8.0 +code-annotations==2.0.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-toggles -cryptography==43.0.3 +cryptography==44.0.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # django-fernet-fields-v2 @@ -118,7 +118,7 @@ defusedxml==0.7.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # djangorestframework-xml -django==4.2.16 +django==4.2.17 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/common_constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -172,7 +172,7 @@ django-simple-history==3.1.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt -django-waffle==4.1.0 +django-waffle==4.2.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-django-utils @@ -214,11 +214,11 @@ edx-api-doc-tools==2.0.0 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt edx-braze-client==0.2.5 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt -edx-ccx-keys==1.3.0 +edx-ccx-keys==2.0.2 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # openedx-events -edx-django-utils==7.0.0 +edx-django-utils==7.1.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # django-config-models @@ -248,7 +248,7 @@ factory-boy==3.3.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r requirements/doc.in -faker==33.0.0 +faker==33.1.0 # via factory-boy fastavro==1.9.7 # via @@ -303,11 +303,11 @@ multidict==6.1.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp # yarl -newrelic==10.2.0 +newrelic==10.3.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-django-utils -nh3==0.2.18 +nh3==0.2.19 # via readme-renderer oauthlib==3.2.2 # via @@ -317,7 +317,7 @@ openai==0.28.1 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt openedx-events==9.15.0 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt -packaging==24.1 +packaging==24.2 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # drf-yasg @@ -350,9 +350,10 @@ prompt-toolkit==3.0.48 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # click-repl -propcache==0.2.0 +propcache==0.2.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt + # aiohttp # yarl psutil==6.1.0 # via @@ -377,7 +378,7 @@ pygments==2.18.0 # pydata-sphinx-theme # readme-renderer # sphinx -pyjwt[crypto]==2.9.0 +pyjwt[crypto]==2.10.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # drf-jwt @@ -393,7 +394,7 @@ pynacl==1.5.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-django-utils # paramiko -pyopenssl==24.2.1 +pyopenssl==24.3.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # snowflake-connector-python @@ -447,10 +448,9 @@ semantic-version==2.10.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-drf-extensions -six==1.16.0 +six==1.17.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt - # bleach # edx-ccx-keys # edx-rbac # python-dateutil @@ -458,7 +458,7 @@ slumber==0.7.1 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt snowballstemmer==2.2.0 # via sphinx -snowflake-connector-python==3.12.3 +snowflake-connector-python==3.12.4 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt sortedcontainers==2.4.0 # via @@ -485,11 +485,11 @@ sphinxcontrib-qthelp==2.0.0 # via sphinx sphinxcontrib-serializinghtml==2.0.0 # via sphinx -sqlparse==0.5.1 +sqlparse==0.5.2 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # django -stevedore==5.3.0 +stevedore==5.4.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # code-annotations @@ -508,7 +508,7 @@ tomlkit==0.13.2 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # snowflake-connector-python -tqdm==4.66.6 +tqdm==4.67.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # openai @@ -550,7 +550,7 @@ webencodings==0.5.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # bleach -yarl==1.17.0 +yarl==1.18.3 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp diff --git a/requirements/edx-platform-constraints.txt b/requirements/edx-platform-constraints.txt index a316d03a8..fc270263a 100644 --- a/requirements/edx-platform-constraints.txt +++ b/requirements/edx-platform-constraints.txt @@ -8,9 +8,9 @@ # via -r requirements/edx/github.in acid-xblock==0.4.1 # via -r requirements/edx/kernel.in -aiohappyeyeballs==2.4.3 +aiohappyeyeballs==2.4.4 # via aiohttp -aiohttp==3.10.10 +aiohttp==3.11.9 # via # geoip2 # openai @@ -53,12 +53,12 @@ babel==2.16.0 # enmerkar-underscore backoff==1.10.0 # via analytics-python -bcrypt==4.2.0 +bcrypt==4.2.1 # via paramiko beautifulsoup4==4.12.3 # via pynliner # via celery -bleach[css]==6.1.0 +bleach[css]==6.2.0 # via # edx-enterprise # lti-consumer-xblock @@ -68,20 +68,20 @@ bleach[css]==6.1.0 # xblock-poll boto==2.49.0 # via -r requirements/edx/kernel.in -boto3==1.35.50 +boto3==1.35.76 # via # -r requirements/edx/kernel.in # django-ses # fs-s3fs # ora2 -botocore==1.35.50 +botocore==1.35.76 # via # -r requirements/edx/kernel.in # boto3 # s3transfer bridgekeeper==0.9 # via -r requirements/edx/kernel.in -cachecontrol==0.14.0 +cachecontrol==0.14.1 # via firebase-admin cachetools==5.5.0 # via google-auth @@ -132,7 +132,7 @@ chem==1.3.0 click-plugins==1.1.1 # via celery # via celery -code-annotations==1.8.0 +code-annotations==2.0.0 # via # edx-enterprise # edx-toggles @@ -140,7 +140,7 @@ codejail-includes==1.0.0 # via -r requirements/edx/kernel.in crowdsourcehinter-xblock==0.8 # via -r requirements/edx/bundled.in -cryptography==43.0.3 +cryptography==44.0.0 # via # -r requirements/edx/kernel.in # django-fernet-fields-v2 @@ -162,7 +162,7 @@ defusedxml==0.7.1 # ora2 # python3-openid # social-auth-core -django==4.2.16 +django==4.2.17 # via # -c requirements/edx/../common_constraints.txt # -c requirements/edx/../constraints.txt @@ -247,7 +247,7 @@ django-config-models==2.7.0 # edx-enterprise # edx-name-affirmation # lti-consumer-xblock -django-cors-headers==4.5.0 +django-cors-headers==4.6.0 # via -r requirements/edx/kernel.in django-countries==7.6.1 # via @@ -300,7 +300,7 @@ django-mptt==0.16.0 # -r requirements/edx/kernel.in # openedx-django-wiki django-multi-email-field==0.7.0 -django-mysql==4.14.0 +django-mysql==4.15.0 # via -r requirements/edx/kernel.in django-oauth-toolkit==1.7.1 # via @@ -318,7 +318,7 @@ django-sekizai==4.1.0 # via # -r requirements/edx/kernel.in # openedx-django-wiki -django-ses==4.2.0 +django-ses==4.3.0 # via -r requirements/edx/bundled.in # via # -c requirements/edx/../constraints.txt @@ -328,12 +328,13 @@ django-ses==4.2.0 # edx-organizations # edx-proctoring # ora2 -django-statici18n==2.5.0 +django-statici18n==2.6.0 # via # -r requirements/edx/kernel.in # lti-consumer-xblock # xblock-drag-and-drop-v2 # xblock-poll + # xblocks-contrib django-storages==1.14.3 # via # -c requirements/edx/../constraints.txt @@ -341,7 +342,7 @@ django-storages==1.14.3 # edxval django-user-tasks==3.2.0 # via -r requirements/edx/kernel.in -django-waffle==4.1.0 +django-waffle==4.2.0 # via # -r requirements/edx/kernel.in # edx-django-utils @@ -383,7 +384,7 @@ done-xblock==2.4.0 # via -r requirements/edx/bundled.in drf-jwt==1.19.2 # via edx-drf-extensions -drf-spectacular==0.27.2 +drf-spectacular==0.28.0 # via -r requirements/edx/kernel.in drf-yasg==1.21.8 # via @@ -405,7 +406,7 @@ edx-bulk-grades==1.1.0 # via # -r requirements/edx/kernel.in # staff-graded-xblock -edx-ccx-keys==1.3.0 +edx-ccx-keys==2.0.2 # via # -r requirements/edx/kernel.in # lti-consumer-xblock @@ -415,9 +416,9 @@ edx-celeryutils==1.3.0 # -r requirements/edx/kernel.in # edx-name-affirmation # super-csv -edx-codejail==3.5.1 +edx-codejail==3.5.2 # via -r requirements/edx/kernel.in -edx-completion==4.7.3 +edx-completion==4.7.6 # via -r requirements/edx/kernel.in edx-django-release-util==1.4.0 # via @@ -426,7 +427,7 @@ edx-django-release-util==1.4.0 # edxval edx-django-sites-extensions==4.2.0 # via -r requirements/edx/kernel.in -edx-django-utils==7.0.0 +edx-django-utils==7.1.0 # via # -r requirements/edx/kernel.in # django-config-models @@ -455,7 +456,7 @@ edx-drf-extensions==10.5.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.32.2 +edx-enterprise==5.3.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in @@ -468,6 +469,7 @@ edx-i18n-tools==1.5.0 # -c requirements/edx/../constraints.txt # -r requirements/edx/bundled.in # ora2 + # xblocks-contrib edx-milestones==0.6.0 # via -r requirements/edx/kernel.in edx-name-affirmation==3.0.1 @@ -490,7 +492,7 @@ edx-opaque-keys[django]==2.11.0 # ora2 edx-organizations==6.13.0 # via -r requirements/edx/kernel.in -edx-proctoring==4.18.3 +edx-proctoring==5.0.1 # via # -r requirements/edx/kernel.in # edx-proctoring-proctortrack @@ -504,7 +506,7 @@ edx-search==4.1.1 # via -r requirements/edx/kernel.in edx-sga==0.25.0 # via -r requirements/edx/bundled.in -edx-submissions==3.8.2 +edx-submissions==3.8.3 # via # -r requirements/edx/kernel.in # ora2 @@ -527,7 +529,7 @@ edx-when==2.5.0 # via # -r requirements/edx/kernel.in # edx-proctoring -edxval==2.6.0 +edxval==2.6.1 # via -r requirements/edx/kernel.in elasticsearch==7.9.1 # via @@ -548,7 +550,7 @@ fastavro==1.9.7 # via openedx-events filelock==3.16.1 # via snowflake-connector-python -firebase-admin==6.5.0 +firebase-admin==6.6.0 # via edx-ace frozenlist==1.5.0 # via @@ -566,20 +568,20 @@ fs-s3fs==0.1.8 # openedx-django-pyfs future==1.0.0 # via pyjwkest -geoip2==4.8.0 +geoip2==4.8.1 # via -r requirements/edx/kernel.in glob2==0.7 # via -r requirements/edx/kernel.in -google-api-core[grpc]==2.22.0 +google-api-core[grpc]==2.23.0 # via # firebase-admin # google-api-python-client # google-cloud-core # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.149.0 +google-api-python-client==2.154.0 # via firebase-admin -google-auth==2.35.0 +google-auth==2.36.0 # via # google-api-core # google-api-python-client @@ -595,7 +597,7 @@ google-cloud-core==2.4.1 # google-cloud-storage google-cloud-firestore==2.19.0 # via firebase-admin -google-cloud-storage==2.18.2 +google-cloud-storage==2.19.0 # via firebase-admin google-crc32c==1.6.0 # via @@ -603,15 +605,15 @@ google-crc32c==1.6.0 # google-resumable-media google-resumable-media==2.7.2 # via google-cloud-storage -googleapis-common-protos==1.65.0 +googleapis-common-protos==1.66.0 # via # google-api-core # grpcio-status -grpcio==1.67.0 +grpcio==1.68.1 # via # google-api-core # grpcio-status -grpcio-status==1.67.0 +grpcio-status==1.68.1 # via google-api-core gunicorn==23.0.0 # via -r requirements/edx/kernel.in @@ -625,7 +627,7 @@ httplib2==0.22.0 # via # google-api-python-client # google-auth-httplib2 -icalendar==6.0.1 +icalendar==6.1.0 # via -r requirements/edx/kernel.in idna==3.10 # via @@ -690,7 +692,7 @@ libsass==0.10.0 # -r requirements/edx/paver.txt loremipsum==1.0.5 # via ora2 -lti-consumer-xblock==9.11.3 +lti-consumer-xblock==9.12.0 # via -r requirements/edx/kernel.in lxml[html-clean,html_clean]==5.3.0 # via @@ -705,11 +707,11 @@ lxml[html-clean,html_clean]==5.3.0 # python3-saml # xblock # xmlsec -lxml-html-clean==0.3.1 +lxml-html-clean==0.4.1 # via lxml mailsnake==1.6.4 # via -r requirements/edx/bundled.in -mako==1.3.6 +mako==1.3.7 # via # -r requirements/edx/kernel.in # acid-xblock @@ -733,7 +735,7 @@ markupsafe==3.0.2 # xblock maxminddb==2.6.2 # via geoip2 -meilisearch==0.31.6 +meilisearch==0.33.0 # via # -r requirements/edx/kernel.in # edx-search @@ -755,11 +757,11 @@ multidict==6.1.0 # via # aiohttp # yarl -mysqlclient==2.2.5 +mysqlclient==2.2.6 # via -r requirements/edx/kernel.in -newrelic==10.2.0 +newrelic==10.3.1 # via edx-django-utils -nh3==0.2.18 +nh3==0.2.19 # via -r requirements/edx/kernel.in nltk==3.9.1 # via chem @@ -787,12 +789,13 @@ openai==0.28.1 # edx-enterprise openedx-atlas==0.6.2 # via -r requirements/edx/kernel.in -openedx-calc==3.1.2 +openedx-calc==4.0.1 # via -r requirements/edx/kernel.in openedx-django-pyfs==3.7.0 # via # lti-consumer-xblock # xblock + # xblocks-contrib openedx-django-require==2.1.0 # via -r requirements/edx/kernel.in openedx-django-wiki==2.1.0 @@ -811,7 +814,7 @@ openedx-filters==1.11.0 # -r requirements/edx/kernel.in # lti-consumer-xblock # ora2 -openedx-learning==0.17.0 +openedx-learning==0.18.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in @@ -821,15 +824,15 @@ optimizely-sdk==4.1.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/bundled.in -ora2==6.14.0 +ora2==6.14.1 # via -r requirements/edx/bundled.in -packaging==24.1 +packaging==24.2 # via # drf-yasg # gunicorn # py2neo # snowflake-connector-python -pansi==2020.7.3 +pansi==2024.11.0 # via py2neo paramiko==3.5.0 path==16.11.0 @@ -859,18 +862,21 @@ pillow==11.0.0 # edx-enterprise # edx-organizations # edxval + # pansi platformdirs==4.3.6 # via snowflake-connector-python polib==1.2.0 # via edx-i18n-tools # via click-repl -propcache==0.2.0 - # via yarl +propcache==0.2.1 + # via + # aiohttp + # yarl proto-plus==1.25.0 # via # google-api-core # google-cloud-firestore -protobuf==5.28.3 +protobuf==5.29.1 # via # google-api-core # google-cloud-firestore @@ -902,9 +908,9 @@ pycryptodomex==3.21.0 # edx-proctoring # lti-consumer-xblock # pyjwkest -pydantic==2.9.2 +pydantic==2.10.3 # via camel-converter -pydantic-core==2.23.4 +pydantic-core==2.27.1 # via pydantic pygments==2.18.0 # via @@ -915,7 +921,7 @@ pyjwkest==1.4.2 # -r requirements/edx/kernel.in # edx-token-utils # lti-consumer-xblock -pyjwt[crypto]==2.9.0 +pyjwt[crypto]==2.10.1 # via # -r requirements/edx/kernel.in # drf-jwt @@ -948,7 +954,7 @@ pynacl==1.5.0 # paramiko pynliner==0.8.0 # via -r requirements/edx/kernel.in -pyopenssl==24.2.1 +pyopenssl==24.3.0 # via # optimizely-sdk # snowflake-connector-python @@ -1031,7 +1037,7 @@ referencing==0.35.1 # via # jsonschema # jsonschema-specifications -regex==2024.9.11 +regex==2024.11.6 # via nltk requests==2.32.3 # via @@ -1064,7 +1070,7 @@ requests-oauthlib==2.0.0 # via # -r requirements/edx/kernel.in # social-auth-core -rpds-py==0.20.0 +rpds-py==0.22.3 # via # jsonschema # referencing @@ -1076,7 +1082,7 @@ rules==3.5 # edx-enterprise # edx-proctoring # openedx-learning -s3transfer==0.10.3 +s3transfer==0.10.4 # via boto3 sailthru-client==2.2.3 # via edx-ace @@ -1095,12 +1101,11 @@ simplejson==3.19.3 # super-csv # xblock # xblock-utils -six==1.16.0 +six==1.17.0 # via # -r requirements/edx/kernel.in # -r requirements/edx/paver.txt # analytics-python - # bleach # codejail-includes # crowdsourcehinter-xblock # edx-ace @@ -1117,7 +1122,6 @@ six==1.16.0 # interchange # libsass # optimizely-sdk - # pansi # paver # py2neo # pyjwkest @@ -1127,7 +1131,7 @@ slumber==0.7.1 # -r requirements/edx/kernel.in # edx-bulk-grades # edx-enterprise -snowflake-connector-python==3.12.3 +snowflake-connector-python==3.12.4 social-auth-app-django==5.4.1 # via # -c requirements/edx/../constraints.txt @@ -1148,11 +1152,11 @@ sortedcontainers==2.4.0 # snowflake-connector-python soupsieve==2.6 # via beautifulsoup4 -sqlparse==0.5.1 +sqlparse==0.5.2 # via django staff-graded-xblock==2.3.0 # via -r requirements/edx/bundled.in -stevedore==5.3.0 +stevedore==5.4.0 # via # -r requirements/edx/kernel.in # -r requirements/edx/paver.txt @@ -1168,11 +1172,11 @@ sympy==1.13.3 testfixtures==8.3.0 text-unidecode==1.3 # via python-slugify -tinycss2==1.2.1 +tinycss2==1.4.0 # via bleach tomlkit==0.13.2 # via snowflake-connector-python -tqdm==4.66.6 +tqdm==4.67.1 # via # nltk # openai @@ -1217,7 +1221,7 @@ voluptuous==0.15.2 # via ora2 walrus==0.9.4 # via edx-event-bus-redis -watchdog==5.0.3 +watchdog==6.0.0 # via -r requirements/edx/paver.txt wcwidth==0.2.13 # via prompt-toolkit @@ -1238,7 +1242,7 @@ webob==1.8.9 # via # -r requirements/edx/kernel.in # xblock -wrapt==1.16.0 +wrapt==1.17.0 # via -r requirements/edx/paver.txt xblock[django]==5.1.0 # via @@ -1255,6 +1259,7 @@ xblock[django]==5.1.0 # xblock-drag-and-drop-v2 # xblock-google-drive # xblock-utils + # xblocks-contrib xblock-drag-and-drop-v2==4.0.3 # via -r requirements/edx/bundled.in xblock-google-drive==0.7.0 @@ -1265,13 +1270,15 @@ xblock-utils==4.0.0 # via # edx-sga # xblock-poll +xblocks-contrib==0.1.0 + # via -r requirements/edx/bundled.in xmlsec==1.3.14 # via python3-saml xss-utils==0.6.0 # via -r requirements/edx/kernel.in -yarl==1.17.0 +yarl==1.18.3 # via aiohttp -zipp==3.20.2 +zipp==3.21.0 # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/js_test.txt b/requirements/js_test.txt index 713a041d4..60649b9f6 100644 --- a/requirements/js_test.txt +++ b/requirements/js_test.txt @@ -67,9 +67,9 @@ python-dateutil==2.9.0.post0 # via tempora pyyaml==6.0.2 # via jasmine -selenium==4.26.1 +selenium==4.27.1 # via jasmine -six==1.16.0 +six==1.17.0 # via python-dateutil sniffio==1.3.1 # via trio diff --git a/requirements/test-master.txt b/requirements/test-master.txt index e4e911aa4..c56258e76 100644 --- a/requirements/test-master.txt +++ b/requirements/test-master.txt @@ -4,11 +4,11 @@ # # make upgrade # -aiohappyeyeballs==2.4.3 +aiohappyeyeballs==2.4.4 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # aiohttp -aiohttp==3.10.10 +aiohttp==3.11.9 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # openai @@ -36,13 +36,13 @@ attrs==24.2.0 # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # aiohttp # openedx-events -bcrypt==4.2.0 +bcrypt==4.2.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # paramiko billiard==4.2.1 # via celery -bleach==6.1.0 +bleach==6.2.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in @@ -82,12 +82,12 @@ click-plugins==1.1.1 # celery click-repl==0.3.0 # via celery -code-annotations==1.8.0 +code-annotations==2.0.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in # edx-toggles -cryptography==43.0.3 +cryptography==44.0.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in @@ -102,7 +102,7 @@ defusedxml==0.7.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # djangorestframework-xml -django==4.2.16 +django==4.2.17 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/common_constraints.txt # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt @@ -177,7 +177,7 @@ django-simple-history==3.1.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in -django-waffle==4.1.0 +django-waffle==4.2.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in @@ -217,11 +217,11 @@ edx-braze-client==0.2.5 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in -edx-ccx-keys==1.3.0 +edx-ccx-keys==2.0.2 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # openedx-events -edx-django-utils==7.0.0 +edx-django-utils==7.1.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in @@ -308,7 +308,7 @@ multidict==6.1.0 # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # aiohttp # yarl -newrelic==10.2.0 +newrelic==10.3.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # edx-django-utils @@ -324,7 +324,7 @@ openedx-events==9.15.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in -packaging==24.1 +packaging==24.2 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # drf-yasg @@ -359,9 +359,10 @@ platformdirs==4.3.6 # snowflake-connector-python prompt-toolkit==3.0.48 # via click-repl -propcache==0.2.0 +propcache==0.2.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt + # aiohttp # yarl psutil==6.1.0 # via @@ -375,7 +376,7 @@ pycparser==2.22 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # cffi -pyjwt[crypto]==2.9.0 +pyjwt[crypto]==2.10.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # drf-jwt @@ -391,7 +392,7 @@ pynacl==1.5.0 # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # edx-django-utils # paramiko -pyopenssl==24.2.1 +pyopenssl==24.3.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # snowflake-connector-python @@ -440,10 +441,9 @@ semantic-version==2.10.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # edx-drf-extensions -six==1.16.0 +six==1.17.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt - # bleach # edx-ccx-keys # edx-rbac # python-dateutil @@ -451,7 +451,7 @@ slumber==0.7.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in -snowflake-connector-python==3.12.3 +snowflake-connector-python==3.12.4 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in @@ -459,11 +459,11 @@ sortedcontainers==2.4.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # snowflake-connector-python -sqlparse==0.5.1 +sqlparse==0.5.2 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # django -stevedore==5.3.0 +stevedore==5.4.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in @@ -482,7 +482,7 @@ tomlkit==0.13.2 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # snowflake-connector-python -tqdm==4.66.6 +tqdm==4.67.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # openai @@ -523,7 +523,7 @@ webencodings==0.5.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # bleach -yarl==1.17.0 +yarl==1.18.3 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # aiohttp diff --git a/requirements/test.txt b/requirements/test.txt index 494d73ae7..853dce841 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -4,11 +4,11 @@ # # make upgrade # -aiohappyeyeballs==2.4.3 +aiohappyeyeballs==2.4.4 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp -aiohttp==3.10.10 +aiohttp==3.11.9 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # openai @@ -38,14 +38,14 @@ attrs==24.2.0 # aiohttp # openedx-events # pytest -bcrypt==4.2.0 +bcrypt==4.2.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # paramiko # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # celery -bleach==6.1.0 +bleach==6.2.0 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt @@ -86,13 +86,13 @@ click-plugins==1.1.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # celery -code-annotations==1.8.0 +code-annotations==2.0.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-toggles -coverage[toml]==7.6.7 +coverage[toml]==7.6.9 # via pytest-cov -cryptography==43.0.3 +cryptography==44.0.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # django-fernet-fields-v2 @@ -164,7 +164,7 @@ django-simple-history==3.1.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt -django-waffle==4.1.0 +django-waffle==4.2.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-django-utils @@ -196,11 +196,11 @@ edx-api-doc-tools==2.0.0 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt edx-braze-client==0.2.5 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt -edx-ccx-keys==1.3.0 +edx-ccx-keys==2.0.2 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # openedx-events -edx-django-utils==7.0.0 +edx-django-utils==7.1.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # django-config-models @@ -230,7 +230,7 @@ factory-boy==3.3.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r requirements/test.in -faker==33.0.0 +faker==33.1.0 # via factory-boy fastavro==1.9.7 # via @@ -290,7 +290,7 @@ multidict==6.1.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp # yarl -newrelic==10.2.0 +newrelic==10.3.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-django-utils @@ -302,7 +302,7 @@ openai==0.28.1 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt openedx-events==9.15.0 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt -packaging==24.1 +packaging==24.2 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # drf-yasg @@ -335,9 +335,10 @@ pluggy==1.5.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # click-repl -propcache==0.2.0 +propcache==0.2.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt + # aiohttp # yarl psutil==6.1.0 # via @@ -355,7 +356,7 @@ pycparser==2.22 # cffi pygments==2.18.0 # via diff-cover -pyjwt[crypto]==2.9.0 +pyjwt[crypto]==2.10.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # drf-jwt @@ -371,7 +372,7 @@ pynacl==1.5.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-django-utils # paramiko -pyopenssl==24.2.1 +pyopenssl==24.3.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # snowflake-connector-python @@ -431,10 +432,9 @@ semantic-version==2.10.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-drf-extensions -six==1.16.0 +six==1.17.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt - # bleach # edx-ccx-keys # edx-rbac # freezegun @@ -443,17 +443,17 @@ six==1.16.0 # responses slumber==0.7.1 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt -snowflake-connector-python==3.12.3 +snowflake-connector-python==3.12.4 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt sortedcontainers==2.4.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # snowflake-connector-python -sqlparse==0.5.1 +sqlparse==0.5.2 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # django -stevedore==5.3.0 +stevedore==5.4.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # code-annotations @@ -473,7 +473,7 @@ tomlkit==0.13.2 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # snowflake-connector-python -tqdm==4.66.6 +tqdm==4.67.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # openai @@ -513,7 +513,7 @@ webencodings==0.5.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # bleach -yarl==1.17.0 +yarl==1.18.3 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp From 27d84b952585cb70affb7651980c51c89aa6c9f7 Mon Sep 17 00:00:00 2001 From: muhammad-ammar Date: Mon, 9 Dec 2024 11:00:57 +0500 Subject: [PATCH 02/12] chore: update version to 5.4.0 --- CHANGELOG.rst | 4 ++++ enterprise/__init__.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2666702d9..4111b8372 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,10 @@ Unreleased ---------- * nothing unreleased +[5.4.0] +-------- +* chore: Update python requirements. + [5.3.1] ------- * fix: rely on single constant to define course mode priority order (i.e., ensure all enrollable modes are considered; previously missing honor mode in `enroll_learners_in_courses`). diff --git a/enterprise/__init__.py b/enterprise/__init__.py index f76324143..342a9378c 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "5.3.1" +__version__ = "5.4.0" From 1ed53c2542e14c1606243b997aa3dcd12df77aa9 Mon Sep 17 00:00:00 2001 From: Alexander Dusenbery Date: Tue, 10 Dec 2024 11:36:01 -0500 Subject: [PATCH 03/12] fix: unenrolled default intentions not realizable The default enrollment ``learner_status`` view now considers intended courses from which the requested user has unenrolled as no-longer realizable. ENT-9839 --- CHANGELOG.rst | 5 + enterprise/__init__.py | 2 +- .../views/default_enterprise_enrollments.py | 11 + .../api/test_default_enrollment_views.py | 726 ++++++++++++++++++ tests/test_enterprise/api/test_views.py | 711 +---------------- 5 files changed, 761 insertions(+), 694 deletions(-) create mode 100644 tests/test_enterprise/api/test_default_enrollment_views.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4111b8372..fdd3e8e00 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,11 @@ Unreleased ---------- * nothing unreleased +[5.4.1] +-------- +* fix: The default enrollment ``learner_status`` view now considers intended courses + from which the requested user has unenrolled as no-longer realizable. + [5.4.0] -------- * chore: Update python requirements. diff --git a/enterprise/__init__.py b/enterprise/__init__.py index 342a9378c..e1699b91c 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "5.4.0" +__version__ = "5.4.1" diff --git a/enterprise/api/v1/views/default_enterprise_enrollments.py b/enterprise/api/v1/views/default_enterprise_enrollments.py index f1fcb6ab2..aa1716527 100644 --- a/enterprise/api/v1/views/default_enterprise_enrollments.py +++ b/enterprise/api/v1/views/default_enterprise_enrollments.py @@ -211,6 +211,17 @@ def _partition_default_enrollment_intentions_by_enrollment_status( already_enrolled.append((intention, non_audit_enrollment)) continue + if non_audit_enrollment and non_audit_enrollment.unenrolled: + # Learner has un-enrolled in non-audit mode for this course run, + # so don't consider this as an enrollable intention. + # Note that that we don't consider the case of an unenrolled *audit* enrollment, + # because default enrollments should be "exactly once" per (user, enterprise), if possible. + # If only an (unenrolled) audit enrollment exists, it means the user likely + # never had a default intention realized for them in the given course, + # so we'd still like to consider it as enrollable. + needs_enrollment_not_enrollable.append((intention, non_audit_enrollment)) + continue + if not intention.is_course_run_enrollable: # Course run is not enrollable needs_enrollment_not_enrollable.append((intention, audit_enrollment)) diff --git a/tests/test_enterprise/api/test_default_enrollment_views.py b/tests/test_enterprise/api/test_default_enrollment_views.py new file mode 100644 index 000000000..91d631805 --- /dev/null +++ b/tests/test_enterprise/api/test_default_enrollment_views.py @@ -0,0 +1,726 @@ +""" +Tests for the edx-enterprise ``api.v1.views.default_enterprise_enrollments module``. +""" +import uuid +from unittest import mock + +import ddt +from faker import Faker +from oauth2_provider.models import get_application_model +from pytest import mark +from rest_framework import status +from rest_framework.reverse import reverse + +from django.conf import settings + +from enterprise.constants import ENTERPRISE_LEARNER_ROLE +from enterprise.models import EnterpriseCourseEnrollment +from test_utils import FAKE_UUIDS, TEST_PASSWORD, factories, fake_catalog_api + +from .constants import AUDIT_COURSE_MODE, VERIFIED_COURSE_MODE +from .test_views import BaseTestEnterpriseAPIViews, create_mock_default_enterprise_enrollment_intention + +Application = get_application_model() +fake = Faker() + +DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LIST_ENDPOINT = reverse('default-enterprise-enrollment-intentions-list') +DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT = reverse( + 'default-enterprise-enrollment-intentions-learner-status' +) + + +def get_default_enterprise_enrollment_intention_detail_endpoint(enrollment_intention_uuid=None): + return reverse( + 'default-enterprise-enrollment-intentions-detail', + kwargs={'pk': enrollment_intention_uuid if enrollment_intention_uuid else FAKE_UUIDS[0]} + ) + + +@ddt.ddt +@mark.django_db +class TestDefaultEnterpriseEnrollmentIntentionViewSet(BaseTestEnterpriseAPIViews): + """ + Test DefaultEnterpriseEnrollmentIntentionViewSet + """ + + def setUp(self): + super().setUp() + self.enterprise_customer = factories.EnterpriseCustomerFactory() + + username = 'test_user_default_enterprise_enrollment_intentions' + self.user = self.create_user(username=username, is_staff=False) + self.client.login(username=self.user.username, password=TEST_PASSWORD) + + def get_default_enrollment_intention_with_learner_enrollment_state(self, enrollment_intention, **kwargs): + """ + Returns the expected serialized default enrollment intention with learner enrollment state. + + Args: + enrollment_intention: The enrollment intention to serialize. + **kwargs: Additional parameters to customize the response. + - applicable_enterprise_catalog_uuids: List of applicable enterprise catalog UUIDs. + - is_course_run_enrollable: Boolean indicating if the course run is enrollable. + - best_mode_for_course_run: The best mode for the course run (e.g., "verified", "audit"). + - has_existing_enrollment: Boolean indicating if there is an existing enrollment. + - is_existing_enrollment_active: Boolean indicating if the existing enrollment is + active, or None if no existing enrollment. + - is_existing_enrollment_audit: Boolean indicating if the existing enrollment is + audit, or None if no existing enrollment. + """ + return { + 'uuid': str(enrollment_intention.uuid), + 'content_key': enrollment_intention.content_key, + 'enterprise_customer': str(self.enterprise_customer.uuid), + 'course_key': enrollment_intention.course_key, + 'course_run_key': enrollment_intention.course_run_key, + 'is_course_run_enrollable': kwargs.get('is_course_run_enrollable', True), + 'best_mode_for_course_run': kwargs.get('best_mode_for_course_run', VERIFIED_COURSE_MODE), + 'applicable_enterprise_catalog_uuids': kwargs.get( + 'applicable_enterprise_catalog_uuids', + [fake_catalog_api.FAKE_CATALOG_RESULT.get('uuid')], + ), + 'course_run_normalized_metadata': { + 'start_date': fake_catalog_api.FAKE_COURSE_RUN.get('start'), + 'end_date': fake_catalog_api.FAKE_COURSE_RUN.get('end'), + 'enroll_by_date': fake_catalog_api.FAKE_COURSE_RUN.get('seats')[1].get('upgrade_deadline'), + 'enroll_start_date': fake_catalog_api.FAKE_COURSE_RUN.get('enrollment_start'), + 'content_price': fake_catalog_api.FAKE_COURSE_RUN.get('first_enrollable_paid_seat_price'), + }, + 'created': enrollment_intention.created.strftime("%Y-%m-%dT%H:%M:%SZ"), + 'modified': enrollment_intention.modified.strftime("%Y-%m-%dT%H:%M:%SZ"), + 'has_existing_enrollment': kwargs.get('has_existing_enrollment', False), + 'is_existing_enrollment_active': kwargs.get('is_existing_enrollment_active', None), + 'is_existing_enrollment_audit': kwargs.get('is_existing_enrollment_audit', None), + } + + def test_default_enterprise_enrollment_intentions_missing_enterprise_uuid(self): + """ + Test expected response when successfully listing existing default enterprise enrollment intentions. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + response = self.client.get(f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LIST_ENDPOINT}") + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.json() == {'detail': 'enterprise_customer_uuid is a required query parameter.'} + + def test_default_enterprise_enrollment_intentions_invalid_enterprise_uuid(self): + """ + Test expected response when successfully listing existing default enterprise enrollment intentions. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + query_params = 'enterprise_customer_uuid=invalid-uuid' + response = self.client.get( + f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LIST_ENDPOINT}?{query_params}" + ) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.json() == {'detail': 'enterprise_customer_uuid query parameter is not a valid UUID.'} + + @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') + def test_default_enterprise_enrollment_intentions_list(self, mock_catalog_api_client): + """ + Test expected response when successfully listing existing default enterprise enrollment intentions. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + enrollment_intention = create_mock_default_enterprise_enrollment_intention( + enterprise_customer=self.enterprise_customer, + mock_catalog_api_client=mock_catalog_api_client, + ) + query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' + response = self.client.get( + f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LIST_ENDPOINT}?{query_params}" + ) + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + assert response_data['count'] == 1 + result = response_data['results'][0] + assert result['content_key'] == enrollment_intention.content_key + assert result['applicable_enterprise_catalog_uuids'] == [fake_catalog_api.FAKE_CATALOG_RESULT.get('uuid')] + + @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') + def test_default_enterprise_enrollment_intentions_detail(self, mock_catalog_api_client): + """ + Test expected response when unauthorized user attempts to list default + enterprise enrollment intentions. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + enrollment_intention = create_mock_default_enterprise_enrollment_intention( + enterprise_customer=self.enterprise_customer, + mock_catalog_api_client=mock_catalog_api_client, + ) + query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' + base_url = get_default_enterprise_enrollment_intention_detail_endpoint(str(enrollment_intention.uuid)) + response = self.client.get(f"{settings.TEST_SERVER}{base_url}?{query_params}") + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + assert response_data['content_key'] == enrollment_intention.content_key + assert response_data['applicable_enterprise_catalog_uuids'] == \ + [fake_catalog_api.FAKE_CATALOG_RESULT.get('uuid')] + + @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') + def test_default_enterprise_enrollment_intentions_list_unauthorized(self, mock_catalog_api_client): + """ + Test expected response when unauthorized user attempts to list default + enterprise enrollment intentions. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + create_mock_default_enterprise_enrollment_intention( + enterprise_customer=self.enterprise_customer, + mock_catalog_api_client=mock_catalog_api_client, + ) + query_params = f'enterprise_customer_uuid={str(uuid.uuid4())}' + response = self.client.get( + f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LIST_ENDPOINT}?{query_params}" + ) + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + assert response_data['count'] == 0 + assert response_data['results'] == [] + + @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') + def test_default_enterprise_enrollment_intentions_detail_403_forbidden(self, mock_catalog_api_client): + """ + Test expected response when unauthorized user attempts to list default + enterprise enrollment intentions. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + enrollment_intention = create_mock_default_enterprise_enrollment_intention( + enterprise_customer=self.enterprise_customer, + mock_catalog_api_client=mock_catalog_api_client, + ) + query_params = f'enterprise_customer_uuid={str(uuid.uuid4())}' + base_url = get_default_enterprise_enrollment_intention_detail_endpoint(str(enrollment_intention.uuid)) + response = self.client.get(f"{settings.TEST_SERVER}{base_url}?{query_params}") + assert response.status_code == status.HTTP_403_FORBIDDEN + + @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') + def test_default_enterprise_enrollment_intentions_not_in_catalog(self, mock_catalog_api_client): + """ + Test expected response when default enterprise enrollment intention is not in catalog. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + enrollment_intention = create_mock_default_enterprise_enrollment_intention( + enterprise_customer=self.enterprise_customer, + mock_catalog_api_client=mock_catalog_api_client, + contains_content_items=False, + catalog_list=[], + ) + query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' + base_url = get_default_enterprise_enrollment_intention_detail_endpoint(str(enrollment_intention.uuid)) + response = self.client.get(f"{settings.TEST_SERVER}{base_url}?{query_params}") + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + assert response_data['content_key'] == enrollment_intention.content_key + assert response_data['applicable_enterprise_catalog_uuids'] == [] + + def test_default_enterprise_enrollment_intentions_learner_status_not_linked(self): + """ + Test default enterprise enrollment intentions for specific learner not linked to enterprise customer. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' + response = self.client.get( + f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" + ) + assert response.status_code == status.HTTP_400_BAD_REQUEST + response_data = response.json() + assert response_data['detail'] == ( + f'User with lms_user_id {self.user.id} is not associated with ' + f'the enterprise customer {str(self.enterprise_customer.uuid)}.' + ) + + @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') + @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') + def test_default_enterprise_enrollment_intentions_learner_status_enrollable( + self, + mock_get_best_mode_from_course_key, + mock_catalog_api_client, + ): + """ + Test default enterprise enrollment intentions for specific learner linked to enterprise customer, where + the course run associated with the default enrollment intention is enrollable. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + enrollment_intention = create_mock_default_enterprise_enrollment_intention( + enterprise_customer=self.enterprise_customer, + mock_catalog_api_client=mock_catalog_api_client, + ) + mock_get_best_mode_from_course_key.return_value = VERIFIED_COURSE_MODE + factories.EnterpriseCustomerUserFactory( + user_id=self.user.id, + enterprise_customer=self.enterprise_customer, + ) + query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' + response = self.client.get( + f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" + ) + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + assert response_data['lms_user_id'] == self.user.id + assert response_data['user_email'] == self.user.email + assert response_data['enrollment_statuses'] == { + 'needs_enrollment': { + 'enrollable': [ + self.get_default_enrollment_intention_with_learner_enrollment_state(enrollment_intention) + ], + 'not_enrollable': [], + }, + 'already_enrolled': [], + } + assert response_data['metadata'] == { + 'total_default_enterprise_enrollment_intentions': 1, + 'total_needs_enrollment': { + 'enrollable': 1, + 'not_enrollable': 0, + }, + 'total_already_enrolled': 0, + } + + @ddt.data( + {'run_is_enrollable': False, 'unenrollment_exists': False}, + {'run_is_enrollable': True, 'unenrollment_exists': True}, + ) + @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') + @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') + @mock.patch.object(EnterpriseCourseEnrollment, 'course_enrollment', new_callable=mock.PropertyMock) + @ddt.unpack + def test_default_enrollment_intentions_learner_status_content_not_enrollable( + self, + mock_course_enrollment, + mock_get_best_mode_from_course_key, + mock_catalog_api_client, + run_is_enrollable, + unenrollment_exists, + ): + """ + Test default enterprise enrollment intentions (not enrollable) for + specific learner linked to enterprise customer. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + + mock_course_run = fake_catalog_api.FAKE_COURSE_RUN.copy() + mock_course_run.update({'is_enrollable': run_is_enrollable}) + mock_course = fake_catalog_api.FAKE_COURSE.copy() + mock_course.update({'course_runs': [mock_course_run]}) + enrollment_intention = create_mock_default_enterprise_enrollment_intention( + enterprise_customer=self.enterprise_customer, + mock_catalog_api_client=mock_catalog_api_client, + content_metadata=mock_course, + ) + + mock_get_best_mode_from_course_key.return_value = VERIFIED_COURSE_MODE + ecu = factories.EnterpriseCustomerUserFactory( + user_id=self.user.id, + enterprise_customer=self.enterprise_customer, + ) + if unenrollment_exists: + factories.EnterpriseCourseEnrollmentFactory( + enterprise_customer_user=ecu, + course_id=fake_catalog_api.FAKE_COURSE_RUN.get('key'), + unenrolled=True, + ) + mock_course_enrollment.return_value = mock.Mock( + is_active=False, + mode=VERIFIED_COURSE_MODE, + ) + + query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' + response = self.client.get( + f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" + ) + + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + assert response_data['lms_user_id'] == self.user.id + assert response_data['user_email'] == self.user.email + assert response_data['enrollment_statuses'] == { + 'needs_enrollment': { + 'enrollable': [], + 'not_enrollable': [ + self.get_default_enrollment_intention_with_learner_enrollment_state( + enrollment_intention, + is_course_run_enrollable=run_is_enrollable, + has_existing_enrollment=unenrollment_exists, + is_existing_enrollment_active=False if unenrollment_exists else None, + is_existing_enrollment_audit=False if unenrollment_exists else None, + ) + ], + }, + 'already_enrolled': [], + } + assert response_data['metadata'] == { + 'total_default_enterprise_enrollment_intentions': 1, + 'total_needs_enrollment': { + 'enrollable': 0, + 'not_enrollable': 1, + }, + 'total_already_enrolled': 0, + } + + @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') + @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') + def test_default_enrollment_intentions_learner_status_content_not_in_catalog( + self, + mock_get_best_mode_from_course_key, + mock_catalog_api_client, + ): + """ + Test default enterprise enrollment intentions (not enrollable, no applicable + catalog) for specific learner linked to enterprise customer. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + enrollment_intention = create_mock_default_enterprise_enrollment_intention( + enterprise_customer=self.enterprise_customer, + mock_catalog_api_client=mock_catalog_api_client, + contains_content_items=False, + catalog_list=[], + ) + mock_get_best_mode_from_course_key.return_value = VERIFIED_COURSE_MODE + factories.EnterpriseCustomerUserFactory( + user_id=self.user.id, + enterprise_customer=self.enterprise_customer, + ) + query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' + response = self.client.get( + f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" + ) + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + assert response_data['lms_user_id'] == self.user.id + assert response_data['user_email'] == self.user.email + assert response_data['enrollment_statuses'] == { + 'needs_enrollment': { + 'enrollable': [], + 'not_enrollable': [ + self.get_default_enrollment_intention_with_learner_enrollment_state( + enrollment_intention, + applicable_enterprise_catalog_uuids=[], + is_course_run_enrollable=True, + has_existing_enrollment=False, + is_existing_enrollment_active=None, + is_existing_enrollment_audit=None, + ) + ], + }, + 'already_enrolled': [], + } + assert response_data['metadata'] == { + 'total_default_enterprise_enrollment_intentions': 1, + 'total_needs_enrollment': { + 'enrollable': 0, + 'not_enrollable': 1, + }, + 'total_already_enrolled': 0, + } + + @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') + @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') + @mock.patch.object(EnterpriseCourseEnrollment, 'course_enrollment', new_callable=mock.PropertyMock) + def test_default_enrollment_intentions_learner_status_already_enrolled_active( + self, + mock_course_enrollment, + mock_get_best_mode_from_course_key, + mock_catalog_api_client, + ): + """ + Test default enterprise enrollment intentions (already enrolled, active + enrollment) for specific learner linked to enterprise customer. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + enrollment_intention = create_mock_default_enterprise_enrollment_intention( + enterprise_customer=self.enterprise_customer, + mock_catalog_api_client=mock_catalog_api_client, + ) + mock_get_best_mode_from_course_key.return_value = VERIFIED_COURSE_MODE + enterprise_customer_user = factories.EnterpriseCustomerUserFactory( + user_id=self.user.id, + enterprise_customer=self.enterprise_customer, + ) + factories.EnterpriseCourseEnrollmentFactory( + enterprise_customer_user=enterprise_customer_user, + course_id=fake_catalog_api.FAKE_COURSE_RUN.get('key'), + ) + course_enrollment_kwargs = { + 'is_active': True, + 'mode': VERIFIED_COURSE_MODE, + } + mock_course_enrollment.return_value = mock.Mock(**course_enrollment_kwargs) + query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' + response = self.client.get( + f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" + ) + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + assert response_data['lms_user_id'] == self.user.id + assert response_data['user_email'] == self.user.email + assert response_data['enrollment_statuses'] == { + 'needs_enrollment': { + 'enrollable': [], + 'not_enrollable': [], + }, + 'already_enrolled': [ + self.get_default_enrollment_intention_with_learner_enrollment_state( + enrollment_intention, + has_existing_enrollment=True, + is_existing_enrollment_active=True, + is_existing_enrollment_audit=False, + ) + ], + } + assert response_data['metadata'] == { + 'total_default_enterprise_enrollment_intentions': 1, + 'total_needs_enrollment': { + 'enrollable': 0, + 'not_enrollable': 0, + }, + 'total_already_enrolled': 1, + } + + @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') + @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') + @mock.patch.object(EnterpriseCourseEnrollment, 'course_enrollment', new_callable=mock.PropertyMock) + def test_default_enrollment_intentions_learner_status_already_enrolled_inactive( + self, + mock_course_enrollment, + mock_get_best_mode_from_course_key, + mock_catalog_api_client, + ): + """ + Test default enterprise enrollment intentions (already enrolled, inactive + enrollment) for specific learner linked to enterprise customer. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + enrollment_intention = create_mock_default_enterprise_enrollment_intention( + enterprise_customer=self.enterprise_customer, + mock_catalog_api_client=mock_catalog_api_client, + ) + mock_get_best_mode_from_course_key.return_value = VERIFIED_COURSE_MODE + enterprise_customer_user = factories.EnterpriseCustomerUserFactory( + user_id=self.user.id, + enterprise_customer=self.enterprise_customer, + ) + factories.EnterpriseCourseEnrollmentFactory( + enterprise_customer_user=enterprise_customer_user, + course_id=fake_catalog_api.FAKE_COURSE_RUN.get('key'), + ) + course_enrollment_kwargs = { + 'is_active': False, + 'mode': VERIFIED_COURSE_MODE, + } + mock_course_enrollment.return_value = mock.Mock(**course_enrollment_kwargs) + query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' + response = self.client.get( + f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" + ) + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + assert response_data['lms_user_id'] == self.user.id + assert response_data['user_email'] == self.user.email + assert response_data['enrollment_statuses'] == { + 'needs_enrollment': { + 'enrollable': [ + self.get_default_enrollment_intention_with_learner_enrollment_state( + enrollment_intention, + has_existing_enrollment=True, + is_existing_enrollment_active=False, + is_existing_enrollment_audit=False, + ) + ], + 'not_enrollable': [], + }, + 'already_enrolled': [], + } + assert response_data['metadata'] == { + 'total_default_enterprise_enrollment_intentions': 1, + 'total_needs_enrollment': { + 'enrollable': 1, + 'not_enrollable': 0, + }, + 'total_already_enrolled': 0, + } + + @ddt.data( + {'has_audit_mode_only': True}, + {'has_audit_mode_only': False}, + ) + @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') + @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') + @mock.patch.object(EnterpriseCourseEnrollment, 'course_enrollment', new_callable=mock.PropertyMock) + @ddt.unpack + def test_default_enrollment_intentions_learner_status_already_enrolled_active_audit( + self, + mock_course_enrollment, + mock_get_best_mode_from_course_key, + mock_catalog_api_client, + has_audit_mode_only, + ): + """ + Test default enterprise enrollment intentions (already enrolled, active + audit enrollment) for specific learner linked to enterprise customer. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + enrollment_intention = create_mock_default_enterprise_enrollment_intention( + enterprise_customer=self.enterprise_customer, + mock_catalog_api_client=mock_catalog_api_client, + ) + + best_mode_for_course_run = AUDIT_COURSE_MODE if has_audit_mode_only else VERIFIED_COURSE_MODE + mock_get_best_mode_from_course_key.return_value = best_mode_for_course_run + + enterprise_customer_user = factories.EnterpriseCustomerUserFactory( + user_id=self.user.id, + enterprise_customer=self.enterprise_customer, + ) + factories.EnterpriseCourseEnrollmentFactory( + enterprise_customer_user=enterprise_customer_user, + course_id=fake_catalog_api.FAKE_COURSE_RUN.get('key'), + ) + course_enrollment_kwargs = { + 'is_active': True, + 'mode': AUDIT_COURSE_MODE, + } + mock_course_enrollment.return_value = mock.Mock(**course_enrollment_kwargs) + query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' + response = self.client.get( + f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" + ) + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + assert response_data['lms_user_id'] == self.user.id + assert response_data['user_email'] == self.user.email + + expected_enrollable = [] + expected_already_enrolled = [] + + expected_serialized_intention = self.get_default_enrollment_intention_with_learner_enrollment_state( + enrollment_intention, + has_existing_enrollment=True, + is_existing_enrollment_active=True, + is_existing_enrollment_audit=True, + best_mode_for_course_run=best_mode_for_course_run, + ) + + if has_audit_mode_only: + expected_already_enrolled.append(expected_serialized_intention) + else: + expected_enrollable.append(expected_serialized_intention) + + assert response_data['enrollment_statuses'] == { + 'needs_enrollment': { + 'enrollable': expected_enrollable, + 'not_enrollable': [], + }, + 'already_enrolled': expected_already_enrolled, + } + assert response_data['metadata'] == { + 'total_default_enterprise_enrollment_intentions': 1, + 'total_needs_enrollment': { + 'enrollable': len(expected_enrollable), + 'not_enrollable': 0, + }, + 'total_already_enrolled': len(expected_already_enrolled), + } + + @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') + @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') + def test_default_enrollment_intentions_learner_status_staff_lms_user_id_override( + self, + mock_get_best_mode_from_course_key, + mock_catalog_api_client, + ): + """ + Test default enterprise enrollment intentions for staff user, requesting a specific user + linked to enterprise customer via lms_user_id query parameter. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + + # Create and login as a staff user + staff_user = self.create_user(username='staff_username', password=TEST_PASSWORD, is_staff=True) + self.client.login(username=staff_user.username, password=TEST_PASSWORD) + + enrollment_intention = create_mock_default_enterprise_enrollment_intention( + enterprise_customer=self.enterprise_customer, + mock_catalog_api_client=mock_catalog_api_client, + ) + mock_get_best_mode_from_course_key.return_value = VERIFIED_COURSE_MODE + factories.EnterpriseCustomerUserFactory( + user_id=self.user.id, + enterprise_customer=self.enterprise_customer, + ) + query_params = ( + f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' + # Validates staff user can get back data for another user (i.e., request user is `staff_user`) + f'&lms_user_id={self.user.id}' + ) + response = self.client.get( + f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" + ) + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + assert response_data['lms_user_id'] == self.user.id + assert response_data['user_email'] == self.user.email + assert response_data['enrollment_statuses'] == { + 'needs_enrollment': { + 'enrollable': [ + self.get_default_enrollment_intention_with_learner_enrollment_state(enrollment_intention) + ], + 'not_enrollable': [], + }, + 'already_enrolled': [], + } + assert response_data['metadata'] == { + 'total_default_enterprise_enrollment_intentions': 1, + 'total_needs_enrollment': { + 'enrollable': 1, + 'not_enrollable': 0, + }, + 'total_already_enrolled': 0, + } + + @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') + @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') + def test_default_enrollment_intentions_learner_status_nonstaff_lms_user_id_override( + self, + mock_get_best_mode_from_course_key, + mock_catalog_api_client + ): + """ + Test default enterprise enrollment intentions for non-staff user linked to enterprise customer. + """ + self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) + enrollment_intention = create_mock_default_enterprise_enrollment_intention( + enterprise_customer=self.enterprise_customer, + mock_catalog_api_client=mock_catalog_api_client, + ) + mock_get_best_mode_from_course_key.return_value = VERIFIED_COURSE_MODE + factories.EnterpriseCustomerUserFactory( + user_id=self.user.id, + enterprise_customer=self.enterprise_customer, + ) + query_params = ( + f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' + f'&lms_user_id={self.user.id + 1}' # Validates non-staff user can't get back data for another user + ) + response = self.client.get( + f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" + ) + + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + assert response_data['lms_user_id'] == self.user.id + assert response_data['user_email'] == self.user.email + assert response_data['enrollment_statuses'] == { + 'needs_enrollment': { + 'enrollable': [ + self.get_default_enrollment_intention_with_learner_enrollment_state(enrollment_intention) + ], + 'not_enrollable': [], + }, + 'already_enrolled': [], + } + assert response_data['metadata'] == { + 'total_default_enterprise_enrollment_intentions': 1, + 'total_needs_enrollment': { + 'enrollable': 1, + 'not_enrollable': 0, + }, + 'total_already_enrolled': 0, + } diff --git a/tests/test_enterprise/api/test_views.py b/tests/test_enterprise/api/test_views.py index 1d4016719..be30cac5d 100644 --- a/tests/test_enterprise/api/test_views.py +++ b/tests/test_enterprise/api/test_views.py @@ -170,35 +170,6 @@ EXPIRED_LICENSED_ENTERPRISE_COURSE_ENROLLMENTS_ENDPOINT = reverse( 'licensed-enterprise-course-enrollment-bulk-licensed-enrollments-expiration' ) -DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LIST_ENDPOINT = reverse('default-enterprise-enrollment-intentions-list') -DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT = reverse( - 'default-enterprise-enrollment-intentions-learner-status' -) - - -def get_default_enterprise_enrollment_intention_detail_endpoint(enrollment_intention_uuid=None): - return reverse( - 'default-enterprise-enrollment-intentions-detail', - kwargs={'pk': enrollment_intention_uuid if enrollment_intention_uuid else FAKE_UUIDS[0]} - ) - - -def side_effect(url, query_parameters): - """ - returns a url with updated query parameters. - """ - if any(key in ['utm_medium', 'catalog'] for key in query_parameters): - return url - - scheme, netloc, path, query_string, fragment = urlsplit(url) - url_params = parse_qs(query_string) - - # Update url query parameters - url_params.update(query_parameters) - - return urlunsplit( - (scheme, netloc, path, urlencode(url_params, doseq=True), fragment), - ) def create_mock_default_enterprise_enrollment_intention( @@ -237,6 +208,24 @@ def create_mock_default_enterprise_enrollment_intention( return enrollment_intention +def side_effect(url, query_parameters): + """ + returns a url with updated query parameters. + """ + if any(key in ['utm_medium', 'catalog'] for key in query_parameters): + return url + + scheme, netloc, path, query_string, fragment = urlsplit(url) + url_params = parse_qs(query_string) + + # Update url query parameters + url_params.update(query_parameters) + + return urlunsplit( + (scheme, netloc, path, urlencode(url_params, doseq=True), fragment), + ) + + class BaseTestEnterpriseAPIViews(APITest): """ Shared setup and methods for enterprise api views. @@ -9867,667 +9856,3 @@ def test_list_users_filtered(self): assert expected_json == response.json().get('results') assert response.json().get('count') == 1 - - -@ddt.ddt -@mark.django_db -class TestDefaultEnterpriseEnrollmentIntentionViewSet(BaseTestEnterpriseAPIViews): - """ - Test DefaultEnterpriseEnrollmentIntentionViewSet - """ - - def setUp(self): - super().setUp() - self.enterprise_customer = factories.EnterpriseCustomerFactory() - - username = 'test_user_default_enterprise_enrollment_intentions' - self.user = self.create_user(username=username, is_staff=False) - self.client.login(username=self.user.username, password=TEST_PASSWORD) - - def get_default_enrollment_intention_with_learner_enrollment_state(self, enrollment_intention, **kwargs): - """ - Returns the expected serialized default enrollment intention with learner enrollment state. - - Args: - enrollment_intention: The enrollment intention to serialize. - **kwargs: Additional parameters to customize the response. - - applicable_enterprise_catalog_uuids: List of applicable enterprise catalog UUIDs. - - is_course_run_enrollable: Boolean indicating if the course run is enrollable. - - best_mode_for_course_run: The best mode for the course run (e.g., "verified", "audit"). - - has_existing_enrollment: Boolean indicating if there is an existing enrollment. - - is_existing_enrollment_active: Boolean indicating if the existing enrollment is - active, or None if no existing enrollment. - - is_existing_enrollment_audit: Boolean indicating if the existing enrollment is - audit, or None if no existing enrollment. - """ - return { - 'uuid': str(enrollment_intention.uuid), - 'content_key': enrollment_intention.content_key, - 'enterprise_customer': str(self.enterprise_customer.uuid), - 'course_key': enrollment_intention.course_key, - 'course_run_key': enrollment_intention.course_run_key, - 'is_course_run_enrollable': kwargs.get('is_course_run_enrollable', True), - 'best_mode_for_course_run': kwargs.get('best_mode_for_course_run', VERIFIED_COURSE_MODE), - 'applicable_enterprise_catalog_uuids': kwargs.get( - 'applicable_enterprise_catalog_uuids', - [fake_catalog_api.FAKE_CATALOG_RESULT.get('uuid')], - ), - 'course_run_normalized_metadata': { - 'start_date': fake_catalog_api.FAKE_COURSE_RUN.get('start'), - 'end_date': fake_catalog_api.FAKE_COURSE_RUN.get('end'), - 'enroll_by_date': fake_catalog_api.FAKE_COURSE_RUN.get('seats')[1].get('upgrade_deadline'), - 'enroll_start_date': fake_catalog_api.FAKE_COURSE_RUN.get('enrollment_start'), - 'content_price': fake_catalog_api.FAKE_COURSE_RUN.get('first_enrollable_paid_seat_price'), - }, - 'created': enrollment_intention.created.strftime("%Y-%m-%dT%H:%M:%SZ"), - 'modified': enrollment_intention.modified.strftime("%Y-%m-%dT%H:%M:%SZ"), - 'has_existing_enrollment': kwargs.get('has_existing_enrollment', False), - 'is_existing_enrollment_active': kwargs.get('is_existing_enrollment_active', None), - 'is_existing_enrollment_audit': kwargs.get('is_existing_enrollment_audit', None), - } - - def test_default_enterprise_enrollment_intentions_missing_enterprise_uuid(self): - """ - Test expected response when successfully listing existing default enterprise enrollment intentions. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - response = self.client.get(f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LIST_ENDPOINT}") - assert response.status_code == status.HTTP_400_BAD_REQUEST - assert response.json() == {'detail': 'enterprise_customer_uuid is a required query parameter.'} - - def test_default_enterprise_enrollment_intentions_invalid_enterprise_uuid(self): - """ - Test expected response when successfully listing existing default enterprise enrollment intentions. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - query_params = 'enterprise_customer_uuid=invalid-uuid' - response = self.client.get( - f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LIST_ENDPOINT}?{query_params}" - ) - assert response.status_code == status.HTTP_400_BAD_REQUEST - assert response.json() == {'detail': 'enterprise_customer_uuid query parameter is not a valid UUID.'} - - @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') - def test_default_enterprise_enrollment_intentions_list(self, mock_catalog_api_client): - """ - Test expected response when successfully listing existing default enterprise enrollment intentions. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - enrollment_intention = create_mock_default_enterprise_enrollment_intention( - enterprise_customer=self.enterprise_customer, - mock_catalog_api_client=mock_catalog_api_client, - ) - query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' - response = self.client.get( - f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LIST_ENDPOINT}?{query_params}" - ) - assert response.status_code == status.HTTP_200_OK - response_data = response.json() - assert response_data['count'] == 1 - result = response_data['results'][0] - assert result['content_key'] == enrollment_intention.content_key - assert result['applicable_enterprise_catalog_uuids'] == [fake_catalog_api.FAKE_CATALOG_RESULT.get('uuid')] - - @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') - def test_default_enterprise_enrollment_intentions_detail(self, mock_catalog_api_client): - """ - Test expected response when unauthorized user attempts to list default - enterprise enrollment intentions. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - enrollment_intention = create_mock_default_enterprise_enrollment_intention( - enterprise_customer=self.enterprise_customer, - mock_catalog_api_client=mock_catalog_api_client, - ) - query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' - base_url = get_default_enterprise_enrollment_intention_detail_endpoint(str(enrollment_intention.uuid)) - response = self.client.get(f"{settings.TEST_SERVER}{base_url}?{query_params}") - assert response.status_code == status.HTTP_200_OK - response_data = response.json() - assert response_data['content_key'] == enrollment_intention.content_key - assert response_data['applicable_enterprise_catalog_uuids'] == \ - [fake_catalog_api.FAKE_CATALOG_RESULT.get('uuid')] - - @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') - def test_default_enterprise_enrollment_intentions_list_unauthorized(self, mock_catalog_api_client): - """ - Test expected response when unauthorized user attempts to list default - enterprise enrollment intentions. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - create_mock_default_enterprise_enrollment_intention( - enterprise_customer=self.enterprise_customer, - mock_catalog_api_client=mock_catalog_api_client, - ) - query_params = f'enterprise_customer_uuid={str(uuid.uuid4())}' - response = self.client.get( - f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LIST_ENDPOINT}?{query_params}" - ) - assert response.status_code == status.HTTP_200_OK - response_data = response.json() - assert response_data['count'] == 0 - assert response_data['results'] == [] - - @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') - def test_default_enterprise_enrollment_intentions_detail_403_forbidden(self, mock_catalog_api_client): - """ - Test expected response when unauthorized user attempts to list default - enterprise enrollment intentions. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - enrollment_intention = create_mock_default_enterprise_enrollment_intention( - enterprise_customer=self.enterprise_customer, - mock_catalog_api_client=mock_catalog_api_client, - ) - query_params = f'enterprise_customer_uuid={str(uuid.uuid4())}' - base_url = get_default_enterprise_enrollment_intention_detail_endpoint(str(enrollment_intention.uuid)) - response = self.client.get(f"{settings.TEST_SERVER}{base_url}?{query_params}") - assert response.status_code == status.HTTP_403_FORBIDDEN - - @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') - def test_default_enterprise_enrollment_intentions_not_in_catalog(self, mock_catalog_api_client): - """ - Test expected response when default enterprise enrollment intention is not in catalog. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - enrollment_intention = create_mock_default_enterprise_enrollment_intention( - enterprise_customer=self.enterprise_customer, - mock_catalog_api_client=mock_catalog_api_client, - contains_content_items=False, - catalog_list=[], - ) - query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' - base_url = get_default_enterprise_enrollment_intention_detail_endpoint(str(enrollment_intention.uuid)) - response = self.client.get(f"{settings.TEST_SERVER}{base_url}?{query_params}") - assert response.status_code == status.HTTP_200_OK - response_data = response.json() - assert response_data['content_key'] == enrollment_intention.content_key - assert response_data['applicable_enterprise_catalog_uuids'] == [] - - def test_default_enterprise_enrollment_intentions_learner_status_not_linked(self): - """ - Test default enterprise enrollment intentions for specific learner not linked to enterprise customer. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' - response = self.client.get( - f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" - ) - assert response.status_code == status.HTTP_400_BAD_REQUEST - response_data = response.json() - assert response_data['detail'] == ( - f'User with lms_user_id {self.user.id} is not associated with ' - f'the enterprise customer {str(self.enterprise_customer.uuid)}.' - ) - - @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') - @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') - def test_default_enterprise_enrollment_intentions_learner_status_enrollable( - self, - mock_get_best_mode_from_course_key, - mock_catalog_api_client, - ): - """ - Test default enterprise enrollment intentions for specific learner linked to enterprise customer, where - the course run associated with the default enrollment intention is enrollable. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - enrollment_intention = create_mock_default_enterprise_enrollment_intention( - enterprise_customer=self.enterprise_customer, - mock_catalog_api_client=mock_catalog_api_client, - ) - mock_get_best_mode_from_course_key.return_value = VERIFIED_COURSE_MODE - factories.EnterpriseCustomerUserFactory( - user_id=self.user.id, - enterprise_customer=self.enterprise_customer, - ) - query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' - response = self.client.get( - f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" - ) - assert response.status_code == status.HTTP_200_OK - response_data = response.json() - assert response_data['lms_user_id'] == self.user.id - assert response_data['user_email'] == self.user.email - assert response_data['enrollment_statuses'] == { - 'needs_enrollment': { - 'enrollable': [ - self.get_default_enrollment_intention_with_learner_enrollment_state(enrollment_intention) - ], - 'not_enrollable': [], - }, - 'already_enrolled': [], - } - assert response_data['metadata'] == { - 'total_default_enterprise_enrollment_intentions': 1, - 'total_needs_enrollment': { - 'enrollable': 1, - 'not_enrollable': 0, - }, - 'total_already_enrolled': 0, - } - - @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') - @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') - def test_default_enrollment_intentions_learner_status_content_not_enrollable( - self, - mock_get_best_mode_from_course_key, - mock_catalog_api_client, - ): - """ - Test default enterprise enrollment intentions (not enrollable) for - specific learner linked to enterprise customer. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - mock_course_run = fake_catalog_api.FAKE_COURSE_RUN.copy() - mock_course_run.update({'is_enrollable': False}) - mock_course = fake_catalog_api.FAKE_COURSE.copy() - mock_course.update({'course_runs': [mock_course_run]}) - enrollment_intention = create_mock_default_enterprise_enrollment_intention( - enterprise_customer=self.enterprise_customer, - mock_catalog_api_client=mock_catalog_api_client, - content_metadata=mock_course, - ) - mock_get_best_mode_from_course_key.return_value = VERIFIED_COURSE_MODE - factories.EnterpriseCustomerUserFactory( - user_id=self.user.id, - enterprise_customer=self.enterprise_customer, - ) - query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' - response = self.client.get( - f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" - ) - assert response.status_code == status.HTTP_200_OK - response_data = response.json() - assert response_data['lms_user_id'] == self.user.id - assert response_data['user_email'] == self.user.email - assert response_data['enrollment_statuses'] == { - 'needs_enrollment': { - 'enrollable': [], - 'not_enrollable': [ - self.get_default_enrollment_intention_with_learner_enrollment_state( - enrollment_intention, - is_course_run_enrollable=False, - ) - ], - }, - 'already_enrolled': [], - } - assert response_data['metadata'] == { - 'total_default_enterprise_enrollment_intentions': 1, - 'total_needs_enrollment': { - 'enrollable': 0, - 'not_enrollable': 1, - }, - 'total_already_enrolled': 0, - } - - @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') - @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') - def test_default_enrollment_intentions_learner_status_content_not_in_catalog( - self, - mock_get_best_mode_from_course_key, - mock_catalog_api_client, - ): - """ - Test default enterprise enrollment intentions (not enrollable, no applicable - catalog) for specific learner linked to enterprise customer. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - enrollment_intention = create_mock_default_enterprise_enrollment_intention( - enterprise_customer=self.enterprise_customer, - mock_catalog_api_client=mock_catalog_api_client, - contains_content_items=False, - catalog_list=[], - ) - mock_get_best_mode_from_course_key.return_value = VERIFIED_COURSE_MODE - factories.EnterpriseCustomerUserFactory( - user_id=self.user.id, - enterprise_customer=self.enterprise_customer, - ) - query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' - response = self.client.get( - f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" - ) - assert response.status_code == status.HTTP_200_OK - response_data = response.json() - assert response_data['lms_user_id'] == self.user.id - assert response_data['user_email'] == self.user.email - assert response_data['enrollment_statuses'] == { - 'needs_enrollment': { - 'enrollable': [], - 'not_enrollable': [ - self.get_default_enrollment_intention_with_learner_enrollment_state( - enrollment_intention, - applicable_enterprise_catalog_uuids=[], - is_course_run_enrollable=True, - has_existing_enrollment=False, - is_existing_enrollment_active=None, - is_existing_enrollment_audit=None, - ) - ], - }, - 'already_enrolled': [], - } - assert response_data['metadata'] == { - 'total_default_enterprise_enrollment_intentions': 1, - 'total_needs_enrollment': { - 'enrollable': 0, - 'not_enrollable': 1, - }, - 'total_already_enrolled': 0, - } - - @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') - @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') - @mock.patch.object(EnterpriseCourseEnrollment, 'course_enrollment', new_callable=mock.PropertyMock) - def test_default_enrollment_intentions_learner_status_already_enrolled_active( - self, - mock_course_enrollment, - mock_get_best_mode_from_course_key, - mock_catalog_api_client, - ): - """ - Test default enterprise enrollment intentions (already enrolled, active - enrollment) for specific learner linked to enterprise customer. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - enrollment_intention = create_mock_default_enterprise_enrollment_intention( - enterprise_customer=self.enterprise_customer, - mock_catalog_api_client=mock_catalog_api_client, - ) - mock_get_best_mode_from_course_key.return_value = VERIFIED_COURSE_MODE - enterprise_customer_user = factories.EnterpriseCustomerUserFactory( - user_id=self.user.id, - enterprise_customer=self.enterprise_customer, - ) - factories.EnterpriseCourseEnrollmentFactory( - enterprise_customer_user=enterprise_customer_user, - course_id=fake_catalog_api.FAKE_COURSE_RUN.get('key'), - ) - course_enrollment_kwargs = { - 'is_active': True, - 'mode': VERIFIED_COURSE_MODE, - } - mock_course_enrollment.return_value = mock.Mock(**course_enrollment_kwargs) - query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' - response = self.client.get( - f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" - ) - assert response.status_code == status.HTTP_200_OK - response_data = response.json() - assert response_data['lms_user_id'] == self.user.id - assert response_data['user_email'] == self.user.email - assert response_data['enrollment_statuses'] == { - 'needs_enrollment': { - 'enrollable': [], - 'not_enrollable': [], - }, - 'already_enrolled': [ - self.get_default_enrollment_intention_with_learner_enrollment_state( - enrollment_intention, - has_existing_enrollment=True, - is_existing_enrollment_active=True, - is_existing_enrollment_audit=False, - ) - ], - } - assert response_data['metadata'] == { - 'total_default_enterprise_enrollment_intentions': 1, - 'total_needs_enrollment': { - 'enrollable': 0, - 'not_enrollable': 0, - }, - 'total_already_enrolled': 1, - } - - @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') - @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') - @mock.patch.object(EnterpriseCourseEnrollment, 'course_enrollment', new_callable=mock.PropertyMock) - def test_default_enrollment_intentions_learner_status_already_enrolled_inactive( - self, - mock_course_enrollment, - mock_get_best_mode_from_course_key, - mock_catalog_api_client, - ): - """ - Test default enterprise enrollment intentions (already enrolled, inactive - enrollment) for specific learner linked to enterprise customer. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - enrollment_intention = create_mock_default_enterprise_enrollment_intention( - enterprise_customer=self.enterprise_customer, - mock_catalog_api_client=mock_catalog_api_client, - ) - mock_get_best_mode_from_course_key.return_value = VERIFIED_COURSE_MODE - enterprise_customer_user = factories.EnterpriseCustomerUserFactory( - user_id=self.user.id, - enterprise_customer=self.enterprise_customer, - ) - factories.EnterpriseCourseEnrollmentFactory( - enterprise_customer_user=enterprise_customer_user, - course_id=fake_catalog_api.FAKE_COURSE_RUN.get('key'), - ) - course_enrollment_kwargs = { - 'is_active': False, - 'mode': VERIFIED_COURSE_MODE, - } - mock_course_enrollment.return_value = mock.Mock(**course_enrollment_kwargs) - query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' - response = self.client.get( - f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" - ) - assert response.status_code == status.HTTP_200_OK - response_data = response.json() - assert response_data['lms_user_id'] == self.user.id - assert response_data['user_email'] == self.user.email - assert response_data['enrollment_statuses'] == { - 'needs_enrollment': { - 'enrollable': [ - self.get_default_enrollment_intention_with_learner_enrollment_state( - enrollment_intention, - has_existing_enrollment=True, - is_existing_enrollment_active=False, - is_existing_enrollment_audit=False, - ) - ], - 'not_enrollable': [], - }, - 'already_enrolled': [], - } - assert response_data['metadata'] == { - 'total_default_enterprise_enrollment_intentions': 1, - 'total_needs_enrollment': { - 'enrollable': 1, - 'not_enrollable': 0, - }, - 'total_already_enrolled': 0, - } - - @ddt.data( - {'has_audit_mode_only': True}, - {'has_audit_mode_only': False}, - ) - @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') - @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') - @mock.patch.object(EnterpriseCourseEnrollment, 'course_enrollment', new_callable=mock.PropertyMock) - @ddt.unpack - def test_default_enrollment_intentions_learner_status_already_enrolled_active_audit( - self, - mock_course_enrollment, - mock_get_best_mode_from_course_key, - mock_catalog_api_client, - has_audit_mode_only, - ): - """ - Test default enterprise enrollment intentions (already enrolled, active - audit enrollment) for specific learner linked to enterprise customer. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - enrollment_intention = create_mock_default_enterprise_enrollment_intention( - enterprise_customer=self.enterprise_customer, - mock_catalog_api_client=mock_catalog_api_client, - ) - - best_mode_for_course_run = AUDIT_COURSE_MODE if has_audit_mode_only else VERIFIED_COURSE_MODE - mock_get_best_mode_from_course_key.return_value = best_mode_for_course_run - - enterprise_customer_user = factories.EnterpriseCustomerUserFactory( - user_id=self.user.id, - enterprise_customer=self.enterprise_customer, - ) - factories.EnterpriseCourseEnrollmentFactory( - enterprise_customer_user=enterprise_customer_user, - course_id=fake_catalog_api.FAKE_COURSE_RUN.get('key'), - ) - course_enrollment_kwargs = { - 'is_active': True, - 'mode': AUDIT_COURSE_MODE, - } - mock_course_enrollment.return_value = mock.Mock(**course_enrollment_kwargs) - query_params = f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' - response = self.client.get( - f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" - ) - assert response.status_code == status.HTTP_200_OK - response_data = response.json() - assert response_data['lms_user_id'] == self.user.id - assert response_data['user_email'] == self.user.email - - expected_enrollable = [] - expected_already_enrolled = [] - - expected_serialized_intention = self.get_default_enrollment_intention_with_learner_enrollment_state( - enrollment_intention, - has_existing_enrollment=True, - is_existing_enrollment_active=True, - is_existing_enrollment_audit=True, - best_mode_for_course_run=best_mode_for_course_run, - ) - - if has_audit_mode_only: - expected_already_enrolled.append(expected_serialized_intention) - else: - expected_enrollable.append(expected_serialized_intention) - - assert response_data['enrollment_statuses'] == { - 'needs_enrollment': { - 'enrollable': expected_enrollable, - 'not_enrollable': [], - }, - 'already_enrolled': expected_already_enrolled, - } - assert response_data['metadata'] == { - 'total_default_enterprise_enrollment_intentions': 1, - 'total_needs_enrollment': { - 'enrollable': len(expected_enrollable), - 'not_enrollable': 0, - }, - 'total_already_enrolled': len(expected_already_enrolled), - } - - @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') - @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') - def test_default_enrollment_intentions_learner_status_staff_lms_user_id_override( - self, - mock_get_best_mode_from_course_key, - mock_catalog_api_client, - ): - """ - Test default enterprise enrollment intentions for staff user, requesting a specific user - linked to enterprise customer via lms_user_id query parameter. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - - # Create and login as a staff user - staff_user = self.create_user(username='staff_username', password=TEST_PASSWORD, is_staff=True) - self.client.login(username=staff_user.username, password=TEST_PASSWORD) - - enrollment_intention = create_mock_default_enterprise_enrollment_intention( - enterprise_customer=self.enterprise_customer, - mock_catalog_api_client=mock_catalog_api_client, - ) - mock_get_best_mode_from_course_key.return_value = VERIFIED_COURSE_MODE - factories.EnterpriseCustomerUserFactory( - user_id=self.user.id, - enterprise_customer=self.enterprise_customer, - ) - query_params = ( - f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' - # Validates staff user can get back data for another user (i.e., request user is `staff_user`) - f'&lms_user_id={self.user.id}' - ) - response = self.client.get( - f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" - ) - assert response.status_code == status.HTTP_200_OK - response_data = response.json() - assert response_data['lms_user_id'] == self.user.id - assert response_data['user_email'] == self.user.email - assert response_data['enrollment_statuses'] == { - 'needs_enrollment': { - 'enrollable': [ - self.get_default_enrollment_intention_with_learner_enrollment_state(enrollment_intention) - ], - 'not_enrollable': [], - }, - 'already_enrolled': [], - } - assert response_data['metadata'] == { - 'total_default_enterprise_enrollment_intentions': 1, - 'total_needs_enrollment': { - 'enrollable': 1, - 'not_enrollable': 0, - }, - 'total_already_enrolled': 0, - } - - @mock.patch('enterprise.content_metadata.api.EnterpriseCatalogApiClient') - @mock.patch('enterprise.models.utils.get_best_mode_from_course_key') - def test_default_enrollment_intentions_learner_status_nonstaff_lms_user_id_override( - self, - mock_get_best_mode_from_course_key, - mock_catalog_api_client - ): - """ - Test default enterprise enrollment intentions for non-staff user linked to enterprise customer. - """ - self.set_jwt_cookie(ENTERPRISE_LEARNER_ROLE, str(self.enterprise_customer.uuid)) - enrollment_intention = create_mock_default_enterprise_enrollment_intention( - enterprise_customer=self.enterprise_customer, - mock_catalog_api_client=mock_catalog_api_client, - ) - mock_get_best_mode_from_course_key.return_value = VERIFIED_COURSE_MODE - factories.EnterpriseCustomerUserFactory( - user_id=self.user.id, - enterprise_customer=self.enterprise_customer, - ) - query_params = ( - f'enterprise_customer_uuid={str(self.enterprise_customer.uuid)}' - f'&lms_user_id={self.user.id + 1}' # Validates non-staff user can't get back data for another user - ) - response = self.client.get( - f"{settings.TEST_SERVER}{DEFAULT_ENTERPRISE_ENROLLMENT_INTENTION_LEARNER_STATUS_ENDPOINT}?{query_params}" - ) - - assert response.status_code == status.HTTP_200_OK - response_data = response.json() - assert response_data['lms_user_id'] == self.user.id - assert response_data['user_email'] == self.user.email - assert response_data['enrollment_statuses'] == { - 'needs_enrollment': { - 'enrollable': [ - self.get_default_enrollment_intention_with_learner_enrollment_state(enrollment_intention) - ], - 'not_enrollable': [], - }, - 'already_enrolled': [], - } - assert response_data['metadata'] == { - 'total_default_enterprise_enrollment_intentions': 1, - 'total_needs_enrollment': { - 'enrollable': 1, - 'not_enrollable': 0, - }, - 'total_already_enrolled': 0, - } From aa2b24e2cbb3241f874fa34bd6bab9c423fa48ad Mon Sep 17 00:00:00 2001 From: Zaman Afzal Date: Wed, 11 Dec 2024 15:13:12 +0500 Subject: [PATCH 04/12] feat: Added management command to update UIDs (#2300) --- CHANGELOG.rst | 4 + enterprise/__init__.py | 2 +- .../update_enterprise_social_auth_uids.py | 165 ++++++++++++++++++ 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 enterprise/management/commands/update_enterprise_social_auth_uids.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fdd3e8e00..a474efecb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,10 @@ Unreleased ---------- * nothing unreleased +[5.4.2] +-------- +* feat: Added a management command to update the Social Auth UID's for an enterprise. + [5.4.1] -------- * fix: The default enrollment ``learner_status`` view now considers intended courses diff --git a/enterprise/__init__.py b/enterprise/__init__.py index e1699b91c..4202621e2 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "5.4.1" +__version__ = "5.4.2" diff --git a/enterprise/management/commands/update_enterprise_social_auth_uids.py b/enterprise/management/commands/update_enterprise_social_auth_uids.py new file mode 100644 index 000000000..8c40bf67f --- /dev/null +++ b/enterprise/management/commands/update_enterprise_social_auth_uids.py @@ -0,0 +1,165 @@ +""" +Django management command to update the social auth records UID +""" + +import csv +import logging + +from django.core.exceptions import ValidationError +from django.core.management.base import BaseCommand +from django.db import transaction + +try: + from social_django.models import UserSocialAuth +except ImportError: + UserSocialAuth = None + +logger = logging.getLogger(__name__) + + +class CSVUpdateError(Exception): + """Custom exception for CSV update process.""" + pass # pylint: disable=unnecessary-pass + + +class Command(BaseCommand): + """ + Update the enterprise related social auth records UID to the new one. + + Example usage: + ./manage.py lms update_enterprise_social_auth_uids csv_file_path + ./manage.py lms update_enterprise_social_auth_uids csv_file_path --old-prefix="slug:" --new-prefix="slug:x|{}@xyz" + ./manage.py lms update_enterprise_social_auth_uids csv_file_path --no-dry-run + + """ + + help = 'Records update from CSV with console logging' + + def add_arguments(self, parser): + parser.add_argument('csv_file', type=str, help='Path to the CSV file') + parser.add_argument( + '--old_prefix', + type=str, + default=None, + help='Optional old prefix for old UID. If not provided, uses CSV value.' + ) + parser.add_argument( + '--new_prefix', + type=str, + default=None, + help='Optional new prefix for new UID. If not provided, uses CSV value.' + ) + parser.add_argument( + '--no-dry-run', + action='store_false', + dest='dry_run', + default=True, + help='Actually save changes instead of simulating' + ) + + def handle(self, *args, **options): + logger.info("Command has started...") + csv_path = options['csv_file'] + dry_run = options['dry_run'] + old_prefix = options['old_prefix'] + new_prefix = options['new_prefix'] + + total_processed = 0 + total_updated = 0 + total_errors = 0 + + try: + with open(csv_path, 'r') as csvfile: + reader = csv.DictReader(csvfile) + + for row_num, row in enumerate(reader, start=1): + total_processed += 1 + + try: + with transaction.atomic(): + if self.update_record(row, dry_run, old_prefix, new_prefix): + total_updated += 1 + + except Exception as row_error: # pylint: disable=broad-except + total_errors += 1 + error_msg = f"Row {row_num} update failed: {row} - Error: {str(row_error)}" + logger.error(error_msg, exc_info=True) + + summary_msg = ( + f"CSV Update Summary:\n" + f"Total Records Processed: {total_processed}\n" + f"Records Successfully Updated: {total_updated}\n" + f"Errors Encountered: {total_errors}\n" + f"Dry Run Mode: {'Enabled' if dry_run else 'Disabled'}" + ) + logger.info(summary_msg) + except IOError as io_error: + logger.critical(f"File I/O error: {str(io_error)}") + + except Exception as e: # pylint: disable=broad-except + logger.critical(f"Critical error in CSV processing: {str(e)}") + + def update_record(self, row, dry_run=True, old_prefix=None, new_prefix=None): + """ + Update a single record, applying optional prefixes to UIDs if provided. + + Args: + row (dict): CSV row data + dry_run (bool): Whether to simulate or actually save changes + old_prefix (str): Prefix to apply to the old UID + new_prefix (str): Prefix to apply to the new UID + + Returns: + bool: Whether the update was successful + """ + try: + old_uid = row.get('old-uid') + new_uid = row.get('new-uid') + + # Validating that both values are present + if not old_uid or not new_uid: + raise CSVUpdateError("Missing required UID fields") + + # Construct dynamic UIDs + old_uid_with_prefix = f'{old_prefix}{old_uid}' if old_prefix else old_uid + new_uid_with_prefix = ( + new_prefix.format(new_uid) if new_prefix and '{}' in new_prefix + else f"{new_prefix}{new_uid}" if new_prefix + else new_uid + ) + + instance_with_old_uid = UserSocialAuth.objects.filter(uid=old_uid_with_prefix).first() + + if not instance_with_old_uid: + raise CSVUpdateError(f"No record found with old UID {old_uid_with_prefix}") + + instance_with_new_uid = UserSocialAuth.objects.filter(uid=new_uid_with_prefix).first() + if instance_with_new_uid: + log_entry = f"Warning: Existing record with new UID {new_uid_with_prefix} is deleting." + logger.info(log_entry) + if not dry_run: + instance_with_new_uid.delete() + + if not dry_run: + instance_with_old_uid.uid = new_uid_with_prefix + instance_with_old_uid.save() + + log_entry = f"Successfully updated record: Old UID {old_uid_with_prefix} → New UID {new_uid_with_prefix}" + logger.info(log_entry) + + return True + + except ValidationError as ve: + error_msg = f"Validation error: {ve}" + logger.error(error_msg) + raise + + except CSVUpdateError as update_error: + error_msg = f"Update processing error: {update_error}" + logger.error(error_msg) + raise + + except Exception as e: + error_msg = f"Unexpected error during record update: {e}" + logger.error(error_msg, exc_info=True) + raise From 6b699986ee2366221c5baeaca17f9280c38044c8 Mon Sep 17 00:00:00 2001 From: Bilal Qamar <59555732+BilalQamar95@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:57:08 +0500 Subject: [PATCH 05/12] chore: update github actions cache from v2 to v4 (#2303) --- .github/workflows/mysql8-migrations.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mysql8-migrations.yml b/.github/workflows/mysql8-migrations.yml index cb8598b0a..76da2ace5 100644 --- a/.github/workflows/mysql8-migrations.yml +++ b/.github/workflows/mysql8-migrations.yml @@ -39,7 +39,7 @@ jobs: echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT - name: Cache pip dependencies id: cache-dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ steps.pip-cache-dir.outputs.dir }} key: ${{ runner.os }}-pip-${{ hashFiles('requirements/pip_tools.txt') }} From e5aa30b0303ea89513926e243e6fa7233c4e4cd8 Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Tue, 17 Dec 2024 10:00:24 -0500 Subject: [PATCH 06/12] feat: expose enterprise.learner_bff_enabled Waffle flag (#2306) --- CHANGELOG.rst | 8 +++- enterprise/__init__.py | 2 +- enterprise/toggles.py | 20 +++++++++ tests/test_enterprise/api/test_filters.py | 1 + tests/test_enterprise/api/test_views.py | 49 ++++++++++++++--------- 5 files changed, 57 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a474efecb..58c97197b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,17 +17,21 @@ Unreleased ---------- * nothing unreleased +[5.5.0] +------- +* feat: introduce Waffle flag for enabling the Learner Portal BFF API. + [5.4.2] -------- * feat: Added a management command to update the Social Auth UID's for an enterprise. [5.4.1] --------- +------- * fix: The default enrollment ``learner_status`` view now considers intended courses from which the requested user has unenrolled as no-longer realizable. [5.4.0] --------- +------- * chore: Update python requirements. [5.3.1] diff --git a/enterprise/__init__.py b/enterprise/__init__.py index 4202621e2..961aea59c 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "5.4.2" +__version__ = "5.5.0" diff --git a/enterprise/toggles.py b/enterprise/toggles.py index 003e5618b..23ff16102 100644 --- a/enterprise/toggles.py +++ b/enterprise/toggles.py @@ -67,6 +67,18 @@ ENTERPRISE_LOG_PREFIX, ) +# .. toggle_name: enterprise.enterprise_learner_bff_enabled +# .. toggle_implementation: WaffleFlag +# .. toggle_default: False +# .. toggle_description: Enables the enterprise learner BFF +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2024-12-16 +ENTERPRISE_LEARNER_BFF_ENABLED = WaffleFlag( + f'{ENTERPRISE_NAMESPACE}.learner_bff_enabled', + __name__, + ENTERPRISE_LOG_PREFIX, +) + def top_down_assignment_real_time_lcm(): """ @@ -103,6 +115,13 @@ def enterprise_customer_support_tool(): return ENTERPRISE_CUSTOMER_SUPPORT_TOOL.is_enabled() +def enterprise_learner_bff_enabled(): + """ + Returns whether the enterprise learner BFF is enabled. + """ + return ENTERPRISE_LEARNER_BFF_ENABLED.is_enabled() + + def enterprise_features(): """ Returns a dict of enterprise Waffle-based feature flags. @@ -113,4 +132,5 @@ def enterprise_features(): 'enterprise_groups_v1': enterprise_groups_v1(), 'enterprise_customer_support_tool': enterprise_customer_support_tool(), 'enterprise_groups_v2': enterprise_groups_v2(), + 'enterprise_learner_bff_enabled': enterprise_learner_bff_enabled(), } diff --git a/tests/test_enterprise/api/test_filters.py b/tests/test_enterprise/api/test_filters.py index f4b68efe3..6ab8484b0 100644 --- a/tests/test_enterprise/api/test_filters.py +++ b/tests/test_enterprise/api/test_filters.py @@ -305,6 +305,7 @@ def test_filter(self, is_staff, is_linked_to_enterprise, has_access): 'enterprise_groups_v1': False, 'enterprise_customer_support_tool': False, 'enterprise_groups_v2': False, + 'enterprise_learner_bff_enabled': False, } } assert response == mock_empty_200_success_response diff --git a/tests/test_enterprise/api/test_views.py b/tests/test_enterprise/api/test_views.py index be30cac5d..87e501024 100644 --- a/tests/test_enterprise/api/test_views.py +++ b/tests/test_enterprise/api/test_views.py @@ -73,6 +73,7 @@ ENTERPRISE_CUSTOMER_SUPPORT_TOOL, ENTERPRISE_GROUPS_V1, ENTERPRISE_GROUPS_V2, + ENTERPRISE_LEARNER_BFF_ENABLED, FEATURE_PREQUERY_SEARCH_SUGGESTIONS, TOP_DOWN_ASSIGNMENT_REAL_TIME_LCM, ) @@ -2018,63 +2019,64 @@ def test_enterprise_customer_support_tool( @ddt.data( # Request missing required permissions query param. (True, False, [], {}, False, {'detail': 'User is not allowed to access the view.'}, - False, False, False, False, False), + False, False, False, False, False, False), # Staff user that does not have the specified group permission. (True, False, [], {'permissions': ['enterprise_enrollment_api_access']}, False, - {'detail': 'User is not allowed to access the view.'}, False, False, False, False, False), + {'detail': 'User is not allowed to access the view.'}, False, False, False, False, False, False), # Staff user that does have the specified group permission. (True, False, ['enterprise_enrollment_api_access'], {'permissions': ['enterprise_enrollment_api_access']}, - True, None, False, False, False, False, False), + True, None, False, False, False, False, False, False), # Non staff user that is not linked to the enterprise, nor do they have the group permission. (False, False, [], {'permissions': ['enterprise_enrollment_api_access']}, False, - {'detail': 'User is not allowed to access the view.'}, False, False, False, False, False), + {'detail': 'User is not allowed to access the view.'}, False, False, False, False, False, False), # Non staff user that is not linked to the enterprise, but does have the group permission. (False, False, ['enterprise_enrollment_api_access'], {'permissions': ['enterprise_enrollment_api_access']}, - False, None, False, False, False, False, False), + False, None, False, False, False, False, False, False), # Non staff user that is linked to the enterprise, but does not have the group permission. (False, True, [], {'permissions': ['enterprise_enrollment_api_access']}, False, - {'detail': 'User is not allowed to access the view.'}, False, False, False, False, False), + {'detail': 'User is not allowed to access the view.'}, False, False, False, False, False, False), # Non staff user that is linked to the enterprise and does have the group permission (False, True, ['enterprise_enrollment_api_access'], {'permissions': ['enterprise_enrollment_api_access']}, - True, None, False, False, False, False, False), + True, None, False, False, False, False, False, False), # Non staff user that is linked to the enterprise and has group permission and the request has passed # multiple groups to check. (False, True, ['enterprise_enrollment_api_access'], {'permissions': ['enterprise_enrollment_api_access', 'enterprise_data_api_access']}, True, None, False, - False, False, False, False), + False, False, False, False, False), # Staff user with group permission filtering on non existent enterprise id. (True, False, ['enterprise_enrollment_api_access'], {'permissions': ['enterprise_enrollment_api_access'], 'enterprise_id': FAKE_UUIDS[1]}, False, - None, False, False, False, False, False), + None, False, False, False, False, False, False), # Staff user with group permission filtering on enterprise id successfully. (True, False, ['enterprise_enrollment_api_access'], {'permissions': ['enterprise_enrollment_api_access'], 'enterprise_id': FAKE_UUIDS[0]}, True, - None, False, False, False, False, False), + None, False, False, False, False, False, False), # Staff user with group permission filtering on search param with no results. (True, False, ['enterprise_enrollment_api_access'], {'permissions': ['enterprise_enrollment_api_access'], 'search': 'blah'}, False, - None, False, False, False, False, False), + None, False, False, False, False, False, False), # Staff user with group permission filtering on search param with results. (True, False, ['enterprise_enrollment_api_access'], {'permissions': ['enterprise_enrollment_api_access'], 'search': 'test'}, True, - None, False, False, False, False, False), + None, False, False, False, False, False, False), # Staff user with group permission filtering on slug with results. (True, False, ['enterprise_enrollment_api_access'], {'permissions': ['enterprise_enrollment_api_access'], 'slug': TEST_SLUG}, True, - None, False, False, False, False, False), + None, False, False, False, False, False, False), # Staff user with group permissions filtering on slug with no results. (True, False, ['enterprise_enrollment_api_access'], {'permissions': ['enterprise_enrollment_api_access'], 'slug': 'blah'}, False, - None, False, False, False, False, False), + None, False, False, False, False, False, False), # Staff user with group permission filtering on slug with results, with # top down assignment & real-time LCM feature enabled, # prequery search results enabled and # enterprise groups v1 feature enabled # enterprise groups v2 feature enabled # enterprise customer support tool enabled + # enterprise learner bff enabled (True, False, ['enterprise_enrollment_api_access'], {'permissions': ['enterprise_enrollment_api_access'], 'slug': TEST_SLUG}, True, - None, True, True, True, True, True), + None, True, True, True, True, True, True), ) @ddt.unpack @mock.patch('enterprise.utils.get_logo_url') @@ -2090,7 +2092,8 @@ def test_enterprise_customer_with_access_to( feature_prequery_search_suggestions_enabled, enterprise_groups_v1_enabled, enterprise_groups_v2_enabled, - enterprise_customer_support_tool, + enterprise_customer_support_tool_enabled, + enterprise_learner_bff_enabled, mock_get_logo_url, ): """ @@ -2156,7 +2159,6 @@ def test_enterprise_customer_with_access_to( ENTERPRISE_GROUPS_V1, active=enterprise_groups_v1_enabled ): - response = client.get( f"{settings.TEST_SERVER}{ENTERPRISE_CUSTOMER_WITH_ACCESS_TO_ENDPOINT}?{urlencode(query_params, True)}" ) @@ -2169,9 +2171,15 @@ def test_enterprise_customer_with_access_to( ) with override_waffle_flag( ENTERPRISE_CUSTOMER_SUPPORT_TOOL, - active=enterprise_customer_support_tool + active=enterprise_customer_support_tool_enabled + ): + response = client.get( + f"{settings.TEST_SERVER}{ENTERPRISE_CUSTOMER_WITH_ACCESS_TO_ENDPOINT}?{urlencode(query_params, True)}" + ) + with override_waffle_flag( + ENTERPRISE_LEARNER_BFF_ENABLED, + active=enterprise_learner_bff_enabled ): - response = client.get( f"{settings.TEST_SERVER}{ENTERPRISE_CUSTOMER_WITH_ACCESS_TO_ENDPOINT}?{urlencode(query_params, True)}" ) @@ -2243,8 +2251,9 @@ def test_enterprise_customer_with_access_to( 'top_down_assignment_real_time_lcm': is_top_down_assignment_real_time_lcm_enabled, 'feature_prequery_search_suggestions': feature_prequery_search_suggestions_enabled, 'enterprise_groups_v1': enterprise_groups_v1_enabled, - 'enterprise_customer_support_tool': enterprise_customer_support_tool, + 'enterprise_customer_support_tool': enterprise_customer_support_tool_enabled, 'enterprise_groups_v2': enterprise_groups_v2_enabled, + 'enterprise_learner_bff_enabled': enterprise_learner_bff_enabled, } } assert response in (expected_error, mock_empty_200_success_response) From 3995cc1994e9065194254b642c05d43e2e095f1a Mon Sep 17 00:00:00 2001 From: Brian Citro <67378070+bcitro@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:42:53 -0500 Subject: [PATCH 07/12] fix: Fixed the query fetching enterprise customer members (#2305) --- CHANGELOG.rst | 4 ++++ enterprise/__init__.py | 2 +- enterprise/api/v1/views/enterprise_customer_members.py | 2 +- enterprise/models.py | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 58c97197b..dd1d451ad 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,10 @@ Unreleased ---------- * nothing unreleased +[5.5.1] +-------- +* fix: Fixed the query fetching enterprise customer members + [5.5.0] ------- * feat: introduce Waffle flag for enabling the Learner Portal BFF API. diff --git a/enterprise/__init__.py b/enterprise/__init__.py index 961aea59c..42f110a41 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "5.5.0" +__version__ = "5.5.1" diff --git a/enterprise/api/v1/views/enterprise_customer_members.py b/enterprise/api/v1/views/enterprise_customer_members.py index ca292aa4c..2c6437671 100644 --- a/enterprise/api/v1/views/enterprise_customer_members.py +++ b/enterprise/api/v1/views/enterprise_customer_members.py @@ -78,7 +78,7 @@ def get_members(self, request, *args, **kwargs): au.id, au.email, au.date_joined, - coalesce(NULLIF(aup.name, ''), (au.first_name || ' ' || au.last_name)) as full_name + coalesce(NULLIF(aup.name, ''), au.username) as full_name FROM enterprise_enterprisecustomeruser ecu INNER JOIN auth_user as au on ecu.user_id = au.id LEFT JOIN auth_userprofile as aup on au.id = aup.user_id diff --git a/enterprise/models.py b/enterprise/models.py index 6e559fc63..3971c5697 100644 --- a/enterprise/models.py +++ b/enterprise/models.py @@ -4699,7 +4699,7 @@ def _get_filtered_ecu_ids(self, user_query): with users as ( select ecu.id, au.email, - coalesce(NULLIF(aup.name, ''), (au.first_name || ' ' || au.last_name)) as full_name + coalesce(NULLIF(aup.name, ''), au.username) as full_name from enterprise_enterprisecustomeruser ecu inner join auth_user au on ecu.user_id = au.id left join auth_userprofile aup on au.id = aup.user_id From 7cfed71a8d91338a96b1a65854d7701d63445dcb Mon Sep 17 00:00:00 2001 From: Marlon Keating Date: Wed, 18 Dec 2024 00:55:14 +0000 Subject: [PATCH 08/12] feat: Add page_size support to enterprise-customer-members endpoint --- CHANGELOG.rst | 4 ++++ enterprise/__init__.py | 2 +- enterprise/api/v1/views/enterprise_customer_members.py | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dd1d451ad..5d3576f4c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,10 @@ Unreleased ---------- * nothing unreleased +[5.5.2] +-------- +* feat: Add page_size support to enterprise-customer-members endpoint + [5.5.1] -------- * fix: Fixed the query fetching enterprise customer members diff --git a/enterprise/__init__.py b/enterprise/__init__.py index 42f110a41..f930f65df 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "5.5.1" +__version__ = "5.5.2" diff --git a/enterprise/api/v1/views/enterprise_customer_members.py b/enterprise/api/v1/views/enterprise_customer_members.py index 2c6437671..680b94d5a 100644 --- a/enterprise/api/v1/views/enterprise_customer_members.py +++ b/enterprise/api/v1/views/enterprise_customer_members.py @@ -22,6 +22,7 @@ class EnterpriseCustomerMembersPaginator(PageNumberPagination): """Custom paginator for the enterprise customer members.""" page_size = 10 + page_size_query_param = 'page_size' def get_paginated_response(self, data): """Return a paginated style `Response` object for the given output data.""" From 050439828056dcaadc08dc15d31e3d41da7dd9ca Mon Sep 17 00:00:00 2001 From: edX requirements bot Date: Sun, 5 Jan 2025 21:30:50 -0500 Subject: [PATCH 09/12] chore: Upgrade Python requirements --- requirements/celery53.txt | 2 +- requirements/ci.txt | 2 +- requirements/dev.txt | 20 +++--- requirements/doc.txt | 10 +-- requirements/edx-platform-constraints.txt | 76 +++++++++-------------- requirements/js_test.txt | 10 +-- requirements/test-master.txt | 2 +- requirements/test.txt | 8 +-- 8 files changed, 56 insertions(+), 74 deletions(-) diff --git a/requirements/celery53.txt b/requirements/celery53.txt index f429cad2c..85fc551b4 100644 --- a/requirements/celery53.txt +++ b/requirements/celery53.txt @@ -1,7 +1,7 @@ amqp==5.3.1 billiard==4.2.1 celery==5.4.0 -click==8.1.7 +click==8.1.8 click-didyoumean==0.3.1 click-repl==0.3.0 kombu==5.4.2 diff --git a/requirements/ci.txt b/requirements/ci.txt index f6c3374e6..8fa480206 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -24,5 +24,5 @@ tox==3.28.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r requirements/ci.in -virtualenv==20.28.0 +virtualenv==20.28.1 # via tox diff --git a/requirements/dev.txt b/requirements/dev.txt index a463e0d4a..7c88c5505 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -55,7 +55,7 @@ asn1crypto==1.5.1 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # snowflake-connector-python -astroid==3.3.6 +astroid==3.3.8 # via # pylint # pylint-celery @@ -127,7 +127,7 @@ charset-normalizer==2.0.12 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # requests # snowflake-connector-python -click==8.1.7 +click==8.1.8 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -168,7 +168,7 @@ code-annotations==2.0.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # edx-lint # edx-toggles -coverage[toml]==7.6.9 +coverage[toml]==7.6.10 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # pytest-cov @@ -192,7 +192,7 @@ defusedxml==0.7.1 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # djangorestframework-xml -diff-cover==9.2.0 +diff-cover==9.2.1 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt dill==0.3.9 # via pylint @@ -408,7 +408,7 @@ factory-boy==3.3.1 # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt -faker==33.1.0 +faker==33.3.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt @@ -526,7 +526,7 @@ newrelic==10.3.1 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # edx-django-utils -nh3==0.2.19 +nh3==0.2.20 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # readme-renderer @@ -650,13 +650,13 @@ pycparser==2.22 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # cffi -pydata-sphinx-theme==0.16.0 +pydata-sphinx-theme==0.16.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # sphinx-book-theme pydocstyle==6.3.0 # via -r requirements/dev.in -pygments==2.18.0 +pygments==2.19.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt @@ -675,7 +675,7 @@ pyjwt[crypto]==2.10.1 # edx-drf-extensions # edx-rest-api-client # snowflake-connector-python -pylint==3.3.2 +pylint==3.3.3 # via # edx-lint # pylint-celery @@ -963,7 +963,7 @@ vine==5.1.0 # amqp # celery # kombu -virtualenv==20.28.0 +virtualenv==20.28.1 # via tox wcwidth==0.2.13 # via diff --git a/requirements/doc.txt b/requirements/doc.txt index b0f8c51f5..85b032943 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -79,7 +79,7 @@ charset-normalizer==2.0.12 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # requests # snowflake-connector-python -click==8.1.7 +click==8.1.8 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # celery @@ -248,7 +248,7 @@ factory-boy==3.3.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r requirements/doc.in -faker==33.1.0 +faker==33.3.0 # via factory-boy fastavro==1.9.7 # via @@ -307,7 +307,7 @@ newrelic==10.3.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-django-utils -nh3==0.2.19 +nh3==0.2.20 # via readme-renderer oauthlib==3.2.2 # via @@ -369,9 +369,9 @@ pycparser==2.22 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # cffi -pydata-sphinx-theme==0.16.0 +pydata-sphinx-theme==0.16.1 # via sphinx-book-theme -pygments==2.18.0 +pygments==2.19.0 # via # accessible-pygments # doc8 diff --git a/requirements/edx-platform-constraints.txt b/requirements/edx-platform-constraints.txt index fc270263a..2d5a91613 100644 --- a/requirements/edx-platform-constraints.txt +++ b/requirements/edx-platform-constraints.txt @@ -56,7 +56,9 @@ backoff==1.10.0 bcrypt==4.2.1 # via paramiko beautifulsoup4==4.12.3 - # via pynliner + # via + # openedx-forum + # pynliner # via celery bleach[css]==6.2.0 # via @@ -98,7 +100,6 @@ camel-converter[pydantic]==4.0.1 # openedx-learning certifi==2024.8.30 # via - # -r requirements/edx/paver.txt # elasticsearch # py2neo # requests @@ -113,7 +114,6 @@ chardet==5.2.0 charset-normalizer==2.0.12 # via # -c requirements/edx/../constraints.txt - # -r requirements/edx/paver.txt # requests # snowflake-connector-python chem==1.3.0 @@ -228,6 +228,7 @@ django==4.2.17 # openedx-django-wiki # openedx-events # openedx-filters + # openedx-forum # openedx-learning # ora2 # social-auth-app-django @@ -372,14 +373,13 @@ djangorestframework==3.14.0 # edx-organizations # edx-proctoring # edx-submissions + # openedx-forum # openedx-learning # ora2 # super-csv djangorestframework-xml==2.0.0 dnspython==2.7.0 - # via - # -r requirements/edx/paver.txt - # pymongo + # via pymongo done-xblock==2.4.0 # via -r requirements/edx/bundled.in drf-jwt==1.19.2 @@ -456,7 +456,7 @@ edx-drf-extensions==10.5.0 # edx-when # edxval # openedx-learning -edx-enterprise==5.3.1 +edx-enterprise==5.5.2 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in @@ -477,7 +477,6 @@ edx-name-affirmation==3.0.1 edx-opaque-keys[django]==2.11.0 # via # -r requirements/edx/kernel.in - # -r requirements/edx/paver.txt # edx-bulk-grades # edx-ccx-keys # edx-completion @@ -492,7 +491,7 @@ edx-opaque-keys[django]==2.11.0 # ora2 edx-organizations==6.13.0 # via -r requirements/edx/kernel.in -edx-proctoring==5.0.1 +edx-proctoring==4.18.4 # via # -r requirements/edx/kernel.in # edx-proctoring-proctortrack @@ -503,7 +502,9 @@ edx-rest-api-client==6.0.0 # edx-enterprise # edx-proctoring edx-search==4.1.1 - # via -r requirements/edx/kernel.in + # via + # -r requirements/edx/kernel.in + # openedx-forum edx-sga==0.25.0 # via -r requirements/edx/bundled.in edx-submissions==3.8.3 @@ -529,12 +530,13 @@ edx-when==2.5.0 # via # -r requirements/edx/kernel.in # edx-proctoring -edxval==2.6.1 +edxval==2.7.0 # via -r requirements/edx/kernel.in elasticsearch==7.9.1 # via # -c requirements/edx/../common_constraints.txt # edx-search + # openedx-forum enmerkar==0.7.1 # via enmerkar-underscore enmerkar-underscore==2.3.1 @@ -631,7 +633,6 @@ icalendar==6.1.0 # via -r requirements/edx/kernel.in idna==3.10 # via - # -r requirements/edx/paver.txt # optimizely-sdk # requests # snowflake-connector-python @@ -681,15 +682,10 @@ laboratory==1.0.2 # via -r requirements/edx/kernel.in lazy==1.6 # via - # -r requirements/edx/paver.txt # acid-xblock # lti-consumer-xblock # ora2 # xblock -libsass==0.10.0 - # via - # -c requirements/edx/../constraints.txt - # -r requirements/edx/paver.txt loremipsum==1.0.5 # via ora2 lti-consumer-xblock==9.12.0 @@ -727,7 +723,6 @@ markdown==3.3.7 # xblock-poll markupsafe==3.0.2 # via - # -r requirements/edx/paver.txt # chem # jinja2 # mako @@ -739,8 +734,6 @@ meilisearch==0.33.0 # via # -r requirements/edx/kernel.in # edx-search -mock==5.1.0 - # via -r requirements/edx/paver.txt mongoengine==0.29.1 # via -r requirements/edx/kernel.in monotonic==1.6 @@ -758,7 +751,9 @@ multidict==6.1.0 # aiohttp # yarl mysqlclient==2.2.6 - # via -r requirements/edx/kernel.in + # via + # -r requirements/edx/kernel.in + # openedx-forum newrelic==10.3.1 # via edx-django-utils nh3==0.2.19 @@ -788,7 +783,9 @@ openai==0.28.1 # -c requirements/edx/../constraints.txt # edx-enterprise openedx-atlas==0.6.2 - # via -r requirements/edx/kernel.in + # via + # -r requirements/edx/kernel.in + # openedx-forum openedx-calc==4.0.1 # via -r requirements/edx/kernel.in openedx-django-pyfs==3.7.0 @@ -809,11 +806,13 @@ openedx-events==9.15.0 # edx-name-affirmation # event-tracking # ora2 -openedx-filters==1.11.0 +openedx-filters==1.12.0 # via # -r requirements/edx/kernel.in # lti-consumer-xblock # ora2 +openedx-forum==0.1.5 + # via -r requirements/edx/kernel.in openedx-learning==0.18.1 # via # -c requirements/edx/../constraints.txt @@ -839,7 +838,6 @@ path==16.11.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in - # -r requirements/edx/paver.txt # edx-i18n-tools # path-py path-py==12.5.0 @@ -847,12 +845,8 @@ path-py==12.5.0 # edx-enterprise # ora2 # staff-graded-xblock -paver==1.3.4 - # via -r requirements/edx/paver.txt pbr==6.1.0 - # via - # -r requirements/edx/paver.txt - # stevedore + # via stevedore pgpy==0.6.0 piexif==1.1.3 # via -r requirements/edx/kernel.in @@ -885,7 +879,7 @@ protobuf==5.29.1 # proto-plus psutil==6.1.0 # via - # -r requirements/edx/paver.txt + # -r requirements/edx/kernel.in # edx-django-utils py2neo @ https://github.com/overhangio/py2neo/releases/download/2021.2.3/py2neo-2021.2.3.tar.gz # via @@ -913,9 +907,7 @@ pydantic==2.10.3 pydantic-core==2.27.1 # via pydantic pygments==2.18.0 - # via - # -r requirements/edx/bundled.in - # py2neo + # via py2neo pyjwkest==1.4.2 # via # -r requirements/edx/kernel.in @@ -938,15 +930,15 @@ pylatexenc==2.10 pylti1p3==2.0.0 # via -r requirements/edx/kernel.in pymemcache==4.0.0 - # via -r requirements/edx/paver.txt + # via -r requirements/edx/kernel.in pymongo==4.4.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in - # -r requirements/edx/paver.txt # edx-opaque-keys # event-tracking # mongoengine + # openedx-forum # openedx-mongodbproxy pynacl==1.5.0 # via @@ -984,8 +976,6 @@ python-dateutil==2.9.0.post0 # xblock python-ipware==3.0.0 # via django-ipware -python-memcached==1.62 - # via -r requirements/edx/paver.txt python-slugify==8.0.4 # via code-annotations python-swiftclient==4.6.0 @@ -1041,7 +1031,6 @@ regex==2024.11.6 # via nltk requests==2.32.3 # via - # -r requirements/edx/paver.txt # algoliasearch # analytics-python # cachecontrol @@ -1056,6 +1045,7 @@ requests==2.32.3 # mailsnake # meilisearch # openai + # openedx-forum # optimizely-sdk # pyjwkest # pylti1p3 @@ -1104,7 +1094,6 @@ simplejson==3.19.3 six==1.17.0 # via # -r requirements/edx/kernel.in - # -r requirements/edx/paver.txt # analytics-python # codejail-includes # crowdsourcehinter-xblock @@ -1120,9 +1109,7 @@ six==1.17.0 # fs-s3fs # html5lib # interchange - # libsass # optimizely-sdk - # paver # py2neo # pyjwkest # python-dateutil @@ -1159,7 +1146,6 @@ staff-graded-xblock==2.3.0 stevedore==5.4.0 # via # -r requirements/edx/kernel.in - # -r requirements/edx/paver.txt # code-annotations # edx-ace # edx-django-utils @@ -1182,7 +1168,6 @@ tqdm==4.67.1 # openai typing-extensions==4.12.2 # via - # -r requirements/edx/paver.txt # django-countries # edx-opaque-keys # jwcrypto @@ -1206,7 +1191,6 @@ uritemplate==4.1.1 # google-api-python-client urllib3==2.2.3 # via - # -r requirements/edx/paver.txt # botocore # elasticsearch # py2neo @@ -1221,8 +1205,6 @@ voluptuous==0.15.2 # via ora2 walrus==0.9.4 # via edx-event-bus-redis -watchdog==6.0.0 - # via -r requirements/edx/paver.txt wcwidth==0.2.13 # via prompt-toolkit web-fragments==2.2.0 @@ -1243,7 +1225,7 @@ webob==1.8.9 # -r requirements/edx/kernel.in # xblock wrapt==1.17.0 - # via -r requirements/edx/paver.txt + # via -r requirements/edx/kernel.in xblock[django]==5.1.0 # via # -r requirements/edx/kernel.in diff --git a/requirements/js_test.txt b/requirements/js_test.txt index 60649b9f6..385953082 100644 --- a/requirements/js_test.txt +++ b/requirements/js_test.txt @@ -4,7 +4,7 @@ # # make upgrade # -attrs==24.2.0 +attrs==24.3.0 # via # outcome # trio @@ -12,7 +12,7 @@ autocommand==2.2.2 # via jaraco-text backports-tarfile==1.2.0 # via jaraco-context -certifi==2024.8.30 +certifi==2024.12.14 # via selenium cheroot==10.0.1 # via cherrypy @@ -75,11 +75,11 @@ sniffio==1.3.1 # via trio sortedcontainers==2.4.0 # via trio -tempora==5.7.0 +tempora==5.8.0 # via # -r requirements/js_test.in # portend -trio==0.27.0 +trio==0.28.0 # via # selenium # trio-websocket @@ -87,7 +87,7 @@ trio-websocket==0.11.1 # via selenium typing-extensions==4.12.2 # via selenium -urllib3[socks]==2.2.3 +urllib3[socks]==2.3.0 # via selenium websocket-client==1.8.0 # via selenium diff --git a/requirements/test-master.txt b/requirements/test-master.txt index c56258e76..2dd2f761e 100644 --- a/requirements/test-master.txt +++ b/requirements/test-master.txt @@ -66,7 +66,7 @@ charset-normalizer==2.0.12 # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # requests # snowflake-connector-python -click==8.1.7 +click==8.1.8 # via # celery # click-didyoumean diff --git a/requirements/test.txt b/requirements/test.txt index 853dce841..624db5d7e 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -90,7 +90,7 @@ code-annotations==2.0.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-toggles -coverage[toml]==7.6.9 +coverage[toml]==7.6.10 # via pytest-cov cryptography==44.0.0 # via @@ -108,7 +108,7 @@ defusedxml==0.7.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # djangorestframework-xml -diff-cover==9.2.0 +diff-cover==9.2.1 # via -r requirements/test.in # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/common_constraints.txt @@ -230,7 +230,7 @@ factory-boy==3.3.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r requirements/test.in -faker==33.1.0 +faker==33.3.0 # via factory-boy fastavro==1.9.7 # via @@ -354,7 +354,7 @@ pycparser==2.22 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # cffi -pygments==2.18.0 +pygments==2.19.0 # via diff-cover pyjwt[crypto]==2.10.1 # via From fd8c1c5eccf44d27e53810de4282bc2a08fc7c1e Mon Sep 17 00:00:00 2001 From: edX requirements bot <49161187+edx-requirements-bot@users.noreply.github.com> Date: Mon, 13 Jan 2025 13:38:12 -0500 Subject: [PATCH 10/12] chore: Upgrade Python requirements (#2312) --- requirements/common_constraints.txt | 4 ++ requirements/dev.txt | 29 ++++---- requirements/doc.txt | 29 ++++---- requirements/edx-platform-constraints.txt | 82 ++++++++++++----------- requirements/js_test.txt | 6 +- requirements/test-master.txt | 25 +++---- requirements/test.txt | 29 ++++---- 7 files changed, 110 insertions(+), 94 deletions(-) diff --git a/requirements/common_constraints.txt b/requirements/common_constraints.txt index 84139d660..b5f13fa87 100644 --- a/requirements/common_constraints.txt +++ b/requirements/common_constraints.txt @@ -25,3 +25,7 @@ elasticsearch<7.14.0 # Cause: https://github.com/openedx/edx-lint/issues/458 # This can be unpinned once https://github.com/openedx/edx-lint/issues/459 has been resolved. pip<24.3 + +# Cause: https://github.com/openedx/edx-lint/issues/475 +# This can be unpinned once https://github.com/openedx/edx-lint/issues/476 has been resolved. +urllib3<2.3.0 diff --git a/requirements/dev.txt b/requirements/dev.txt index 7c88c5505..27890001b 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -14,13 +14,13 @@ aiohappyeyeballs==2.4.4 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # aiohttp -aiohttp==3.11.9 +aiohttp==3.11.11 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # openai -aiosignal==1.3.1 +aiosignal==1.3.2 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -59,7 +59,7 @@ astroid==3.3.8 # via # pylint # pylint-celery -attrs==24.2.0 +attrs==24.3.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -101,7 +101,7 @@ celery==5.4.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt -certifi==2024.8.30 +certifi==2024.12.14 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -161,7 +161,7 @@ click-repl==0.3.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # celery -code-annotations==2.0.0 +code-annotations==2.1.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -223,7 +223,7 @@ django==4.2.17 # edx-toggles # jsonfield # openedx-events -django-cache-memoize==0.2.0 +django-cache-memoize==0.2.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -408,12 +408,12 @@ factory-boy==3.3.1 # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt -faker==33.3.0 +faker==33.3.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # factory-boy -fastavro==1.9.7 +fastavro==1.10.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -465,7 +465,7 @@ isort==5.13.2 # via # -r requirements/dev.in # pylint -jinja2==3.1.4 +jinja2==3.1.5 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -520,7 +520,7 @@ multidict==6.1.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # aiohttp # yarl -newrelic==10.3.1 +newrelic==10.4.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -585,7 +585,7 @@ pgpy==0.6.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt -pillow==11.0.0 +pillow==11.1.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -624,7 +624,7 @@ propcache==0.2.1 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt # aiohttp # yarl -psutil==6.1.0 +psutil==6.1.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -656,7 +656,7 @@ pydata-sphinx-theme==0.16.1 # sphinx-book-theme pydocstyle==6.3.0 # via -r requirements/dev.in -pygments==2.19.0 +pygments==2.19.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt @@ -868,7 +868,7 @@ sphinxcontrib-serializinghtml==2.0.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # sphinx -sqlparse==0.5.2 +sqlparse==0.5.3 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -951,6 +951,7 @@ uritemplate==4.1.1 # drf-yasg urllib3==2.2.3 # via + # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/common_constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/doc.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test.txt diff --git a/requirements/doc.txt b/requirements/doc.txt index 85b032943..0f2c47f0b 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -10,11 +10,11 @@ aiohappyeyeballs==2.4.4 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp -aiohttp==3.11.9 +aiohttp==3.11.11 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # openai -aiosignal==1.3.1 +aiosignal==1.3.2 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp @@ -37,7 +37,7 @@ asn1crypto==1.5.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # snowflake-connector-python -attrs==24.2.0 +attrs==24.3.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp @@ -63,7 +63,7 @@ celery==5.4.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt -certifi==2024.8.30 +certifi==2024.12.14 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # requests @@ -100,7 +100,7 @@ click-repl==0.3.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # celery -code-annotations==2.0.0 +code-annotations==2.1.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-toggles @@ -140,7 +140,7 @@ django==4.2.17 # edx-toggles # jsonfield # openedx-events -django-cache-memoize==0.2.0 +django-cache-memoize==0.2.1 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt django-config-models==2.7.0 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -248,9 +248,9 @@ factory-boy==3.3.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r requirements/doc.in -faker==33.3.0 +faker==33.3.1 # via factory-boy -fastavro==1.9.7 +fastavro==1.10.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # openedx-events @@ -277,7 +277,7 @@ inflection==0.5.1 # drf-yasg iniconfig==2.0.0 # via pytest -jinja2==3.1.4 +jinja2==3.1.5 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # code-annotations @@ -303,7 +303,7 @@ multidict==6.1.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp # yarl -newrelic==10.3.1 +newrelic==10.4.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-django-utils @@ -338,7 +338,7 @@ pbr==6.1.0 # stevedore pgpy==0.6.0 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt -pillow==11.0.0 +pillow==11.1.0 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt platformdirs==4.3.6 # via @@ -355,7 +355,7 @@ propcache==0.2.1 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp # yarl -psutil==6.1.0 +psutil==6.1.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-django-utils @@ -371,7 +371,7 @@ pycparser==2.22 # cffi pydata-sphinx-theme==0.16.1 # via sphinx-book-theme -pygments==2.19.0 +pygments==2.19.1 # via # accessible-pygments # doc8 @@ -485,7 +485,7 @@ sphinxcontrib-qthelp==2.0.0 # via sphinx sphinxcontrib-serializinghtml==2.0.0 # via sphinx -sqlparse==0.5.2 +sqlparse==0.5.3 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # django @@ -534,6 +534,7 @@ uritemplate==4.1.1 # drf-yasg urllib3==2.2.3 # via + # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/common_constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # requests vine==5.1.0 diff --git a/requirements/edx-platform-constraints.txt b/requirements/edx-platform-constraints.txt index 2d5a91613..dca06a7d9 100644 --- a/requirements/edx-platform-constraints.txt +++ b/requirements/edx-platform-constraints.txt @@ -10,11 +10,11 @@ acid-xblock==0.4.1 # via -r requirements/edx/kernel.in aiohappyeyeballs==2.4.4 # via aiohttp -aiohttp==3.11.9 +aiohttp==3.11.11 # via # geoip2 # openai -aiosignal==1.3.1 +aiosignal==1.3.2 # via aiohttp algoliasearch==3.0.0 # via @@ -36,7 +36,7 @@ asgiref==3.8.1 # django-countries asn1crypto==1.5.1 # via snowflake-connector-python -attrs==24.2.0 +attrs==24.3.0 # via # -r requirements/edx/kernel.in # aiohttp @@ -70,13 +70,13 @@ bleach[css]==6.2.0 # xblock-poll boto==2.49.0 # via -r requirements/edx/kernel.in -boto3==1.35.76 +boto3==1.35.93 # via # -r requirements/edx/kernel.in # django-ses # fs-s3fs # ora2 -botocore==1.35.76 +botocore==1.35.93 # via # -r requirements/edx/kernel.in # boto3 @@ -98,7 +98,7 @@ camel-converter[pydantic]==4.0.1 # edx-enterprise # event-tracking # openedx-learning -certifi==2024.8.30 +certifi==2024.12.14 # via # elasticsearch # py2neo @@ -132,7 +132,7 @@ chem==1.3.0 click-plugins==1.1.1 # via celery # via celery -code-annotations==2.0.0 +code-annotations==2.1.0 # via # edx-enterprise # edx-toggles @@ -237,7 +237,7 @@ django==4.2.17 # xss-utils django-appconf==1.0.6 # via django-statici18n -django-cache-memoize==0.2.0 +django-cache-memoize==0.2.1 django-celery-results==2.5.1 # via -r requirements/edx/kernel.in django-classy-tags==4.1.0 @@ -274,7 +274,7 @@ django-ipware==7.0.1 # -r requirements/edx/kernel.in # edx-enterprise # edx-proctoring -django-js-asset==2.2.0 +django-js-asset==3.0.1 # via django-mptt django-method-override==1.0.4 # via -r requirements/edx/kernel.in @@ -309,7 +309,7 @@ django-oauth-toolkit==1.7.1 # -r requirements/edx/kernel.in # edx-enterprise django-object-actions==4.3.0 -django-pipeline==3.1.0 +django-pipeline==4.0.0 # via -r requirements/edx/kernel.in django-push-notifications==3.1.0 # via edx-ace @@ -319,7 +319,7 @@ django-sekizai==4.1.0 # via # -r requirements/edx/kernel.in # openedx-django-wiki -django-ses==4.3.0 +django-ses==4.3.1 # via -r requirements/edx/bundled.in # via # -c requirements/edx/../constraints.txt @@ -418,7 +418,7 @@ edx-celeryutils==1.3.0 # super-csv edx-codejail==3.5.2 # via -r requirements/edx/kernel.in -edx-completion==4.7.6 +edx-completion==4.7.8 # via -r requirements/edx/kernel.in edx-django-release-util==1.4.0 # via @@ -491,7 +491,7 @@ edx-opaque-keys[django]==2.11.0 # ora2 edx-organizations==6.13.0 # via -r requirements/edx/kernel.in -edx-proctoring==4.18.4 +edx-proctoring==5.0.1 # via # -r requirements/edx/kernel.in # edx-proctoring-proctortrack @@ -507,7 +507,7 @@ edx-search==4.1.1 # openedx-forum edx-sga==0.25.0 # via -r requirements/edx/bundled.in -edx-submissions==3.8.3 +edx-submissions==3.8.4 # via # -r requirements/edx/kernel.in # ora2 @@ -530,11 +530,12 @@ edx-when==2.5.0 # via # -r requirements/edx/kernel.in # edx-proctoring -edxval==2.7.0 +edxval==2.8.0 # via -r requirements/edx/kernel.in elasticsearch==7.9.1 # via # -c requirements/edx/../common_constraints.txt + # -c requirements/edx/../constraints.txt # edx-search # openedx-forum enmerkar==0.7.1 @@ -548,7 +549,7 @@ event-tracking==3.0.0 # edx-completion # edx-proctoring # edx-search -fastavro==1.9.7 +fastavro==1.10.0 # via openedx-events filelock==3.16.1 # via snowflake-connector-python @@ -574,16 +575,16 @@ geoip2==4.8.1 # via -r requirements/edx/kernel.in glob2==0.7 # via -r requirements/edx/kernel.in -google-api-core[grpc]==2.23.0 +google-api-core[grpc]==2.24.0 # via # firebase-admin # google-api-python-client # google-cloud-core # google-cloud-firestore # google-cloud-storage -google-api-python-client==2.154.0 +google-api-python-client==2.157.0 # via firebase-admin -google-auth==2.36.0 +google-auth==2.37.0 # via # google-api-core # google-api-python-client @@ -611,11 +612,11 @@ googleapis-common-protos==1.66.0 # via # google-api-core # grpcio-status -grpcio==1.68.1 +grpcio==1.69.0 # via # google-api-core # grpcio-status -grpcio-status==1.68.1 +grpcio-status==1.69.0 # via google-api-core gunicorn==23.0.0 # via -r requirements/edx/kernel.in @@ -649,7 +650,7 @@ ipaddress==1.0.23 # via -r requirements/edx/kernel.in isodate==0.7.2 # via python3-saml -jinja2==3.1.4 +jinja2==3.1.5 # via code-annotations jmespath==1.0.1 # via @@ -688,8 +689,10 @@ lazy==1.6 # xblock loremipsum==1.0.5 # via ora2 -lti-consumer-xblock==9.12.0 - # via -r requirements/edx/kernel.in +lti-consumer-xblock==9.12.1 + # via + # -c requirements/edx/../constraints.txt + # -r requirements/edx/kernel.in lxml[html-clean,html_clean]==5.3.0 # via # -r requirements/edx/kernel.in @@ -707,7 +710,7 @@ lxml-html-clean==0.4.1 # via lxml mailsnake==1.6.4 # via -r requirements/edx/bundled.in -mako==1.3.7 +mako==1.3.8 # via # -r requirements/edx/kernel.in # acid-xblock @@ -754,9 +757,9 @@ mysqlclient==2.2.6 # via # -r requirements/edx/kernel.in # openedx-forum -newrelic==10.3.1 +newrelic==10.4.0 # via edx-django-utils -nh3==0.2.19 +nh3==0.2.20 # via -r requirements/edx/kernel.in nltk==3.9.1 # via chem @@ -823,7 +826,7 @@ optimizely-sdk==4.1.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/bundled.in -ora2==6.14.1 +ora2==6.14.3 # via -r requirements/edx/bundled.in packaging==24.2 # via @@ -850,7 +853,7 @@ pbr==6.1.0 pgpy==0.6.0 piexif==1.1.3 # via -r requirements/edx/kernel.in -pillow==11.0.0 +pillow==11.1.0 # via # -r requirements/edx/kernel.in # edx-enterprise @@ -870,14 +873,14 @@ proto-plus==1.25.0 # via # google-api-core # google-cloud-firestore -protobuf==5.29.1 +protobuf==5.29.2 # via # google-api-core # google-cloud-firestore # googleapis-common-protos # grpcio-status # proto-plus -psutil==6.1.0 +psutil==6.1.1 # via # -r requirements/edx/kernel.in # edx-django-utils @@ -902,11 +905,11 @@ pycryptodomex==3.21.0 # edx-proctoring # lti-consumer-xblock # pyjwkest -pydantic==2.10.3 +pydantic==2.10.4 # via camel-converter -pydantic-core==2.27.1 +pydantic-core==2.27.2 # via pydantic -pygments==2.18.0 +pygments==2.19.1 # via py2neo pyjwkest==1.4.2 # via @@ -950,7 +953,7 @@ pyopenssl==24.3.0 # via # optimizely-sdk # snowflake-connector-python -pyparsing==3.2.0 +pyparsing==3.2.1 # via # chem # httplib2 @@ -1019,7 +1022,7 @@ random2==1.0.2 # via -r requirements/edx/kernel.in recommender-xblock==3.0.0 # via -r requirements/edx/bundled.in -redis==5.2.0 +redis==5.2.1 # via # -r requirements/edx/kernel.in # walrus @@ -1076,7 +1079,7 @@ s3transfer==0.10.4 # via boto3 sailthru-client==2.2.3 # via edx-ace -scipy==1.14.1 +scipy==1.15.0 # via # chem # openedx-calc @@ -1139,7 +1142,7 @@ sortedcontainers==2.4.0 # snowflake-connector-python soupsieve==2.6 # via beautifulsoup4 -sqlparse==0.5.2 +sqlparse==0.5.3 # via django staff-graded-xblock==2.3.0 # via -r requirements/edx/bundled.in @@ -1191,6 +1194,7 @@ uritemplate==4.1.1 # google-api-python-client urllib3==2.2.3 # via + # -c requirements/edx/../common_constraints.txt # botocore # elasticsearch # py2neo @@ -1224,6 +1228,8 @@ webob==1.8.9 # via # -r requirements/edx/kernel.in # xblock +wheel==0.45.1 + # via django-pipeline wrapt==1.17.0 # via -r requirements/edx/kernel.in xblock[django]==5.1.0 diff --git a/requirements/js_test.txt b/requirements/js_test.txt index 385953082..791410659 100644 --- a/requirements/js_test.txt +++ b/requirements/js_test.txt @@ -87,8 +87,10 @@ trio-websocket==0.11.1 # via selenium typing-extensions==4.12.2 # via selenium -urllib3[socks]==2.3.0 - # via selenium +urllib3[socks]==2.2.3 + # via + # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/common_constraints.txt + # selenium websocket-client==1.8.0 # via selenium wsproto==1.2.0 diff --git a/requirements/test-master.txt b/requirements/test-master.txt index 2dd2f761e..ede624e45 100644 --- a/requirements/test-master.txt +++ b/requirements/test-master.txt @@ -8,11 +8,11 @@ aiohappyeyeballs==2.4.4 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # aiohttp -aiohttp==3.11.9 +aiohttp==3.11.11 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # openai -aiosignal==1.3.1 +aiosignal==1.3.2 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # aiohttp @@ -31,7 +31,7 @@ asn1crypto==1.5.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # snowflake-connector-python -attrs==24.2.0 +attrs==24.3.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # aiohttp @@ -50,7 +50,7 @@ celery==5.4.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in -certifi==2024.8.30 +certifi==2024.12.14 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # requests @@ -82,7 +82,7 @@ click-plugins==1.1.1 # celery click-repl==0.3.0 # via celery -code-annotations==2.0.0 +code-annotations==2.1.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in @@ -125,7 +125,7 @@ django==4.2.17 # edx-toggles # jsonfield # openedx-events -django-cache-memoize==0.2.0 +django-cache-memoize==0.2.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in @@ -258,7 +258,7 @@ edx-toggles==5.2.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in -fastavro==1.9.7 +fastavro==1.10.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # openedx-events @@ -281,7 +281,7 @@ inflection==0.5.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # drf-yasg -jinja2==3.1.4 +jinja2==3.1.5 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # code-annotations @@ -308,7 +308,7 @@ multidict==6.1.0 # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # aiohttp # yarl -newrelic==10.3.1 +newrelic==10.4.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # edx-django-utils @@ -349,7 +349,7 @@ pgpy==0.6.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in -pillow==11.0.0 +pillow==11.1.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/base.in @@ -364,7 +364,7 @@ propcache==0.2.1 # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # aiohttp # yarl -psutil==6.1.0 +psutil==6.1.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # edx-django-utils @@ -459,7 +459,7 @@ sortedcontainers==2.4.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # snowflake-connector-python -sqlparse==0.5.2 +sqlparse==0.5.3 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # django @@ -508,6 +508,7 @@ uritemplate==4.1.1 # drf-yasg urllib3==2.2.3 # via + # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/common_constraints.txt # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/edx-platform-constraints.txt # requests vine==5.1.0 diff --git a/requirements/test.txt b/requirements/test.txt index 624db5d7e..914aa933b 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -8,11 +8,11 @@ aiohappyeyeballs==2.4.4 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp -aiohttp==3.11.9 +aiohttp==3.11.11 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # openai -aiosignal==1.3.1 +aiosignal==1.3.2 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp @@ -32,7 +32,7 @@ asn1crypto==1.5.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # snowflake-connector-python -attrs==24.2.0 +attrs==24.3.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp @@ -50,7 +50,7 @@ bleach==6.2.0 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt -certifi==2024.8.30 +certifi==2024.12.14 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # requests @@ -86,7 +86,7 @@ click-plugins==1.1.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # celery -code-annotations==2.0.0 +code-annotations==2.1.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-toggles @@ -131,7 +131,7 @@ diff-cover==9.2.1 # edx-toggles # jsonfield # openedx-events -django-cache-memoize==0.2.0 +django-cache-memoize==0.2.1 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt django-config-models==2.7.0 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt @@ -230,9 +230,9 @@ factory-boy==3.3.1 # via # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/constraints.txt # -r requirements/test.in -faker==33.3.0 +faker==33.3.1 # via factory-boy -fastavro==1.9.7 +fastavro==1.10.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # openedx-events @@ -261,7 +261,7 @@ inflection==0.5.1 # drf-yasg iniconfig==2.0.0 # via pytest -jinja2==3.1.4 +jinja2==3.1.5 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # code-annotations @@ -290,7 +290,7 @@ multidict==6.1.0 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp # yarl -newrelic==10.3.1 +newrelic==10.4.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-django-utils @@ -322,7 +322,7 @@ pbr==6.1.0 # stevedore pgpy==0.6.0 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt -pillow==11.0.0 +pillow==11.1.0 # via -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt platformdirs==4.3.6 # via @@ -340,7 +340,7 @@ propcache==0.2.1 # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # aiohttp # yarl -psutil==6.1.0 +psutil==6.1.1 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # edx-django-utils @@ -354,7 +354,7 @@ pycparser==2.22 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # cffi -pygments==2.19.0 +pygments==2.19.1 # via diff-cover pyjwt[crypto]==2.10.1 # via @@ -449,7 +449,7 @@ sortedcontainers==2.4.0 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # snowflake-connector-python -sqlparse==0.5.2 +sqlparse==0.5.3 # via # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # django @@ -498,6 +498,7 @@ uritemplate==4.1.1 # drf-yasg urllib3==2.2.3 # via + # -c /home/runner/work/edx-enterprise/edx-enterprise/requirements/common_constraints.txt # -r /home/runner/work/edx-enterprise/edx-enterprise/requirements/test-master.txt # requests # via From 23d01228f656e775de9d436935f9e8286af4502c Mon Sep 17 00:00:00 2001 From: Katrina Nguyen <71999631+katrinan029@users.noreply.github.com> Date: Mon, 13 Jan 2025 12:10:28 -0800 Subject: [PATCH 11/12] feat: adds sorting support to enterprise-customer-members endpoint (#2311) * feat: adds sorting support to enterprise-customer-members endpoint --- CHANGELOG.rst | 4 +++ enterprise/__init__.py | 2 +- enterprise/api/v1/serializers.py | 18 ++++++++++- .../v1/views/enterprise_customer_members.py | 31 +++++++++++++++++-- tests/test_enterprise/api/test_serializers.py | 2 ++ 5 files changed, 53 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5d3576f4c..c4fe189d0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,10 @@ Unreleased ---------- * nothing unreleased +[5.6.0] +-------- +* feat: Adds sorting support to enterprise-customer-members endpoint + [5.5.2] -------- * feat: Add page_size support to enterprise-customer-members endpoint diff --git a/enterprise/__init__.py b/enterprise/__init__.py index f930f65df..8422168fd 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "5.5.2" +__version__ = "5.6.0" diff --git a/enterprise/api/v1/serializers.py b/enterprise/api/v1/serializers.py index 69488030f..d6ef22c11 100644 --- a/enterprise/api/v1/serializers.py +++ b/enterprise/api/v1/serializers.py @@ -1922,7 +1922,22 @@ def get_role_assignments(self, obj): return None -class EnterpriseMembersSerializer(serializers.Serializer): +class EnterpriseCustomerMembersRequestQuerySerializer(serializers.Serializer): + """ + Serializer for the Enterprise Customer Members endpoint query filter + """ + user_query = serializers.CharField(required=False, max_length=250) + sort_by = serializers.ChoiceField( + choices=[ + ('name', 'name'), + ('joined_org', 'joined_org'), + ], + required=False, + ) + is_reversed = serializers.BooleanField(required=False, default=False) + + +class EnterpriseMembersSerializer(serializers.ModelSerializer): """ Serializer for EnterpriseCustomerUser model with additions. """ @@ -1954,6 +1969,7 @@ def get_enterprise_customer_user(self, obj): """ if user := obj: return { + "user_id": user[0], "email": user[1], "joined_org": user[2].strftime("%b %d, %Y"), "name": user[3], diff --git a/enterprise/api/v1/views/enterprise_customer_members.py b/enterprise/api/v1/views/enterprise_customer_members.py index 680b94d5a..4a41965cc 100644 --- a/enterprise/api/v1/views/enterprise_customer_members.py +++ b/enterprise/api/v1/views/enterprise_customer_members.py @@ -6,6 +6,7 @@ from rest_framework import permissions, response, status from rest_framework.pagination import PageNumberPagination +from rest_framework.response import Response from django.core.exceptions import ValidationError from django.db import connection @@ -63,13 +64,31 @@ class EnterpriseCustomerMembersViewSet(EnterpriseReadOnlyModelViewSet): def get_members(self, request, *args, **kwargs): """ Get all members associated with that enterprise customer + + Request Arguments: + - ``enterprise_uuid`` (URL location, required): The uuid of the enterprise from which learners should be listed. + + Optional query params: + - ``user_query`` (string, optional): Filter the returned members by user name and email with a provided + sub-string + - ``sort_by`` (string, optional): Specify how the returned members should be ordered. Supported sorting values + are `joined_org`, `name`, and `enrollments`. + - ``is_reversed`` (bool, optional): Include to reverse the records in descending order. By default, the results + returned are in ascending order. """ + query_params = self.request.query_params + param_serializers = serializers.EnterpriseCustomerMembersRequestQuerySerializer( + data=query_params + ) + if not param_serializers.is_valid(): + return Response(param_serializers.errors, status=400) enterprise_uuid = kwargs.get("enterprise_uuid", None) # Raw sql is picky about uuid format uuid_no_dashes = str(enterprise_uuid).replace("-", "") users = [] - user_query = self.request.query_params.get("user_query", None) - + user_query = param_serializers.validated_data.get('user_query') + is_reversed = param_serializers.validated_data.get('is_reversed', False) + sort_by = param_serializers.validated_data.get('sort_by') # On logistration, the name field of auth_userprofile is populated, but if it's not # filled in, we check the auth_user model for it's first/last name fields # https://2u-internal.atlassian.net/wiki/spaces/ENGAGE/pages/747143186/Use+of+full+name+in+edX#Data-on-Name-Field @@ -109,6 +128,14 @@ def get_members(self, request, *args, **kwargs): status=status.HTTP_404_NOT_FOUND, ) + if sort_by: + lambda_keys = { + # 3 and 2 are indices in the tuple associated to a user row (uuid, email, joined_org, name) + 'name': lambda t: t[3], + 'joined_org': lambda t: t[2], + } + users = sorted(users, key=lambda_keys.get(sort_by), reverse=is_reversed) + # paginate the queryset users_page = self.paginator.paginate_queryset(users, request, view=self) diff --git a/tests/test_enterprise/api/test_serializers.py b/tests/test_enterprise/api/test_serializers.py index 8cba7803c..4d177fd0d 100644 --- a/tests/test_enterprise/api/test_serializers.py +++ b/tests/test_enterprise/api/test_serializers.py @@ -589,6 +589,7 @@ def setUp(self): def test_serialize_users(self): expected_user = { 'enterprise_customer_user': { + 'user_id': self.user_1.id, 'email': self.user_1.email, 'joined_org': self.user_1.date_joined.strftime("%b %d, %Y"), 'name': (self.user_1.first_name + ' ' + self.user_1.last_name), @@ -609,6 +610,7 @@ def test_serialize_users(self): expected_user_2 = { 'enterprise_customer_user': { + 'user_id': self.user_2.id, 'email': self.user_2.email, 'joined_org': self.user_2.date_joined.strftime("%b %d, %Y"), 'name': self.user_2.first_name + ' ' + self.user_2.last_name, From 9779f3e96e91f66324de6bcddd55858b3f954947 Mon Sep 17 00:00:00 2001 From: Zaman Afzal Date: Tue, 14 Jan 2025 16:47:55 +0500 Subject: [PATCH 12/12] fix: Log all learner transmissions (#2313) --- CHANGELOG.rst | 4 ++++ enterprise/__init__.py | 2 +- integrated_channels/moodle/client.py | 26 +++++++++++++------------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c4fe189d0..c0701ae01 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,10 @@ Unreleased ---------- * nothing unreleased +[5.6.1] +-------- +* fix: Log all learner transmission records. + [5.6.0] -------- * feat: Adds sorting support to enterprise-customer-members endpoint diff --git a/enterprise/__init__.py b/enterprise/__init__.py index 8422168fd..701028e69 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "5.6.0" +__version__ = "5.6.1" diff --git a/integrated_channels/moodle/client.py b/integrated_channels/moodle/client.py index cd883e12a..25b6cf544 100644 --- a/integrated_channels/moodle/client.py +++ b/integrated_channels/moodle/client.py @@ -418,19 +418,19 @@ def _wrapped_create_course_completion(self, user_id, payload): headers = response.headers else: headers = None - if not status_code or not text or not headers: - LOGGER.info( - 'Learner Data Transmission' - f'for course={completion_data["courseID"]} with data ' - f'source: {module_name}, ' - f'activityid: {course_module_id}, ' - f'grades[0][studentid]: {moodle_user_id}, ' - f'grades[0][grade]: {completion_data["grade"] * self.enterprise_configuration.grade_scale} ' - f' with response: {response} ' - f'Status Code: {status_code}, ' - f'Text: {text}, ' - f'Headers: {headers}, ' - ) + + LOGGER.info( + 'Learner Data Transmission' + f'for course={completion_data["courseID"]} with data ' + f'source: {module_name}, ' + f'activityid: {course_module_id}, ' + f'grades[0][studentid]: {moodle_user_id}, ' + f'grades[0][grade]: {completion_data["grade"] * self.enterprise_configuration.grade_scale} ' + f' with response: {response} ' + f'Status Code: {status_code}, ' + f'Text: {text}, ' + f'Headers: {headers}, ' + ) return response