diff --git a/.github/workflows/migrations-check.yml b/.github/workflows/migrations-check.yml
index bd0a07680ad7..fc4a48876a93 100644
--- a/.github/workflows/migrations-check.yml
+++ b/.github/workflows/migrations-check.yml
@@ -61,10 +61,6 @@ jobs:
- name: Install Python dependencies
run: |
make dev-requirements
- pip uninstall -y mysqlclient
- pip install --no-binary mysqlclient mysqlclient
- pip uninstall -y xmlsec
- pip install --no-binary xmlsec xmlsec
- name: Initiate Services
run: |
@@ -78,6 +74,11 @@ jobs:
UPDATE mysql.user SET authentication_string = null WHERE user = 'root';
FLUSH PRIVILEGES;
EOF
+
+ - name: Install mysqlclient-dev binary
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y libmysqlclient-dev
- name: Run Tests
env:
diff --git a/.github/workflows/unit-tests-gh-hosted.yml b/.github/workflows/unit-tests-gh-hosted.yml
index 894f61048e90..80a6a4e53a08 100644
--- a/.github/workflows/unit-tests-gh-hosted.yml
+++ b/.github/workflows/unit-tests-gh-hosted.yml
@@ -9,7 +9,7 @@ on:
jobs:
run-test:
- if: github.repository != 'openedx/edx-platform' && github.repository != 'edx/edx-platform-private'
+ if: (github.repository != 'openedx/edx-platform' && github.repository != 'edx/edx-platform-private') || (github.repository == 'openedx/edx-platform' && (startsWith(github.base_ref, 'open-release') == true))
runs-on: ubuntu-20.04
strategy:
fail-fast: false
@@ -17,23 +17,24 @@ jobs:
python-version: [ '3.8' ]
django-version:
- "pinned"
- shard_name: [
- "lms-1",
- "lms-2",
- "lms-3",
- "lms-4",
- "lms-5",
- "lms-6",
- "openedx-1",
- "openedx-2",
- "openedx-3",
- "openedx-4",
- "cms-1",
- "cms-2",
- "common-1",
- "common-2",
- "common-3",
- ]
+ # When updating the shards, remember to make the same changes in
+ # .github/workflows/unit-tests.yml
+ shard_name:
+ - "lms-1"
+ - "lms-2"
+ - "lms-3"
+ - "lms-4"
+ - "lms-5"
+ - "lms-6"
+ - "openedx-1"
+ - "openedx-2"
+ - "openedx-3"
+ - "openedx-4"
+ - "cms-1"
+ - "cms-2"
+ - "common-1"
+ - "common-2"
+ - "xmodule-1"
name: gh-hosted-python-${{ matrix.python-version }},django-${{ matrix.django-version }},${{ matrix.shard_name }}
steps:
- uses: actions/checkout@v2
@@ -78,7 +79,7 @@ jobs:
uses: ./.github/actions/unit-tests
collect-and-verify:
- if: github.repository != 'openedx/edx-platform' && github.repository != 'edx/edx-platform-private'
+ if: (github.repository != 'openedx/edx-platform' && github.repository != 'edx/edx-platform-private') || (github.repository == 'openedx/edx-platform' && (startsWith(github.base_ref, 'open-release') == true))
runs-on: ubuntu-20.04
strategy:
matrix:
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index 5b4b8ed94355..c66c30d7be6f 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -9,7 +9,7 @@ on:
jobs:
run-tests:
name: python-${{ matrix.python-version }},django-${{ matrix.django-version }},${{ matrix.shard_name }}
- if: github.repository == 'edx/edx-platform-private' || github.repository == 'openedx/edx-platform'
+ if: (github.repository == 'edx/edx-platform-private') || (github.repository == 'openedx/edx-platform' && (startsWith(github.base_ref, 'open-release') == false))
runs-on: [ edx-platform-runner ]
strategy:
matrix:
@@ -18,6 +18,8 @@ jobs:
django-version:
- "pinned"
#- "4.0"
+ # When updating the shards, remember to make the same changes in
+ # .github/workflows/unit-tests-gh-hosted.yml
shard_name:
- "lms-1"
- "lms-2"
@@ -80,7 +82,7 @@ jobs:
# https://github.com/orgs/community/discussions/33579
success:
name: Tests successful
- if: always()
+ if: (github.repository == 'edx/edx-platform-private') || (github.repository == 'openedx/edx-platform' && (startsWith(github.base_ref, 'open-release') == false))
needs:
- run-tests
runs-on: ubuntu-latest
diff --git a/.github/workflows/verify-gha-unit-tests-count.yml b/.github/workflows/verify-gha-unit-tests-count.yml
index a519c62fa0a0..c68092942d70 100644
--- a/.github/workflows/verify-gha-unit-tests-count.yml
+++ b/.github/workflows/verify-gha-unit-tests-count.yml
@@ -8,7 +8,7 @@ on:
jobs:
collect-and-verify:
- if: github.repository == 'edx/edx-platform-private' || github.repository == 'openedx/edx-platform'
+ if: (github.repository == 'edx/edx-platform-private') || (github.repository == 'openedx/edx-platform' && (startsWith(github.base_ref, 'open-release') == false))
runs-on: [ edx-platform-runner ]
steps:
- name: sync directory owner
diff --git a/Makefile b/Makefile
index 7aa7334fdfac..fec1c2caf2e7 100644
--- a/Makefile
+++ b/Makefile
@@ -64,8 +64,8 @@ pull: ## update the Docker image used by "make shell"
docker pull edxops/edxapp:latest
pre-requirements: ## install Python requirements for running pip-tools
- pip install -qr requirements/pip.txt
- pip install -qr requirements/edx/pip-tools.txt
+ pip install -r requirements/pip.txt
+ pip install -r requirements/pip-tools.txt
local-requirements:
# edx-platform installs some Python projects from within the edx-platform repo itself.
@@ -74,7 +74,7 @@ local-requirements:
dev-requirements: pre-requirements
@# The "$(wildcard..)" is to include private.txt if it exists, and make no mention
@# of it if it does not. Shell wildcarding can't do that with default options.
- pip-sync -q requirements/edx/development.txt $(wildcard requirements/edx/private.txt)
+ pip-sync requirements/edx/development.txt $(wildcard requirements/edx/private.txt)
make local-requirements
base-requirements: pre-requirements
@@ -96,7 +96,6 @@ shell: ## launch a bash shell in a Docker container with all edx-platform depend
# Order is very important in this list: files must appear after everything they include!
REQ_FILES = \
- requirements/edx/pip-tools \
requirements/edx/coverage \
requirements/edx/doc \
requirements/edx/paver \
@@ -117,23 +116,26 @@ $(COMMON_CONSTRAINTS_TXT):
echo "$(COMMON_CONSTRAINTS_TEMP_COMMENT)" | cat - $(@) > temp && mv temp $(@)
compile-requirements: export CUSTOM_COMPILE_COMMAND=make upgrade
-compile-requirements: $(COMMON_CONSTRAINTS_TXT) ## Re-compile *.in requirements to *.txt
- pip install -q pip-tools
- pip-compile --allow-unsafe --upgrade -o requirements/edx/pip.txt requirements/edx/pip.in
+compile-requirements: pre-requirements $(COMMON_CONSTRAINTS_TXT) ## Re-compile *.in requirements to *.txt
+ @# Bootstrapping: Rebuild pip and pip-tools first, and then install them
+ @# so that if there are any failures we'll know now, rather than the next
+ @# time someone tries to use the outputs.
+ pip-compile -v --allow-unsafe ${COMPILE_OPTS} -o requirements/pip.txt requirements/pip.in
+ pip install -r requirements/pip.txt
+
+ pip-compile -v ${COMPILE_OPTS} -o requirements/pip-tools.txt requirements/pip-tools.in
+ pip install -r requirements/pip-tools.txt
@ export REBUILD='--rebuild'; \
for f in $(REQ_FILES); do \
echo ; \
echo "== $$f ===============================" ; \
- echo "pip-compile -v --no-emit-trusted-host --no-emit-index-url $$REBUILD ${COMPILE_OPTS} -o $$f.txt $$f.in"; \
- pip-compile -v --no-emit-trusted-host --no-emit-index-url $$REBUILD ${COMPILE_OPTS} -o $$f.txt $$f.in || exit 1; \
+ echo "pip-compile -v $$REBUILD ${COMPILE_OPTS} -o $$f.txt $$f.in"; \
+ pip-compile -v $$REBUILD ${COMPILE_OPTS} -o $$f.txt $$f.in || exit 1; \
export REBUILD=''; \
done
- pip install -qr requirements/edx/pip.txt
- pip install -qr requirements/edx/pip-tools.txt
-
-upgrade: pre-requirements ## update the pip requirements files to use the latest releases satisfying our constraints
+upgrade: ## update the pip requirements files to use the latest releases satisfying our constraints
$(MAKE) compile-requirements COMPILE_OPTS="--upgrade"
check-types: ## run static type-checking tests
diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py
index a135faf06712..be7691ee7e45 100644
--- a/cms/djangoapps/contentstore/tests/test_course_settings.py
+++ b/cms/djangoapps/contentstore/tests/test_course_settings.py
@@ -106,7 +106,12 @@ def setUp(self):
super().setUp()
self.fullcourse = CourseFactory.create()
self.course_setting_url = get_url(self.course.id, 'advanced_settings_handler')
- self.non_staff_client, _ = self.create_non_staff_authed_user_client()
+
+ self.non_staff_client, self.nonstaff = self.create_non_staff_authed_user_client()
+ # "nonstaff" means "non Django staff" here. We assign this user to course staff
+ # role to check that even so they won't have advanced settings access when explicitly
+ # restricted.
+ CourseStaffRole(self.course.id).add_users(self.nonstaff)
@override_settings(FEATURES={'DISABLE_MOBILE_COURSE_AVAILABLE': True})
def test_mobile_field_available(self):
@@ -145,16 +150,50 @@ def test_discussion_fields_available(self, is_pages_and_resources_enabled,
self.assertEqual('discussion_blackouts' in response, fields_visible)
self.assertEqual('discussion_topics' in response, fields_visible)
- @override_settings(FEATURES={'DISABLE_ADVANCED_SETTINGS': True})
- def test_disable_advanced_settings_feature(self):
- """
- If this feature is enabled, only staff should be able to access the advanced settings page.
- """
- response = self.non_staff_client.get_html(self.course_setting_url)
- self.assertEqual(response.status_code, 403)
+ @ddt.data(False, True)
+ def test_disable_advanced_settings_feature(self, disable_advanced_settings):
+ """
+ If this feature is enabled, only Django Staff/Superuser should be able to access the "Advanced Settings" page.
+ For non-staff users the "Advanced Settings" tab link should not be visible.
+ """
+ advanced_settings_link_html = f"Advanced Settings".encode('utf-8')
+
+ with override_settings(FEATURES={'DISABLE_ADVANCED_SETTINGS': disable_advanced_settings}):
+ for handler in (
+ 'import_handler',
+ 'export_handler',
+ 'course_team_handler',
+ 'course_info_handler',
+ 'assets_handler',
+ 'tabs_handler',
+ 'settings_handler',
+ 'grading_handler',
+ 'textbooks_list_handler',
+ ):
+ # Test that non-staff users don't see the "Advanced Settings" tab link.
+ response = self.non_staff_client.get_html(
+ get_url(self.course.id, handler)
+ )
+ self.assertEqual(response.status_code, 200)
+ if disable_advanced_settings:
+ self.assertNotIn(advanced_settings_link_html, response.content)
+ else:
+ self.assertIn(advanced_settings_link_html, response.content)
+
+ # Test that staff users see the "Advanced Settings" tab link.
+ response = self.client.get_html(
+ get_url(self.course.id, handler)
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertIn(advanced_settings_link_html, response.content)
- response = self.client.get_html(self.course_setting_url)
- self.assertEqual(response.status_code, 200)
+ # Test that non-staff users can't access the "Advanced Settings" page.
+ response = self.non_staff_client.get_html(self.course_setting_url)
+ self.assertEqual(response.status_code, 403 if disable_advanced_settings else 200)
+
+ # Test that staff users can access the "Advanced Settings" page.
+ response = self.client.get_html(self.course_setting_url)
+ self.assertEqual(response.status_code, 200)
@ddt.ddt
diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py
index 457f84793efc..b7dd09d5c7cb 100644
--- a/cms/djangoapps/contentstore/views/component.py
+++ b/cms/djangoapps/contentstore/views/component.py
@@ -292,8 +292,8 @@ def create_support_legend_dict():
# by the components in the order listed in COMPONENT_TYPES.
component_types = COMPONENT_TYPES[:]
- # Libraries do not support discussions and openassessment and other libraries
- component_not_supported_by_library = ['discussion', 'library', 'openassessment']
+ # Libraries do not support discussions, drag-and-drop, and openassessment and other libraries
+ component_not_supported_by_library = ['discussion', 'library', 'openassessment', 'drag-and-drop-v2']
if library:
component_types = [component for component in component_types
if component not in set(component_not_supported_by_library)]
diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py
index 3bf9ade016e4..023cb07199fe 100644
--- a/cms/djangoapps/contentstore/views/course.py
+++ b/cms/djangoapps/contentstore/views/course.py
@@ -43,7 +43,12 @@
from common.djangoapps.course_action_state.models import CourseRerunState, CourseRerunUIStateManager
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.edxmako.shortcuts import render_to_response
-from common.djangoapps.student.auth import has_course_author_access, has_studio_read_access, has_studio_write_access
+from common.djangoapps.student.auth import (
+ has_course_author_access,
+ has_studio_read_access,
+ has_studio_write_access,
+ has_studio_advanced_settings_access
+)
from common.djangoapps.student.roles import (
CourseInstructorRole,
CourseStaffRole,
@@ -147,17 +152,6 @@ class AccessListFallback(Exception):
pass # lint-amnesty, pylint: disable=unnecessary-pass
-def has_advanced_settings_access(user):
- """
- If DISABLE_ADVANCED_SETTINGS feature is enabled, only global staff can access "Advanced Settings".
- """
- return (
- not settings.FEATURES.get('DISABLE_ADVANCED_SETTINGS', False)
- or user.is_staff
- or user.is_superuser
- )
-
-
def get_course_and_check_access(course_key, user, depth=0):
"""
Function used to calculate and return the locator and course block
@@ -763,7 +757,6 @@ def course_index(request, course_key):
'frontend_app_publisher_url': frontend_app_publisher_url,
'mfe_proctored_exam_settings_url': get_proctored_exam_settings_url(course_block.id),
'advance_settings_url': reverse_course_url('advanced_settings_handler', course_block.id),
- 'advance_settings_access': has_advanced_settings_access(request.user),
'proctoring_errors': proctoring_errors,
})
@@ -1432,7 +1425,7 @@ def advanced_settings_handler(request, course_key_string):
json: update the Course's settings. The payload is a json rep of the
metadata dicts.
"""
- if not has_advanced_settings_access(request.user):
+ if not has_studio_advanced_settings_access(request.user):
raise PermissionDenied()
course_key = CourseKey.from_string(course_key_string)
diff --git a/cms/templates/settings.html b/cms/templates/settings.html
index c3ed1afb9957..b7ef368a0911 100644
--- a/cms/templates/settings.html
+++ b/cms/templates/settings.html
@@ -7,6 +7,7 @@
<%namespace name='static' file='static_content.html'/>
<%!
from django.utils.translation import gettext as _
+ from common.djangoapps.student.auth import has_studio_advanced_settings_access
from cms.djangoapps.contentstore import utils
from lms.djangoapps.certificates.api import can_show_certificate_available_date_field
from openedx.core.djangolib.js_utils import (
@@ -721,7 +722,9 @@
${_("Other Course Settings")}
${_("Grading")}
${_("Course Team")}
${_("Group Configurations")}
+ % if has_studio_advanced_settings_access(request.user):
${_("Advanced Settings")}
+ % endif
% if mfe_proctored_exam_settings_url:
${_("Proctored Exam Settings")}
% endif
diff --git a/cms/templates/settings_graders.html b/cms/templates/settings_graders.html
index bde135122155..ac7119177978 100644
--- a/cms/templates/settings_graders.html
+++ b/cms/templates/settings_graders.html
@@ -11,6 +11,7 @@
import json
from cms.djangoapps.contentstore import utils
from django.utils.translation import gettext as _
+ from common.djangoapps.student.auth import has_studio_advanced_settings_access
from cms.djangoapps.models.settings.encoder import CourseSettingsEncoder
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
@@ -165,7 +166,9 @@ ${_("Other Course Settings")}
${_("Details & Schedule")}
${_("Course Team")}
${_("Group Configurations")}
+ % if has_studio_advanced_settings_access(request.user):
${_("Advanced Settings")}
+ % endif
% if mfe_proctored_exam_settings_url:
${_("Proctored Exam Settings")}
% endif
diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html
index 23678ec395ae..48b818da95a5 100644
--- a/cms/templates/widgets/header.html
+++ b/cms/templates/widgets/header.html
@@ -7,6 +7,7 @@
from django.urls import reverse
from django.utils.translation import gettext as _
from urllib.parse import quote_plus
+ from common.djangoapps.student.auth import has_studio_advanced_settings_access
from cms.djangoapps.contentstore import toggles
from cms.djangoapps.contentstore.utils import get_pages_and_resources_url
from openedx.core.djangoapps.discussions.config.waffle import ENABLE_PAGES_AND_RESOURCES_MICROFRONTEND
@@ -120,7 +121,7 @@ ${_("Course"
${_("Proctored Exam Settings")}
% endif
- % if advance_settings_access:
+ % if has_studio_advanced_settings_access(request.user):
${_("Advanced Settings")}
diff --git a/common/djangoapps/student/auth.py b/common/djangoapps/student/auth.py
index 3091dbc45406..f8e45ce6ba6c 100644
--- a/common/djangoapps/student/auth.py
+++ b/common/djangoapps/student/auth.py
@@ -125,9 +125,23 @@ def has_course_author_access(user, course_key):
return has_studio_write_access(user, course_key)
+def has_studio_advanced_settings_access(user):
+ """
+ If DISABLE_ADVANCED_SETTINGS feature is enabled, only Django Superuser
+ or Django Staff can access "Advanced Settings".
+
+ By default, this feature is disabled.
+ """
+ return (
+ not settings.FEATURES.get('DISABLE_ADVANCED_SETTINGS', False)
+ or user.is_staff
+ or user.is_superuser
+ )
+
+
def has_studio_read_access(user, course_key):
"""
- Return True iff user is allowed to view this course/library in studio.
+ Return True if user is allowed to view this course/library in studio.
Will also return True if user has write access in studio (has_course_author_access)
There is currently no such thing as read-only course access in studio, but
diff --git a/openedx/core/djangoapps/cache_toolbox/middleware.py b/openedx/core/djangoapps/cache_toolbox/middleware.py
index 6284613d49e6..4ba2162fe35f 100644
--- a/openedx/core/djangoapps/cache_toolbox/middleware.py
+++ b/openedx/core/djangoapps/cache_toolbox/middleware.py
@@ -52,6 +52,14 @@
compounded by most projects wishing to avoid expiring session data as long
as possible (in addition to storing sessions in persistent stores).
+Dependency with SafeSessionMiddleware
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+CacheBackedAuthenticationMiddleware middleware logs out the user if the
+session hash is changed due to password change. It flushes the session
+and mark cookies for deletion in request which are then deleted in the
+process_response of SafeSessionMiddleware.
+
Usage
~~~~~
@@ -88,7 +96,7 @@
from django.utils.crypto import constant_time_compare
from django.utils.deprecation import MiddlewareMixin
-from openedx.core.djangoapps.safe_sessions.middleware import SafeSessionMiddleware
+from openedx.core.djangoapps.safe_sessions.middleware import SafeSessionMiddleware, _mark_cookie_for_deletion
from .model import cache_model
@@ -138,3 +146,4 @@ def _verify_session_auth(self, request):
# change. Log the user out.
request.session.flush()
request.user = AnonymousUser()
+ _mark_cookie_for_deletion(request)
diff --git a/openedx/core/djangoapps/cache_toolbox/tests/test_middleware.py b/openedx/core/djangoapps/cache_toolbox/tests/test_middleware.py
index 68f0577968e2..21547b69ca1c 100644
--- a/openedx/core/djangoapps/cache_toolbox/tests/test_middleware.py
+++ b/openedx/core/djangoapps/cache_toolbox/tests/test_middleware.py
@@ -2,11 +2,15 @@
from unittest.mock import patch
from django.conf import settings
-from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
+from django.contrib.auth.models import User, AnonymousUser # lint-amnesty, pylint: disable=imported-auth-user
from django.urls import reverse
from django.test import TestCase
+from django.contrib.auth import SESSION_KEY
+from django.http import HttpResponse, SimpleCookie
-from openedx.core.djangolib.testing.utils import skip_unless_cms, skip_unless_lms
+from openedx.core.djangoapps.cache_toolbox.middleware import CacheBackedAuthenticationMiddleware
+from openedx.core.djangoapps.safe_sessions.middleware import SafeCookieData, SafeSessionMiddleware
+from openedx.core.djangolib.testing.utils import skip_unless_cms, skip_unless_lms, get_mock_request
from common.djangoapps.student.tests.factories import UserFactory
@@ -18,6 +22,9 @@ def setUp(self):
password = 'test-password'
self.user = UserFactory(password=password)
self.client.login(username=self.user.username, password=password)
+ self.request = get_mock_request(self.user)
+ self.client.response = HttpResponse()
+ self.client.response.cookies = SimpleCookie() # preparing cookies
def _test_change_session_hash(self, test_url, redirect_url, target_status_code=200):
"""
@@ -45,3 +52,36 @@ def test_session_change_cms(self):
home_url = reverse('home')
# Studio login redirects to LMS login
self._test_change_session_hash(home_url, settings.LOGIN_URL + '?next=' + home_url, target_status_code=302)
+
+ def test_user_logout_on_session_hash_change(self):
+ """
+ Verify that if a user's session auth hash and the request's hash
+ differ, the user is logged out:
+ - session is flushed
+ - request user is changed to Anonymous user
+ - logged in cookies are deleted
+ """
+ # preparing session and setting cookies
+ session_id = self.client.session.session_key
+ safe_cookie_data = SafeCookieData.create(session_id, self.user.id)
+ self.request.COOKIES[settings.SESSION_COOKIE_NAME] = str(safe_cookie_data)
+ self.client.response.cookies[settings.SESSION_COOKIE_NAME] = session_id
+ self.client.response.cookies['edx-jwt-cookie-header-payload'] = 'test-jwt-payload'
+ SafeSessionMiddleware().process_request(self.request)
+
+ # asserts that user, session, and JWT cookies exist
+ assert self.request.session.get(SESSION_KEY) is not None
+ assert self.request.user != AnonymousUser()
+ assert self.client.response.cookies.get(settings.SESSION_COOKIE_NAME).value == session_id
+ assert self.client.response.cookies.get('edx-jwt-cookie-header-payload').value == 'test-jwt-payload'
+
+ with patch.object(User, 'get_session_auth_hash', return_value='abc123'):
+ CacheBackedAuthenticationMiddleware().process_request(self.request)
+ SafeSessionMiddleware().process_response(self.request, self.client.response)
+
+ # asserts that user, session, and JWT cookies do not exist
+ assert self.request.session.get(SESSION_KEY) is None
+ assert self.request.user == AnonymousUser()
+ assert self.client.response.cookies.get(settings.SESSION_COOKIE_NAME).value != session_id
+ assert self.client.response.cookies.get(settings.SESSION_COOKIE_NAME).value == ""
+ assert self.client.response.cookies.get('edx-jwt-cookie-header-payload').value == ""
diff --git a/openedx/core/djangoapps/catalog/management/commands/cache_programs.py b/openedx/core/djangoapps/catalog/management/commands/cache_programs.py
index b815f8b4e4e5..9e0664f4930f 100644
--- a/openedx/core/djangoapps/catalog/management/commands/cache_programs.py
+++ b/openedx/core/djangoapps/catalog/management/commands/cache_programs.py
@@ -45,8 +45,17 @@ class Command(BaseCommand):
"""
help = "Rebuild the LMS' cache of program data."
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '--domain',
+ dest='domain',
+ type=str,
+ help='Help in caching the programs for one site'
+ )
+
# lint-amnesty, pylint: disable=bad-option-value, unicode-format-string
def handle(self, *args, **options): # lint-amnesty, pylint: disable=too-many-statements
+ domain = options.get('domain', '')
failure = False
logger.info('populate-multitenant-programs switch is ON')
@@ -68,7 +77,9 @@ def handle(self, *args, **options): # lint-amnesty, pylint: disable=too-many-st
programs_by_type = {}
programs_by_type_slug = {}
organizations = {}
- for site in Site.objects.all():
+
+ sites = Site.objects.filter(domain=domain) if domain else Site.objects.all()
+ for site in sites:
site_config = getattr(site, 'configuration', None)
if site_config is None or not site_config.get_value('COURSE_CATALOG_API_URL'):
logger.info(f'Skipping site {site.domain}. No configuration.')
diff --git a/openedx/core/djangoapps/catalog/management/commands/tests/test_cache_programs.py b/openedx/core/djangoapps/catalog/management/commands/tests/test_cache_programs.py
index 331d53a73042..4078b2d90d55 100644
--- a/openedx/core/djangoapps/catalog/management/commands/tests/test_cache_programs.py
+++ b/openedx/core/djangoapps/catalog/management/commands/tests/test_cache_programs.py
@@ -48,29 +48,53 @@ def setUp(self):
}
)
+ self.site_domain2 = 'testsite2.com'
+ self.site2 = self.set_up_site(
+ self.site_domain2,
+ {
+ 'COURSE_CATALOG_API_URL': self.catalog_integration.get_internal_api_url().rstrip('/')
+ }
+ )
+
self.list_url = self.catalog_integration.get_internal_api_url().rstrip('/') + '/programs/'
self.detail_tpl = self.list_url.rstrip('/') + '/{uuid}/'
self.pathway_url = self.catalog_integration.get_internal_api_url().rstrip('/') + '/pathways/'
self.programs = ProgramFactory.create_batch(3)
+ self.programs2 = ProgramFactory.create_batch(3)
self.pathways = PathwayFactory.create_batch(3)
+ self.pathways2 = PathwayFactory.create_batch(3)
self.child_program = ProgramFactory.create()
+ self.child_program2 = ProgramFactory.create()
self.programs[0]['curricula'][0]['programs'].append(self.child_program)
self.programs.append(self.child_program)
-
self.programs[0]['authoring_organizations'] = OrganizationFactory.create_batch(2)
+ self.programs2[0]['curricula'][0]['programs'].append(self.child_program2)
+ self.programs2.append(self.child_program2)
+ self.programs2[0]['authoring_organizations'] = OrganizationFactory.create_batch(2)
+
for pathway in self.pathways:
self.programs += pathway['programs']
- self.uuids = [program['uuid'] for program in self.programs]
+ for pathway in self.pathways2:
+ self.programs2 += pathway['programs']
+
+ self.uuids = {
+ f"{self.site_domain}": [program["uuid"] for program in self.programs],
+ f"{self.site_domain2}": [program["uuid"] for program in self.programs2],
+ }
# add some of the previously created programs to some pathways
self.pathways[0]['programs'].extend([self.programs[0], self.programs[1]])
self.pathways[1]['programs'].append(self.programs[0])
- def mock_list(self):
+ # add some of the previously created programs to some pathways
+ self.pathways2[0]['programs'].extend([self.programs2[0], self.programs2[1]])
+ self.pathways2[1]['programs'].append(self.programs2[0])
+
+ def mock_list(self, site=""):
""" Mock the data returned by the program listing API endpoint. """
# pylint: disable=unused-argument
def list_callback(request, uri, headers):
@@ -81,8 +105,8 @@ def list_callback(request, uri, headers):
'uuids_only': ['1']
}
assert request.querystring == expected
-
- return (200, headers, json.dumps(self.uuids))
+ uuids = self.uuids[self.site_domain2] if site else self.uuids[self.site_domain]
+ return (200, headers, json.dumps(uuids))
httpretty.register_uri(
httpretty.GET,
@@ -130,17 +154,36 @@ def pathways_callback(request, uri, headers): # pylint: disable=unused-argument
return (200, headers, json.dumps(body))
- # NOTE: httpretty does not properly match against query strings here (using match_querystring arg)
- # as such, it does not actually look at the query parameters (for page num), but returns items in a LIFO order.
- # this means that for multiple pages, you must call this function starting from the last page.
- # we do assert the page number query param above, however
httpretty.register_uri(
httpretty.GET,
- self.pathway_url,
+ self.pathway_url + f'?exclude_utm=1&page={page_number}',
body=pathways_callback,
content_type='application/json',
+ match_querystring=True,
)
+ def test_handle_domain(self):
+ """
+ Verify that the command argument is working correct or not.
+ """
+ UserFactory(username=self.catalog_integration.service_username)
+
+ programs = {
+ PROGRAM_CACHE_KEY_TPL.format(uuid=program['uuid']): program for program in self.programs2
+ }
+
+ self.mock_list(self.site2)
+ self.mock_pathways(self.pathways2)
+
+ for uuid in self.uuids[self.site_domain2]:
+ program = programs[PROGRAM_CACHE_KEY_TPL.format(uuid=uuid)]
+ self.mock_detail(uuid, program)
+
+ call_command('cache_programs', f'--domain={self.site_domain2}')
+
+ cached_uuids = cache.get(SITE_PROGRAM_UUIDS_CACHE_KEY_TPL.format(domain=self.site_domain2))
+ assert set(cached_uuids) == set(self.uuids[self.site_domain2])
+
def test_handle_programs(self):
"""
Verify that the command requests and caches program UUIDs and details.
@@ -158,14 +201,14 @@ def test_handle_programs(self):
self.mock_list()
self.mock_pathways(self.pathways)
- for uuid in self.uuids:
+ for uuid in self.uuids[self.site_domain]:
program = programs[PROGRAM_CACHE_KEY_TPL.format(uuid=uuid)]
self.mock_detail(uuid, program)
call_command('cache_programs')
cached_uuids = cache.get(SITE_PROGRAM_UUIDS_CACHE_KEY_TPL.format(domain=self.site_domain))
- assert set(cached_uuids) == set(self.uuids)
+ assert set(cached_uuids) == set(self.uuids[self.site_domain])
program_keys = list(programs.keys())
cached_programs = cache.get_many(program_keys)
@@ -228,7 +271,7 @@ def test_handle_pathways(self):
self.mock_list()
self.mock_pathways(self.pathways)
- for uuid in self.uuids:
+ for uuid in self.uuids[self.site_domain]:
program = programs[PROGRAM_CACHE_KEY_TPL.format(uuid=uuid)]
self.mock_detail(uuid, program)
@@ -267,7 +310,7 @@ def test_pathways_multiple_pages(self):
}
self.mock_list()
- for uuid in self.uuids:
+ for uuid in self.uuids[self.site_domain]:
program = programs[PROGRAM_CACHE_KEY_TPL.format(uuid=uuid)]
self.mock_detail(uuid, program)
@@ -337,7 +380,7 @@ def test_handle_missing_pathways(self):
self.mock_list()
- for uuid in self.uuids:
+ for uuid in self.uuids[self.site_domain]:
program = programs[PROGRAM_CACHE_KEY_TPL.format(uuid=uuid)]
self.mock_detail(uuid, program)
@@ -365,7 +408,7 @@ def test_handle_missing_programs(self):
self.mock_list()
- for uuid in self.uuids[:2]:
+ for uuid in self.uuids[self.site_domain][:2]:
program = partial_programs[PROGRAM_CACHE_KEY_TPL.format(uuid=uuid)]
self.mock_detail(uuid, program)
@@ -375,7 +418,7 @@ def test_handle_missing_programs(self):
assert context.value.code == 1
cached_uuids = cache.get(SITE_PROGRAM_UUIDS_CACHE_KEY_TPL.format(domain=self.site_domain))
- assert set(cached_uuids) == set(self.uuids)
+ assert set(cached_uuids) == set(self.uuids[self.site_domain])
program_keys = list(all_programs.keys())
cached_programs = cache.get_many(program_keys)
diff --git a/openedx/core/djangoapps/schedules/resolvers.py b/openedx/core/djangoapps/schedules/resolvers.py
index 1b406a6d7150..3498042415ff 100644
--- a/openedx/core/djangoapps/schedules/resolvers.py
+++ b/openedx/core/djangoapps/schedules/resolvers.py
@@ -3,6 +3,7 @@
import datetime
import logging
from itertools import groupby
+from urllib.parse import urljoin
import attr
from django.conf import settings
@@ -322,7 +323,7 @@ def get_template_context(self, user, user_schedules):
context = {
'course_links': course_links,
'first_course_name': first_schedule.enrollment.course.display_name,
- 'cert_image': static('course_experience/images/verified-cert.png'),
+ 'cert_image': urljoin(settings.LMS_ROOT_URL, static('course_experience/images/verified-cert.png')),
'course_ids': course_id_strs,
}
context.update(first_valid_upsell_context)
diff --git a/requirements/constraints.txt b/requirements/constraints.txt
index fd79c09c815d..61324782cc06 100644
--- a/requirements/constraints.txt
+++ b/requirements/constraints.txt
@@ -90,3 +90,7 @@ babel==2.11.0
social-auth-app-django==5.0.0
algoliasearch==2.6.3
django-ipware==4.0.2
+
+# pytz>2022 has major changes which are causing test failures.
+# Pinning this version for now so this could be fixed in a separate PR later on
+pytz<2023
diff --git a/requirements/edx-sandbox/py38.txt b/requirements/edx-sandbox/py38.txt
index 08f881603dad..3935028963eb 100644
--- a/requirements/edx-sandbox/py38.txt
+++ b/requirements/edx-sandbox/py38.txt
@@ -38,7 +38,7 @@ matplotlib==3.3.4
# -r requirements/edx-sandbox/py38.in
mpmath==1.3.0
# via sympy
-networkx==3.0
+networkx==3.1
# via -r requirements/edx-sandbox/py38.in
nltk==3.8.1
# via
@@ -52,7 +52,7 @@ numpy==1.22.4
# scipy
openedx-calc==3.0.1
# via -r requirements/edx-sandbox/py38.in
-pillow==9.4.0
+pillow==9.5.0
# via matplotlib
pycparser==2.21
# via cffi
@@ -66,7 +66,7 @@ python-dateutil==2.8.2
# via matplotlib
random2==1.0.1
# via -r requirements/edx-sandbox/py38.in
-regex==2022.10.31
+regex==2023.3.23
# via nltk
scipy==1.7.3
# via
diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt
index c29df9ff37d2..a3bbb45cf8ed 100644
--- a/requirements/edx/base.txt
+++ b/requirements/edx/base.txt
@@ -16,7 +16,7 @@ algoliasearch==2.6.3
# -r requirements/edx/base.in
amqp==5.1.1
# via kombu
-analytics-python==1.4.0
+analytics-python==1.4.post1
# via -r requirements/edx/base.in
aniso8601==9.0.1
# via edx-tincan-py35
@@ -34,7 +34,7 @@ async-timeout==4.0.2
# via
# aiohttp
# redis
-attrs==22.2.0
+attrs==23.1.0
# via
# -r requirements/edx/base.in
# aiohttp
@@ -53,7 +53,7 @@ backoff==1.10.0
# via analytics-python
backports-zoneinfo==0.2.1
# via icalendar
-beautifulsoup4==4.11.2
+beautifulsoup4==4.12.2
# via pynliner
billiard==3.6.4.0
# via celery
@@ -177,7 +177,7 @@ defusedxml==0.7.1
# social-auth-core
deprecated==1.2.13
# via jwcrypto
-django==3.2.18
+django==3.2.19
# via
# -c requirements/edx/../common_constraints.txt
# -r requirements/edx/base.in
@@ -261,7 +261,7 @@ django-config-models==2.3.0
# edx-enterprise
# edx-name-affirmation
# lti-consumer-xblock
-django-cors-headers==3.13.0
+django-cors-headers==3.14.0
# via -r requirements/edx/base.in
django-countries==7.5.1
# via
@@ -282,7 +282,7 @@ django-fernet-fields==0.6
# via
# -r requirements/edx/base.in
# edx-enterprise
-django-filter==22.1
+django-filter==23.1
# via
# -r requirements/edx/base.in
# edx-enterprise
@@ -467,7 +467,7 @@ edx-django-utils==5.3.0
# ora2
# outcome-surveys
# super-csv
-edx-drf-extensions==8.4.1
+edx-drf-extensions==8.6.0
# via
# -r requirements/edx/base.in
# edx-completion
@@ -484,7 +484,7 @@ edx-enterprise==3.61.11
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.in
# learner-pathway-progress
-edx-event-bus-kafka==3.9.4
+edx-event-bus-kafka==3.9.6
# via -r requirements/edx/base.in
edx-i18n-tools==0.9.2
# via ora2
@@ -542,6 +542,7 @@ edx-toggles==5.0.0
# edx-completion
# edx-event-bus-kafka
# edx-name-affirmation
+ # edx-search
# edxval
# learner-pathway-progress
# ora2
@@ -570,7 +571,7 @@ event-tracking==2.1.0
# edx-search
fastavro==1.7.3
# via openedx-events
-filelock==3.10.0
+filelock==3.11.0
# via snowflake-connector-python
frozenlist==1.3.3
# via
@@ -600,7 +601,7 @@ html5lib==1.1
# via
# -r requirements/edx/base.in
# ora2
-icalendar==5.0.4
+icalendar==5.0.5
# via -r requirements/edx/base.in
idna==3.4
# via
@@ -609,7 +610,7 @@ idna==3.4
# requests
# snowflake-connector-python
# yarl
-importlib-metadata==6.0.0
+importlib-metadata==6.4.1
# via markdown
importlib-resources==5.12.0
# via jsonschema
@@ -711,7 +712,7 @@ markupsafe==2.1.2
# xblock
maxminddb==2.2.0
# via geoip2
-mock==5.0.1
+mock==5.0.2
# via -r requirements/edx/paver.txt
mongodbproxy @ git+https://github.com/openedx/MongoDBProxy.git@d92bafe9888d2940f647a7b2b2383b29c752f35a
# via -r requirements/edx/github.in
@@ -731,7 +732,7 @@ mysqlclient==2.1.1
# via
# -r requirements/edx/base.in
# openedx-blockstore
-newrelic==8.7.0
+newrelic==8.8.0
# via
# -r requirements/edx/base.in
# edx-django-utils
@@ -767,7 +768,7 @@ openedx-django-require==2.0.0
# via -r requirements/edx/base.in
openedx-django-wiki==2.0.0
# via -r requirements/edx/base.in
-openedx-events==5.1.0
+openedx-events==7.0.0
# via
# -r requirements/edx/base.in
# edx-event-bus-kafka
@@ -783,7 +784,7 @@ oscrypto==1.3.0
# via snowflake-connector-python
outcome-surveys==2.4.0
# via -r requirements/edx/base.in
-packaging==23.0
+packaging==23.1
# via
# drf-yasg
# py2neo
@@ -811,7 +812,7 @@ pgpy==0.6.0
# via edx-enterprise
piexif==1.1.3
# via -r requirements/edx/base.in
-pillow==9.4.0
+pillow==9.5.0
# via
# -r requirements/edx/base.in
# edx-enterprise
@@ -822,7 +823,7 @@ polib==1.2.0
# via edx-i18n-tools
prompt-toolkit==3.0.38
# via click-repl
-psutil==5.9.4
+psutil==5.9.5
# via
# -r requirements/edx/paver.txt
# edx-django-utils
@@ -843,7 +844,7 @@ pycryptodomex==3.17
# lti-consumer-xblock
# pyjwkest
# snowflake-connector-python
-pygments==2.14.0
+pygments==2.15.0
# via
# -r requirements/edx/base.in
# py2neo
@@ -917,7 +918,7 @@ python-memcached==1.59
# via -r requirements/edx/paver.txt
python-slugify==8.0.1
# via code-annotations
-python-swiftclient==4.2.0
+python-swiftclient==4.3.0
# via ora2
python3-openid==3.2.0 ; python_version >= "3"
# via
@@ -929,6 +930,7 @@ python3-saml==1.9.0
# -r requirements/edx/base.in
pytz==2022.7.1
# via
+ # -c requirements/edx/../constraints.txt
# -r requirements/edx/base.in
# babel
# celery
@@ -961,13 +963,13 @@ pyyaml==6.0
# xblock
random2==1.0.1
# via -r requirements/edx/base.in
-rapidfuzz==2.13.7
+rapidfuzz==2.15.1
# via levenshtein
recommender-xblock==2.0.1
# via -r requirements/edx/base.in
-redis==4.5.1
+redis==4.5.4
# via -r requirements/edx/base.in
-regex==2022.10.31
+regex==2023.3.23
# via nltk
requests==2.28.2
# via
@@ -1019,7 +1021,7 @@ semantic-version==2.10.0
# via edx-drf-extensions
shapely==2.0.1
# via -r requirements/edx/base.in
-simplejson==3.18.4
+simplejson==3.19.1
# via
# -r requirements/edx/base.in
# sailthru-client
@@ -1062,7 +1064,7 @@ slumber==0.7.1
# edx-bulk-grades
# edx-enterprise
# edx-rest-api-client
-snowflake-connector-python==3.0.1
+snowflake-connector-python==3.0.2
# via edx-enterprise
social-auth-app-django==5.0.0
# via
@@ -1081,7 +1083,7 @@ sorl-thumbnail==12.9.0
# openedx-django-wiki
sortedcontainers==2.4.0
# via -r requirements/edx/base.in
-soupsieve==2.4
+soupsieve==2.4.1
# via beautifulsoup4
sqlparse==0.4.3
# via
@@ -1143,7 +1145,7 @@ vine==5.0.0
# kombu
voluptuous==0.13.1
# via ora2
-watchdog==2.3.1
+watchdog==3.0.0
# via -r requirements/edx/paver.txt
wcwidth==0.2.6
# via prompt-toolkit
diff --git a/requirements/edx/coverage.txt b/requirements/edx/coverage.txt
index 0192aa373a2f..248e7daa1f3d 100644
--- a/requirements/edx/coverage.txt
+++ b/requirements/edx/coverage.txt
@@ -6,7 +6,7 @@
#
chardet==5.1.0
# via diff-cover
-coverage==7.2.1
+coverage==7.2.3
# via -r requirements/edx/coverage.in
diff-cover==7.5.0
# via -r requirements/edx/coverage.in
@@ -16,5 +16,5 @@ markupsafe==2.1.2
# via jinja2
pluggy==1.0.0
# via diff-cover
-pygments==2.14.0
+pygments==2.15.0
# via diff-cover
diff --git a/requirements/edx/development.in b/requirements/edx/development.in
index 6b4cfa8317a6..1a857f291711 100644
--- a/requirements/edx/development.in
+++ b/requirements/edx/development.in
@@ -10,7 +10,7 @@
-c ../constraints.txt
--r pip-tools.txt # pip-tools and its dependencies, for managing requirements files
+-r ../pip-tools.txt # pip-tools and its dependencies, for managing requirements files
-r testing.txt # Dependencies for running the various test suites
click # Used for perf_tests utilities in modulestore
diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt
index 145905017d4b..58dc6727674c 100644
--- a/requirements/edx/development.txt
+++ b/requirements/edx/development.txt
@@ -24,7 +24,7 @@ amqp==5.1.1
# via
# -r requirements/edx/testing.txt
# kombu
-analytics-python==1.4.0
+analytics-python==1.4.post1
# via -r requirements/edx/testing.txt
aniso8601==9.0.1
# via
@@ -59,7 +59,7 @@ async-timeout==4.0.2
# -r requirements/edx/testing.txt
# aiohttp
# redis
-attrs==22.2.0
+attrs==23.1.0
# via
# -r requirements/edx/testing.txt
# aiohttp
@@ -68,7 +68,6 @@ attrs==22.2.0
# lti-consumer-xblock
# openedx-blockstore
# openedx-events
- # pytest
babel==2.11.0
# via
# -c requirements/edx/../constraints.txt
@@ -84,7 +83,7 @@ backports-zoneinfo==0.2.1
# via
# -r requirements/edx/testing.txt
# icalendar
-beautifulsoup4==4.11.2
+beautifulsoup4==4.12.2
# via
# -r requirements/edx/testing.txt
# pynliner
@@ -125,7 +124,7 @@ bridgekeeper==0.9
# via -r requirements/edx/testing.txt
build==0.10.0
# via
- # -r requirements/edx/pip-tools.txt
+ # -r requirements/edx/../pip-tools.txt
# pip-tools
celery==5.2.7
# via
@@ -168,8 +167,8 @@ chem==1.2.0
click==8.1.3
# via
# -c requirements/edx/../constraints.txt
+ # -r requirements/edx/../pip-tools.txt
# -r requirements/edx/development.in
- # -r requirements/edx/pip-tools.txt
# -r requirements/edx/testing.txt
# celery
# click-didyoumean
@@ -220,7 +219,7 @@ coreschema==0.0.4
# -r requirements/edx/testing.txt
# coreapi
# drf-yasg
-coverage[toml]==7.2.1
+coverage[toml]==7.2.3
# via
# -r requirements/edx/testing.txt
# pytest-cov
@@ -274,7 +273,7 @@ distlib==0.3.6
# via
# -r requirements/edx/testing.txt
# virtualenv
-django==3.2.18
+django==3.2.19
# via
# -c requirements/edx/../common_constraints.txt
# -r requirements/edx/testing.txt
@@ -363,7 +362,7 @@ django-config-models==2.3.0
# edx-enterprise
# edx-name-affirmation
# lti-consumer-xblock
-django-cors-headers==3.13.0
+django-cors-headers==3.14.0
# via -r requirements/edx/testing.txt
django-countries==7.5.1
# via
@@ -378,7 +377,7 @@ django-crum==0.7.9
# edx-rbac
# edx-toggles
# super-csv
-django-debug-toolbar==3.8.1
+django-debug-toolbar==4.0.0
# via -r requirements/edx/development.in
django-environ==0.10.0
# via
@@ -388,7 +387,7 @@ django-fernet-fields==0.6
# via
# -r requirements/edx/testing.txt
# edx-enterprise
-django-filter==22.1
+django-filter==23.1
# via
# -r requirements/edx/testing.txt
# edx-enterprise
@@ -593,7 +592,7 @@ edx-django-utils==5.2.0
# ora2
# outcome-surveys
# super-csv
-edx-drf-extensions==8.4.1
+edx-drf-extensions==8.6.0
# via
# -r requirements/edx/testing.txt
# edx-completion
@@ -610,7 +609,7 @@ edx-enterprise==3.61.11
# -c requirements/edx/../constraints.txt
# -r requirements/edx/testing.txt
# learner-pathway-progress
-edx-event-bus-kafka==3.9.4
+edx-event-bus-kafka==3.9.6
# via -r requirements/edx/testing.txt
edx-i18n-tools==0.9.2
# via
@@ -677,6 +676,7 @@ edx-toggles==5.0.0
# edx-completion
# edx-event-bus-kafka
# edx-name-affirmation
+ # edx-search
# edxval
# learner-pathway-progress
# ora2
@@ -716,11 +716,11 @@ execnet==1.9.0
# pytest-xdist
factory-boy==3.2.1
# via -r requirements/edx/testing.txt
-faker==17.6.0
+faker==18.4.0
# via
# -r requirements/edx/testing.txt
# factory-boy
-fastapi==0.94.1
+fastapi==0.95.1
# via
# -r requirements/edx/testing.txt
# pact-python
@@ -728,7 +728,7 @@ fastavro==1.7.3
# via
# -r requirements/edx/testing.txt
# openedx-events
-filelock==3.10.0
+filelock==3.11.0
# via
# -r requirements/edx/testing.txt
# snowflake-connector-python
@@ -786,7 +786,7 @@ httpx==0.23.1
# via
# -r requirements/edx/testing.txt
# pact-python
-icalendar==5.0.4
+icalendar==5.0.5
# via -r requirements/edx/testing.txt
idna==3.4
# via
@@ -801,7 +801,7 @@ imagesize==1.4.1
# via sphinx
import-linter==1.8.0
# via -r requirements/edx/testing.txt
-importlib-metadata==6.0.0
+importlib-metadata==6.4.1
# via
# -r requirements/edx/testing.txt
# markdown
@@ -960,7 +960,7 @@ mccabe==0.7.0
# pylint
mistune==2.0.5
# via sphinx-mdinclude
-mock==5.0.1
+mock==5.0.2
# via -r requirements/edx/testing.txt
mongodbproxy @ git+https://github.com/openedx/MongoDBProxy.git@d92bafe9888d2940f647a7b2b2383b29c752f35a
# via -r requirements/edx/testing.txt
@@ -980,7 +980,7 @@ multidict==6.0.4
# -r requirements/edx/testing.txt
# aiohttp
# yarl
-mypy==1.1.1
+mypy==1.2.0
# via -r requirements/edx/development.in
mypy-extensions==1.0.0
# via mypy
@@ -988,7 +988,7 @@ mysqlclient==2.1.1
# via
# -r requirements/edx/testing.txt
# openedx-blockstore
-newrelic==8.7.0
+newrelic==8.8.0
# via
# -r requirements/edx/testing.txt
# edx-django-utils
@@ -1027,7 +1027,7 @@ openedx-django-require==2.0.0
# via -r requirements/edx/testing.txt
openedx-django-wiki==2.0.0
# via -r requirements/edx/testing.txt
-openedx-events==5.1.0
+openedx-events==7.0.0
# via
# -r requirements/edx/testing.txt
# edx-event-bus-kafka
@@ -1045,9 +1045,9 @@ oscrypto==1.3.0
# snowflake-connector-python
outcome-surveys==2.4.0
# via -r requirements/edx/testing.txt
-packaging==23.0
+packaging==23.1
# via
- # -r requirements/edx/pip-tools.txt
+ # -r requirements/edx/../pip-tools.txt
# -r requirements/edx/testing.txt
# build
# drf-yasg
@@ -1083,22 +1083,22 @@ pgpy==0.6.0
# via
# -r requirements/edx/testing.txt
# edx-enterprise
-picobox==2.2.0
+picobox==3.0.0
# via sphinxcontrib-openapi
piexif==1.1.3
# via -r requirements/edx/testing.txt
-pillow==9.4.0
+pillow==9.5.0
# via
# -r requirements/edx/testing.txt
# edx-enterprise
# edx-organizations
-pip-tools==6.12.3
- # via -r requirements/edx/pip-tools.txt
+pip-tools==6.13.0
+ # via -r requirements/edx/../pip-tools.txt
pkgutil-resolve-name==1.3.10
# via
# -r requirements/edx/testing.txt
# jsonschema
-platformdirs==3.1.1
+platformdirs==3.2.0
# via
# -r requirements/edx/testing.txt
# pylint
@@ -1117,7 +1117,7 @@ prompt-toolkit==3.0.38
# via
# -r requirements/edx/testing.txt
# click-repl
-psutil==5.9.4
+psutil==5.9.5
# via
# -r requirements/edx/testing.txt
# edx-django-utils
@@ -1152,11 +1152,11 @@ pycryptodomex==3.17
# lti-consumer-xblock
# pyjwkest
# snowflake-connector-python
-pydantic==1.10.6
+pydantic==1.10.7
# via
# -r requirements/edx/testing.txt
# fastapi
-pygments==2.14.0
+pygments==2.15.0
# via
# -r requirements/edx/testing.txt
# diff-cover
@@ -1237,7 +1237,7 @@ pyparsing==3.0.9
# openedx-calc
pyproject-hooks==1.0.0
# via
- # -r requirements/edx/pip-tools.txt
+ # -r requirements/edx/../pip-tools.txt
# build
pyquery==2.0.0
# via -r requirements/edx/testing.txt
@@ -1250,7 +1250,7 @@ pysrt==1.1.2
# via
# -r requirements/edx/testing.txt
# edxval
-pytest==7.2.2
+pytest==7.3.1
# via
# -r requirements/edx/testing.txt
# pylint-pytest
@@ -1300,7 +1300,7 @@ python-slugify==8.0.1
# via
# -r requirements/edx/testing.txt
# code-annotations
-python-swiftclient==4.2.0
+python-swiftclient==4.3.0
# via
# -r requirements/edx/testing.txt
# ora2
@@ -1314,6 +1314,7 @@ python3-saml==1.9.0
# -r requirements/edx/testing.txt
pytz==2022.7.1
# via
+ # -c requirements/edx/../constraints.txt
# -r requirements/edx/testing.txt
# babel
# celery
@@ -1349,15 +1350,15 @@ pyyaml==6.0
# xblock
random2==1.0.1
# via -r requirements/edx/testing.txt
-rapidfuzz==2.13.7
+rapidfuzz==2.15.1
# via
# -r requirements/edx/testing.txt
# levenshtein
recommender-xblock==2.0.1
# via -r requirements/edx/testing.txt
-redis==4.5.1
+redis==4.5.4
# via -r requirements/edx/testing.txt
-regex==2022.10.31
+regex==2023.3.23
# via
# -r requirements/edx/testing.txt
# nltk
@@ -1432,7 +1433,7 @@ semantic-version==2.10.0
# edx-drf-extensions
shapely==2.0.1
# via -r requirements/edx/testing.txt
-simplejson==3.18.4
+simplejson==3.19.1
# via
# -r requirements/edx/testing.txt
# sailthru-client
@@ -1491,7 +1492,7 @@ sniffio==1.3.0
# httpx
snowballstemmer==2.2.0
# via sphinx
-snowflake-connector-python==3.0.1
+snowflake-connector-python==3.0.2
# via
# -r requirements/edx/testing.txt
# edx-enterprise
@@ -1512,7 +1513,7 @@ sorl-thumbnail==12.9.0
# openedx-django-wiki
sortedcontainers==2.4.0
# via -r requirements/edx/testing.txt
-soupsieve==2.4
+soupsieve==2.4.1
# via
# -r requirements/edx/testing.txt
# beautifulsoup4
@@ -1584,7 +1585,7 @@ toml==0.10.2
# via vulture
tomli==2.0.1
# via
- # -r requirements/edx/pip-tools.txt
+ # -r requirements/edx/../pip-tools.txt
# -r requirements/edx/testing.txt
# build
# coverage
@@ -1594,7 +1595,7 @@ tomli==2.0.1
# pyproject-hooks
# pytest
# tox
-tomlkit==0.11.6
+tomlkit==0.11.7
# via
# -r requirements/edx/testing.txt
# pylint
@@ -1665,7 +1666,7 @@ voluptuous==0.13.1
# ora2
vulture==2.7
# via -r requirements/edx/development.in
-watchdog==2.3.1
+watchdog==3.0.0
# via -r requirements/edx/testing.txt
wcwidth==0.2.6
# via
@@ -1691,7 +1692,7 @@ webob==1.8.7
# xblock
wheel==0.40.0
# via
- # -r requirements/edx/pip-tools.txt
+ # -r requirements/edx/../pip-tools.txt
# pip-tools
wrapt==1.15.0
# via
diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt
index 2c73cb516713..087cfd909599 100644
--- a/requirements/edx/doc.txt
+++ b/requirements/edx/doc.txt
@@ -34,7 +34,7 @@ idna==3.4
# via requests
imagesize==1.4.1
# via sphinx
-importlib-metadata==6.0.0
+importlib-metadata==6.4.1
# via sphinx
jinja2==3.1.2
# via
@@ -42,16 +42,18 @@ jinja2==3.1.2
# sphinx
markupsafe==2.1.2
# via jinja2
-packaging==23.0
+packaging==23.1
# via sphinx
pbr==5.11.1
# via stevedore
-pygments==2.14.0
+pygments==2.15.0
# via sphinx
python-slugify==8.0.1
# via code-annotations
pytz==2022.7.1
- # via babel
+ # via
+ # -c requirements/edx/../constraints.txt
+ # babel
pyyaml==6.0
# via code-annotations
requests==2.28.2
diff --git a/requirements/edx/paver.txt b/requirements/edx/paver.txt
index 28aff7e3ec5a..fe1b04e27b1c 100644
--- a/requirements/edx/paver.txt
+++ b/requirements/edx/paver.txt
@@ -20,7 +20,7 @@ libsass==0.10.0
# via -r requirements/edx/paver.in
markupsafe==2.1.2
# via -r requirements/edx/paver.in
-mock==5.0.1
+mock==5.0.2
# via -r requirements/edx/paver.in
path==16.6.0
# via -r requirements/edx/paver.in
@@ -28,7 +28,7 @@ paver==1.3.4
# via -r requirements/edx/paver.in
pbr==5.11.1
# via stevedore
-psutil==5.9.4
+psutil==5.9.5
# via -r requirements/edx/paver.in
pymongo==3.13.0
# via
@@ -50,7 +50,7 @@ stevedore==5.0.0
# edx-opaque-keys
urllib3==1.26.15
# via requests
-watchdog==2.3.1
+watchdog==3.0.0
# via -r requirements/edx/paver.in
wrapt==1.15.0
# via -r requirements/edx/paver.in
diff --git a/requirements/edx/pip.txt b/requirements/edx/pip.txt
deleted file mode 100644
index f0e5e9397cae..000000000000
--- a/requirements/edx/pip.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-#
-# This file is autogenerated by pip-compile with Python 3.8
-# by the following command:
-#
-# make upgrade
-#
-wheel==0.40.0
- # via -r requirements/edx/pip.in
-
-# The following packages are considered to be unsafe in a requirements file:
-pip==23.0.1
- # via -r requirements/edx/pip.in
-setuptools==67.6.0
- # via -r requirements/edx/pip.in
diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt
index 5a94e1edf188..7bf33a176d50 100644
--- a/requirements/edx/testing.txt
+++ b/requirements/edx/testing.txt
@@ -22,7 +22,7 @@ amqp==5.1.1
# via
# -r requirements/edx/base.txt
# kombu
-analytics-python==1.4.0
+analytics-python==1.4.post1
# via -r requirements/edx/base.txt
aniso8601==9.0.1
# via
@@ -55,7 +55,7 @@ async-timeout==4.0.2
# -r requirements/edx/base.txt
# aiohttp
# redis
-attrs==22.2.0
+attrs==23.1.0
# via
# -r requirements/edx/base.txt
# aiohttp
@@ -64,8 +64,6 @@ attrs==22.2.0
# lti-consumer-xblock
# openedx-blockstore
# openedx-events
- # outcome
- # pytest
babel==2.11.0
# via
# -c requirements/edx/../constraints.txt
@@ -80,7 +78,7 @@ backports-zoneinfo==0.2.1
# via
# -r requirements/edx/base.txt
# icalendar
-beautifulsoup4==4.11.2
+beautifulsoup4==4.12.2
# via
# -r requirements/edx/base.txt
# -r requirements/edx/testing.in
@@ -210,7 +208,7 @@ coreschema==0.0.4
# -r requirements/edx/base.txt
# coreapi
# drf-yasg
-coverage[toml]==7.2.1
+coverage[toml]==7.2.3
# via
# -r requirements/edx/coverage.txt
# pytest-cov
@@ -258,7 +256,7 @@ dill==0.3.6
# via pylint
distlib==0.3.6
# via virtualenv
-django==3.2.18
+django==3.2.19
# via
# -c requirements/edx/../common_constraints.txt
# -r requirements/edx/base.txt
@@ -346,7 +344,7 @@ django-config-models==2.3.0
# edx-enterprise
# edx-name-affirmation
# lti-consumer-xblock
-django-cors-headers==3.13.0
+django-cors-headers==3.14.0
# via -r requirements/edx/base.txt
django-countries==7.5.1
# via
@@ -369,7 +367,7 @@ django-fernet-fields==0.6
# via
# -r requirements/edx/base.txt
# edx-enterprise
-django-filter==22.1
+django-filter==23.1
# via
# -r requirements/edx/base.txt
# edx-enterprise
@@ -572,7 +570,7 @@ edx-django-utils==5.2.0
# ora2
# outcome-surveys
# super-csv
-edx-drf-extensions==8.4.1
+edx-drf-extensions==8.6.0
# via
# -r requirements/edx/base.txt
# edx-completion
@@ -589,7 +587,7 @@ edx-enterprise==3.61.11
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
# learner-pathway-progress
-edx-event-bus-kafka==3.9.4
+edx-event-bus-kafka==3.9.6
# via -r requirements/edx/base.txt
edx-i18n-tools==0.9.2
# via
@@ -655,6 +653,7 @@ edx-toggles==5.0.0
# edx-completion
# edx-event-bus-kafka
# edx-name-affirmation
+ # edx-search
# edxval
# learner-pathway-progress
# ora2
@@ -690,15 +689,15 @@ execnet==1.9.0
# via pytest-xdist
factory-boy==3.2.1
# via -r requirements/edx/testing.in
-faker==17.6.0
+faker==18.4.0
# via factory-boy
-fastapi==0.94.1
+fastapi==0.95.1
# via pact-python
fastavro==1.7.3
# via
# -r requirements/edx/base.txt
# openedx-events
-filelock==3.10.0
+filelock==3.11.0
# via
# -r requirements/edx/base.txt
# snowflake-connector-python
@@ -749,7 +748,7 @@ httpretty==1.1.4
# via -r requirements/edx/testing.in
httpx==0.23.1
# via pact-python
-icalendar==5.0.4
+icalendar==5.0.5
# via -r requirements/edx/base.txt
idna==3.4
# via
@@ -762,7 +761,7 @@ idna==3.4
# yarl
import-linter==1.8.0
# via -r requirements/edx/testing.in
-importlib-metadata==6.0.0
+importlib-metadata==6.4.1
# via
# -r requirements/edx/base.txt
# markdown
@@ -912,7 +911,7 @@ maxminddb==2.2.0
# geoip2
mccabe==0.7.0
# via pylint
-mock==5.0.1
+mock==5.0.2
# via -r requirements/edx/base.txt
mongodbproxy @ git+https://github.com/openedx/MongoDBProxy.git@d92bafe9888d2940f647a7b2b2383b29c752f35a
# via -r requirements/edx/base.txt
@@ -936,7 +935,7 @@ mysqlclient==2.1.1
# via
# -r requirements/edx/base.txt
# openedx-blockstore
-newrelic==8.7.0
+newrelic==8.8.0
# via
# -r requirements/edx/base.txt
# edx-django-utils
@@ -975,7 +974,7 @@ openedx-django-require==2.0.0
# via -r requirements/edx/base.txt
openedx-django-wiki==2.0.0
# via -r requirements/edx/base.txt
-openedx-events==5.1.0
+openedx-events==7.0.0
# via
# -r requirements/edx/base.txt
# edx-event-bus-kafka
@@ -993,7 +992,7 @@ oscrypto==1.3.0
# snowflake-connector-python
outcome-surveys==2.4.0
# via -r requirements/edx/base.txt
-packaging==23.0
+packaging==23.1
# via
# -r requirements/edx/base.txt
# drf-yasg
@@ -1030,7 +1029,7 @@ pgpy==0.6.0
# edx-enterprise
piexif==1.1.3
# via -r requirements/edx/base.txt
-pillow==9.4.0
+pillow==9.5.0
# via
# -r requirements/edx/base.txt
# edx-enterprise
@@ -1039,7 +1038,7 @@ pkgutil-resolve-name==1.3.10
# via
# -r requirements/edx/base.txt
# jsonschema
-platformdirs==3.1.1
+platformdirs==3.2.0
# via
# pylint
# virtualenv
@@ -1058,7 +1057,7 @@ prompt-toolkit==3.0.38
# via
# -r requirements/edx/base.txt
# click-repl
-psutil==5.9.4
+psutil==5.9.5
# via
# -r requirements/edx/base.txt
# edx-django-utils
@@ -1091,9 +1090,9 @@ pycryptodomex==3.17
# lti-consumer-xblock
# pyjwkest
# snowflake-connector-python
-pydantic==1.10.6
+pydantic==1.10.7
# via fastapi
-pygments==2.14.0
+pygments==2.15.0
# via
# -r requirements/edx/base.txt
# -r requirements/edx/coverage.txt
@@ -1176,7 +1175,7 @@ pysrt==1.1.2
# via
# -r requirements/edx/base.txt
# edxval
-pytest==7.2.2
+pytest==7.3.1
# via
# -r requirements/edx/testing.in
# pylint-pytest
@@ -1226,7 +1225,7 @@ python-slugify==8.0.1
# via
# -r requirements/edx/base.txt
# code-annotations
-python-swiftclient==4.2.0
+python-swiftclient==4.3.0
# via
# -r requirements/edx/base.txt
# ora2
@@ -1240,6 +1239,7 @@ python3-saml==1.9.0
# -r requirements/edx/base.txt
pytz==2022.7.1
# via
+ # -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
# babel
# celery
@@ -1272,15 +1272,15 @@ pyyaml==6.0
# xblock
random2==1.0.1
# via -r requirements/edx/base.txt
-rapidfuzz==2.13.7
+rapidfuzz==2.15.1
# via
# -r requirements/edx/base.txt
# levenshtein
recommender-xblock==2.0.1
# via -r requirements/edx/base.txt
-redis==4.5.1
+redis==4.5.4
# via -r requirements/edx/base.txt
-regex==2022.10.31
+regex==2023.3.23
# via
# -r requirements/edx/base.txt
# nltk
@@ -1352,7 +1352,7 @@ semantic-version==2.10.0
# edx-drf-extensions
shapely==2.0.1
# via -r requirements/edx/base.txt
-simplejson==3.18.4
+simplejson==3.19.1
# via
# -r requirements/edx/base.txt
# sailthru-client
@@ -1406,7 +1406,7 @@ sniffio==1.3.0
# anyio
# httpcore
# httpx
-snowflake-connector-python==3.0.1
+snowflake-connector-python==3.0.2
# via
# -r requirements/edx/base.txt
# edx-enterprise
@@ -1427,7 +1427,7 @@ sorl-thumbnail==12.9.0
# openedx-django-wiki
sortedcontainers==2.4.0
# via -r requirements/edx/base.txt
-soupsieve==2.4
+soupsieve==2.4.1
# via
# -r requirements/edx/base.txt
# beautifulsoup4
@@ -1476,7 +1476,7 @@ tomli==2.0.1
# pylint
# pytest
# tox
-tomlkit==0.11.6
+tomlkit==0.11.7
# via pylint
tox==3.28.0
# via
@@ -1538,7 +1538,7 @@ voluptuous==0.13.1
# via
# -r requirements/edx/base.txt
# ora2
-watchdog==2.3.1
+watchdog==3.0.0
# via -r requirements/edx/base.txt
wcwidth==0.2.6
# via
diff --git a/requirements/edx/pip-tools.in b/requirements/pip-tools.in
similarity index 95%
rename from requirements/edx/pip-tools.in
rename to requirements/pip-tools.in
index 50f3de3e19d6..5d4419ea4bc0 100644
--- a/requirements/edx/pip-tools.in
+++ b/requirements/pip-tools.in
@@ -7,6 +7,6 @@
# * confirm that it has no system requirements beyond what we already install
# * run "make upgrade" to update the detailed requirements files
--c ../constraints.txt
+-c constraints.txt
pip-tools # Contains pip-compile, used to generate pip requirements files
diff --git a/requirements/edx/pip-tools.txt b/requirements/pip-tools.txt
similarity index 76%
rename from requirements/edx/pip-tools.txt
rename to requirements/pip-tools.txt
index 15e167ee7153..cda5abf53760 100644
--- a/requirements/edx/pip-tools.txt
+++ b/requirements/pip-tools.txt
@@ -8,12 +8,12 @@ build==0.10.0
# via pip-tools
click==8.1.3
# via
- # -c requirements/edx/../constraints.txt
+ # -c requirements/constraints.txt
# pip-tools
-packaging==23.0
+packaging==23.1
# via build
-pip-tools==6.12.3
- # via -r requirements/edx/pip-tools.in
+pip-tools==6.13.0
+ # via -r requirements/pip-tools.in
pyproject-hooks==1.0.0
# via build
tomli==2.0.1
diff --git a/requirements/edx/pip.in b/requirements/pip.in
similarity index 77%
rename from requirements/edx/pip.in
rename to requirements/pip.in
index 741969aac127..cc36db5a0831 100644
--- a/requirements/edx/pip.in
+++ b/requirements/pip.in
@@ -1,4 +1,4 @@
--c ../constraints.txt
+-c constraints.txt
# Core dependencies for installing other dependencies
pip
diff --git a/requirements/pip.txt b/requirements/pip.txt
index a811cc82f187..4b902cbc3468 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -1,2 +1,14 @@
-pip==22.1
-wheel==0.37.1
+#
+# This file is autogenerated by pip-compile with Python 3.8
+# by the following command:
+#
+# make upgrade
+#
+wheel==0.40.0
+ # via -r requirements/pip.in
+
+# The following packages are considered to be unsafe in a requirements file:
+pip==23.1
+ # via -r requirements/pip.in
+setuptools==67.6.1
+ # via -r requirements/pip.in
diff --git a/scripts/ci-runner.Dockerfile b/scripts/ci-runner.Dockerfile
index 999b89941eff..291f1d33d32f 100644
--- a/scripts/ci-runner.Dockerfile
+++ b/scripts/ci-runner.Dockerfile
@@ -46,7 +46,7 @@ COPY openedx/core/lib openedx/core/lib
COPY lms lms
COPY cms cms
COPY requirements/pip.txt requirements/pip.txt
-COPY requirements/edx/pip-tools.txt requirements/edx/pip-tools.txt
+COPY requirements/pip-tools.txt requirements/pip-tools.txt
COPY requirements/edx/testing.txt requirements/edx/testing.txt
COPY Makefile Makefile
RUN make test-requirements
diff --git a/xmodule/js/src/video/04_video_full_screen.js b/xmodule/js/src/video/04_video_full_screen.js
index 0730300d105e..9da85d8dfb67 100644
--- a/xmodule/js/src/video/04_video_full_screen.js
+++ b/xmodule/js/src/video/04_video_full_screen.js
@@ -161,7 +161,21 @@
return this.videoFullScreen.height;
}
- /**
+ function notifyParent(fullscreenOpen) {
+ if (window !== window.parent) {
+ // This is used by the Learning MFE to know about changing fullscreen mode.
+ // The MFE is then able to respond appropriately and scroll window to the previous position.
+ window.parent.postMessage({
+ type: 'plugin.videoFullScreen',
+ payload: {
+ open: fullscreenOpen
+ }
+ }, document.referrer
+ );
+ }
+ }
+
+ /**
* Event handler to toggle fullscreen mode.
* @param {jquery Event} event
*/
@@ -192,6 +206,8 @@
this.resizer.delta.reset().setMode('width');
}
this.el.trigger('fullscreen', [this.isFullScreen]);
+
+ this.videoFullScreen.notifyParent(false);
}
function handleEnter() {
@@ -202,6 +218,8 @@
return;
}
+ this.videoFullScreen.notifyParent(true);
+
this.videoFullScreen.fullScreenState = this.isFullScreen = true;
fullScreenClassNameEl.addClass('video-fullscreen');
this.videoFullScreen.fullScreenEl
@@ -267,7 +285,8 @@
handleFullscreenChange: handleFullscreenChange,
toggle: toggle,
toggleHandler: toggleHandler,
- updateControlsHeight: updateControlsHeight
+ updateControlsHeight: updateControlsHeight,
+ notifyParent: notifyParent
};
state.bindTo(methodsDict, state.videoFullScreen, state);