From 89e3bebfa57ffc07986022c6426f97802c816fd8 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Thu, 21 May 2026 14:05:34 -0500 Subject: [PATCH 1/8] refactor: Remove legacy css's and inject paragon themes - Commented main-v1 and course styles in common.py - Added `MFE_CONFIG_API_URL` - Extrated and created `get_mfe_config` from the MFE config Api view - `_get_paragon_css_urls` created --- lms/djangoapps/courseware/views/views.py | 34 +++++++++++ lms/djangoapps/mfe_config_api/api.py | 60 +++++++++++++++++++ lms/djangoapps/mfe_config_api/views.py | 48 +-------------- lms/envs/common.py | 29 +++++---- .../courseware/courseware-chromeless.html | 4 +- 5 files changed, 116 insertions(+), 59 deletions(-) create mode 100644 lms/djangoapps/mfe_config_api/api.py diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 2c89800d82b6..a4b86e6bb7e6 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -118,6 +118,7 @@ from lms.djangoapps.instructor.views.api import require_global_staff from lms.djangoapps.survey import views as survey_views from lms.djangoapps.verify_student.services import IDVerificationService +from lms.djangoapps.mfe_config_api.views import get_mfe_config from openedx.core.djangoapps.catalog.utils import ( get_course_data, get_course_uuid_for_course, @@ -1724,6 +1725,7 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True, disable_sta 'is_learning_mfe': is_learning_mfe, 'is_mobile_app': is_mobile_app, 'render_course_wide_assets': True, + 'paragon_css_urls': _get_paragon_css_urls(), } try: @@ -1752,6 +1754,38 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True, disable_sta return render_to_response('courseware/courseware-chromeless.html', context, request=request) +def _get_paragon_css_urls(): + """ + Gets the paragon themes + + Calls the MFE Config API. If the API lives in the current + server (relative path), calls the Python Api directly. If + the server lives in a remote server, makes a request to + the API. + """ + + api_url = getattr(settings, 'MFE_CONFIG_API_URL', None) + + if api_url is None or api_url.startswith('/'): + mfe_config = get_mfe_config(mfe='learning') + else: + response = requests.get(f"{api_url}?mfe=learning") + mfe_config = response.json() + + theme_urls = mfe_config.get('PARAGON_THEME_URLS', {}) + + variant = theme_urls.get('default', {}).get('light') + theme = [url for url in [ + 'https://cdn.jsdelivr.net/npm/@openedx/paragon@23/dist/light.min.css', + theme_urls.get('variants', {}).get(variant, {}).get('urls', {}).get('brandOverride'), + ] if url] + core = [url for url in [ + 'https://cdn.jsdelivr.net/npm/@openedx/paragon@23/dist/core.min.css', + theme_urls.get('core', {}).get('urls', {}).get('brandOverride'), + ] if url] + + return theme + core + def get_optimization_flags_for_content(block, fragment): """ diff --git a/lms/djangoapps/mfe_config_api/api.py b/lms/djangoapps/mfe_config_api/api.py new file mode 100644 index 000000000000..9101cfc69323 --- /dev/null +++ b/lms/djangoapps/mfe_config_api/api.py @@ -0,0 +1,60 @@ +from django.conf import settings + +from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers + + +# The public API is only the following symbols: +__all__ = [ + # API methods + "get_mfe_config", +] + +def get_mfe_config(mfe=None): + """ + Return the merged MFE configuration for the given MFE app. + """ + # Get values from django settings (level 6) or site configuration (level 5) + legacy_config = _get_legacy_config() + + # Get values from mfe configuration, either from django settings (level 4) or site configuration (level 3) + mfe_config = configuration_helpers.get_value("MFE_CONFIG", settings.MFE_CONFIG) + + # Get values from mfe overrides, either from django settings (level 2) or site configuration (level 1) + mfe_config_overrides = {} + + if mfe: + app_config = configuration_helpers.get_value( + "MFE_CONFIG_OVERRIDES", + settings.MFE_CONFIG_OVERRIDES, + ) + mfe_config_overrides = app_config.get(mfe, {}) + + # Merge the three configs in the order of precedence + return legacy_config | mfe_config | mfe_config_overrides + + + +def _get_legacy_config() -> dict: + """ + Return legacy configuration values available in either site configuration or django settings. + """ + return { + "ENABLE_COURSE_SORTING_BY_START_DATE": configuration_helpers.get_value( + "ENABLE_COURSE_SORTING_BY_START_DATE", + settings.FEATURES["ENABLE_COURSE_SORTING_BY_START_DATE"] + ), + "HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID": configuration_helpers.get_value( + "homepage_promo_video_youtube_id", + None + ), + "HOMEPAGE_COURSE_MAX": configuration_helpers.get_value( + "HOMEPAGE_COURSE_MAX", + settings.HOMEPAGE_COURSE_MAX + ), + "COURSE_ABOUT_TWITTER_ACCOUNT": configuration_helpers.get_value( + "course_about_twitter_account", + settings.PLATFORM_TWITTER_ACCOUNT + ), + "NON_BROWSABLE_COURSES": not settings.FEATURES.get("COURSES_ARE_BROWSABLE"), + "ENABLE_COURSE_DISCOVERY": settings.FEATURES["ENABLE_COURSE_DISCOVERY"], + } diff --git a/lms/djangoapps/mfe_config_api/views.py b/lms/djangoapps/mfe_config_api/views.py index 0ab71b151b88..1e2537918e8e 100644 --- a/lms/djangoapps/mfe_config_api/views.py +++ b/lms/djangoapps/mfe_config_api/views.py @@ -10,8 +10,7 @@ from rest_framework import status from rest_framework.views import APIView -from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers - +from .api import get_mfe_config class MFEConfigView(APIView): """ @@ -72,49 +71,6 @@ def get(self, request): if not settings.ENABLE_MFE_CONFIG_API: return HttpResponseNotFound() - # Get values from django settings (level 6) or site configuration (level 5) - legacy_config = self._get_legacy_config() - - # Get values from mfe configuration, either from django settings (level 4) or site configuration (level 3) - mfe_config = configuration_helpers.get_value("MFE_CONFIG", settings.MFE_CONFIG) - - # Get values from mfe overrides, either from django settings (level 2) or site configuration (level 1) - mfe_config_overrides = {} - if request.query_params.get("mfe"): - mfe = str(request.query_params.get("mfe")) - app_config = configuration_helpers.get_value( - "MFE_CONFIG_OVERRIDES", - settings.MFE_CONFIG_OVERRIDES, - ) - mfe_config_overrides = app_config.get(mfe, {}) - - # Merge the three configs in the order of precedence - merged_config = legacy_config | mfe_config | mfe_config_overrides + merged_config = get_mfe_config(str(request.query_params.get("mfe"))) return JsonResponse(merged_config, status=status.HTTP_200_OK) - - @staticmethod - def _get_legacy_config() -> dict: - """ - Return legacy configuration values available in either site configuration or django settings. - """ - return { - "ENABLE_COURSE_SORTING_BY_START_DATE": configuration_helpers.get_value( - "ENABLE_COURSE_SORTING_BY_START_DATE", - settings.FEATURES["ENABLE_COURSE_SORTING_BY_START_DATE"] - ), - "HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID": configuration_helpers.get_value( - "homepage_promo_video_youtube_id", - None - ), - "HOMEPAGE_COURSE_MAX": configuration_helpers.get_value( - "HOMEPAGE_COURSE_MAX", - settings.HOMEPAGE_COURSE_MAX - ), - "COURSE_ABOUT_TWITTER_ACCOUNT": configuration_helpers.get_value( - "course_about_twitter_account", - settings.PLATFORM_TWITTER_ACCOUNT - ), - "NON_BROWSABLE_COURSES": not settings.FEATURES.get("COURSES_ARE_BROWSABLE"), - "ENABLE_COURSE_DISCOVERY": settings.FEATURES["ENABLE_COURSE_DISCOVERY"], - } diff --git a/lms/envs/common.py b/lms/envs/common.py index 06ba0c050aea..70c66df856cb 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1679,15 +1679,15 @@ 'output_filename': 'css/lms-style-vendor-tinymce-skin.css', }, 'style-main-v1': { - 'source_filenames': [ - 'css/lms-main-v1.css', - ], + #'source_filenames': [ + # 'css/lms-main-v1.css', + #], 'output_filename': 'css/lms-main-v1.css', }, 'style-main-v1-rtl': { - 'source_filenames': [ - 'css/lms-main-v1-rtl.css', - ], + #'source_filenames': [ + # 'css/lms-main-v1-rtl.css', + #], 'output_filename': 'css/lms-main-v1-rtl.css', }, 'style-course-vendor': { @@ -1699,15 +1699,15 @@ 'output_filename': 'css/lms-style-course-vendor.css', }, 'style-course': { - 'source_filenames': [ - 'css/lms-course.css', - ], + #'source_filenames': [ + # 'css/lms-course.css', + #], 'output_filename': 'css/lms-course.css', }, 'style-course-rtl': { - 'source_filenames': [ - 'css/lms-course-rtl.css', - ], + #'source_filenames': [ + # 'css/lms-course-rtl.css', + #], 'output_filename': 'css/lms-course-rtl.css', }, 'style-student-notes': { @@ -3463,6 +3463,11 @@ # .. setting_creation_date: 2022-08-05 MFE_CONFIG_OVERRIDES = {} +# .. setting_name: MFE_CONFIG_API_URL +# .. setting_default: '/api/mfe_config/v1' +# .. setting_description: The URL to get the MFE Config +MFE_CONFIG_API_URL = '/api/mfe_config/v1' + # .. setting_name: MFE_CONFIG_API_CACHE_TIMEOUT # .. setting_default: 60*5 # .. setting_description: The MFE Config API response will be cached during the diff --git a/lms/templates/courseware/courseware-chromeless.html b/lms/templates/courseware/courseware-chromeless.html index deeda26c431d..366e3a3e98d2 100644 --- a/lms/templates/courseware/courseware-chromeless.html +++ b/lms/templates/courseware/courseware-chromeless.html @@ -33,7 +33,9 @@ <%block name="headextra"> <%static:css group='style-course-vendor'/> -<%static:css group='style-course'/> +% for css_url in paragon_css_urls: + +% endfor ## Utility: Notes % if edx_notes_enabled: <%static:css group='style-student-notes'/> From 31038668e523e2ead4ffa4c7feb2e9f78b5513bc Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Thu, 21 May 2026 14:56:45 -0500 Subject: [PATCH 2/8] fix: Broken tests and style --- lms/djangoapps/mfe_config_api/api.py | 4 ++++ lms/djangoapps/mfe_config_api/tests/test_views.py | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/api.py b/lms/djangoapps/mfe_config_api/api.py index 9101cfc69323..be7fd6cf7e9f 100644 --- a/lms/djangoapps/mfe_config_api/api.py +++ b/lms/djangoapps/mfe_config_api/api.py @@ -1,3 +1,7 @@ +""" +MFE API funtions. +""" + from django.conf import settings from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index fcf1f1ad29e8..141e257ef44e 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -32,7 +32,7 @@ def setUp(self): self.mfe_config_api_url = reverse("mfe_config_api:config") return super().setUp() - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") + @patch("lms.djangoapps.mfe_config_api.api.configuration_helpers") def test_get_mfe_config(self, configuration_helpers_mock): """Test the get mfe config from site configuration with the mfe api. @@ -52,7 +52,7 @@ def side_effect(key, default=None): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json(), {**default_legacy_config, "EXAMPLE_VAR": "value"}) - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") + @patch("lms.djangoapps.mfe_config_api.api.configuration_helpers") def test_get_mfe_config_with_queryparam(self, configuration_helpers_mock): """Test the get mfe config with a query param from site configuration. @@ -120,7 +120,7 @@ def side_effect(key, default=None): expected_response={**default_legacy_config, "EXAMPLE_VAR": "mymfe_value"}, ), ) - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") + @patch("lms.djangoapps.mfe_config_api.api.configuration_helpers") def test_get_mfe_config_with_queryparam_multiple_configs( self, configuration_helpers_mock, @@ -175,7 +175,7 @@ def test_get_mfe_config_with_queryparam_from_django_settings(self): expected = default_legacy_config | settings.MFE_CONFIG | settings.MFE_CONFIG_OVERRIDES["mymfe"] self.assertEqual(response.json(), expected) - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") + @patch("lms.djangoapps.mfe_config_api.api.configuration_helpers") @override_settings(ENABLE_MFE_CONFIG_API=False) def test_404_get_mfe_config(self, configuration_helpers_mock): """Test the 404 not found response from get mfe config. @@ -188,7 +188,7 @@ def test_404_get_mfe_config(self, configuration_helpers_mock): configuration_helpers_mock.get_value.assert_not_called() self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") + @patch("lms.djangoapps.mfe_config_api.api.configuration_helpers") def test_get_mfe_config_for_catalog(self, configuration_helpers_mock): """Test the mfe config by explicitly using catalog mfe as an example. @@ -232,7 +232,7 @@ def side_effect(key, default=None): self.assertEqual(data["NON_BROWSABLE_COURSES"], True) self.assertEqual(data["ENABLE_COURSE_DISCOVERY"], False) - @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") + @patch("lms.djangoapps.mfe_config_api.api.configuration_helpers") def test_config_order_of_precedence(self, configuration_helpers_mock): """Test the precedence of configuration values by explicitly using catalog MFE as an example. From 0d554d926169b35a5def181219f456ce9e37d153 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Thu, 21 May 2026 16:28:14 -0500 Subject: [PATCH 3/8] fix: Error with source_filenames --- lms/envs/common.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lms/envs/common.py b/lms/envs/common.py index 70c66df856cb..be9c59da1a27 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1679,15 +1679,15 @@ 'output_filename': 'css/lms-style-vendor-tinymce-skin.css', }, 'style-main-v1': { - #'source_filenames': [ + 'source_filenames': [ # 'css/lms-main-v1.css', - #], + ], 'output_filename': 'css/lms-main-v1.css', }, 'style-main-v1-rtl': { - #'source_filenames': [ + 'source_filenames': [ # 'css/lms-main-v1-rtl.css', - #], + ], 'output_filename': 'css/lms-main-v1-rtl.css', }, 'style-course-vendor': { @@ -1699,15 +1699,15 @@ 'output_filename': 'css/lms-style-course-vendor.css', }, 'style-course': { - #'source_filenames': [ + 'source_filenames': [ # 'css/lms-course.css', - #], + ], 'output_filename': 'css/lms-course.css', }, 'style-course-rtl': { - #'source_filenames': [ + 'source_filenames': [ # 'css/lms-course-rtl.css', - #], + ], 'output_filename': 'css/lms-course-rtl.css', }, 'style-student-notes': { From 4be2a2ce7f1212aec84a9f46f655f9fe2a6d9a3a Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Fri, 22 May 2026 11:46:03 -0500 Subject: [PATCH 4/8] test: Adding tests --- lms/djangoapps/courseware/tests/test_views.py | 87 +++++++++++++++++++ lms/djangoapps/courseware/views/views.py | 4 +- lms/djangoapps/mfe_config_api/views.py | 2 +- .../courseware/courseware-chromeless.html | 8 +- 4 files changed, 96 insertions(+), 5 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 2c3ece3133a5..5415502af556 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -2609,6 +2609,93 @@ def test_access(self, is_waffle_enabled, is_public_video, expected_status_code): self.assertEqual(expected_status_code, embed_response.status_code) +class TestGetParagonCssUrls(TestCase): + """ + Unit tests for the _get_paragon_css_urls() helper in courseware views. + + The helper has two code paths: + - Local: MFE_CONFIG_API_URL is None or relative → calls get_mfe_config() directly (no HTTP). + - Remote: MFE_CONFIG_API_URL is an absolute URL → calls requests.get(). + In both cases it builds a list of CSS URLs from PARAGON_THEME_URLS, falling back to + jsDelivr CDN URLs when brand overrides are not configured. + """ + + CDN_CORE = 'https://cdn.jsdelivr.net/npm/@openedx/paragon@23/dist/core.min.css' + CDN_LIGHT = 'https://cdn.jsdelivr.net/npm/@openedx/paragon@23/dist/light.min.css' + BRAND_CORE_URL = 'https://wgu-static.example.com/core.css' + BRAND_LIGHT_URL = 'https://wgu-static.example.com/light.css' + REMOTE_API_URL = 'https://remote.example.com/api/mfe_config/v1' + + PARAGON_THEME_URLS = { + 'core': {'urls': {'brandOverride': BRAND_CORE_URL}}, + 'default': {'light': 'light'}, + 'variants': { + 'light': {'urls': {'brandOverride': BRAND_LIGHT_URL}}, + }, + } + + @override_settings(MFE_CONFIG_API_URL='/api/mfe_config/v1') + @patch('lms.djangoapps.courseware.views.views.get_mfe_config') + def test_local_path_no_brand_overrides(self, mock_get_mfe_config): + """Local path, no PARAGON_THEME_URLS: returns only CDN fallback URLs.""" + mock_get_mfe_config.return_value = {} + + result = views._get_paragon_css_urls() + + mock_get_mfe_config.assert_called_once_with(mfe='learning') + self.assertEqual(result, [self.CDN_LIGHT, self.CDN_CORE]) + + @override_settings(MFE_CONFIG_API_URL='/api/mfe_config/v1') + @patch('lms.djangoapps.courseware.views.views.get_mfe_config') + def test_local_path_with_brand_overrides(self, mock_get_mfe_config): + """Local path, PARAGON_THEME_URLS configured: returns CDN + brand override URLs.""" + mock_get_mfe_config.return_value = {'PARAGON_THEME_URLS': self.PARAGON_THEME_URLS} + + result = views._get_paragon_css_urls() + + mock_get_mfe_config.assert_called_once_with(mfe='learning') + self.assertEqual(result, [ + self.CDN_LIGHT, self.BRAND_LIGHT_URL, + self.CDN_CORE, self.BRAND_CORE_URL, + ]) + + @override_settings(MFE_CONFIG_API_URL=None) + @patch('lms.djangoapps.courseware.views.views.get_mfe_config') + def test_none_url_uses_local_path(self, mock_get_mfe_config): + """When MFE_CONFIG_API_URL is None, falls back to direct Python call (no HTTP).""" + mock_get_mfe_config.return_value = {} + + result = views._get_paragon_css_urls() + + mock_get_mfe_config.assert_called_once_with(mfe='learning') + self.assertEqual(result, [self.CDN_LIGHT, self.CDN_CORE]) + + @override_settings(MFE_CONFIG_API_URL=REMOTE_API_URL) + @patch('lms.djangoapps.courseware.views.views.requests.get') + def test_remote_path_no_brand_overrides(self, mock_requests_get): + """Remote path (absolute URL), no PARAGON_THEME_URLS: returns only CDN fallback URLs.""" + mock_requests_get.return_value.json.return_value = {} + + result = views._get_paragon_css_urls() + + mock_requests_get.assert_called_once_with(f'{self.REMOTE_API_URL}?mfe=learning') + self.assertEqual(result, [self.CDN_LIGHT, self.CDN_CORE]) + + @override_settings(MFE_CONFIG_API_URL=REMOTE_API_URL) + @patch('lms.djangoapps.courseware.views.views.requests.get') + def test_remote_path_with_brand_overrides(self, mock_requests_get): + """Remote path (absolute URL): calls requests.get and returns CDN + brand URLs.""" + mock_requests_get.return_value.json.return_value = {'PARAGON_THEME_URLS': self.PARAGON_THEME_URLS} + + result = views._get_paragon_css_urls() + + mock_requests_get.assert_called_once_with(f'{self.REMOTE_API_URL}?mfe=learning') + self.assertEqual(result, [ + self.CDN_LIGHT, self.BRAND_LIGHT_URL, + self.CDN_CORE, self.BRAND_CORE_URL, + ]) + + class TestRenderXBlockSelfPaced(TestRenderXBlock): # lint-amnesty, pylint: disable=test-inherits-tests """ Test rendering XBlocks for a self-paced course. Relies on the query diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index a4b86e6bb7e6..8501fd249008 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -118,7 +118,7 @@ from lms.djangoapps.instructor.views.api import require_global_staff from lms.djangoapps.survey import views as survey_views from lms.djangoapps.verify_student.services import IDVerificationService -from lms.djangoapps.mfe_config_api.views import get_mfe_config +from lms.djangoapps.mfe_config_api.api import get_mfe_config from openedx.core.djangoapps.catalog.utils import ( get_course_data, get_course_uuid_for_course, @@ -1754,6 +1754,7 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True, disable_sta return render_to_response('courseware/courseware-chromeless.html', context, request=request) + def _get_paragon_css_urls(): """ Gets the paragon themes @@ -1941,6 +1942,7 @@ def get_template_and_context(self, course, video_block): 'is_learning_mfe': True, 'is_mobile_app': False, 'is_enrolled_in_course': self.get_is_enrolled_in_course(course), + 'paragon_css_urls': _get_paragon_css_urls(), } return 'public_video.html', context diff --git a/lms/djangoapps/mfe_config_api/views.py b/lms/djangoapps/mfe_config_api/views.py index 1e2537918e8e..624c7bcc13ff 100644 --- a/lms/djangoapps/mfe_config_api/views.py +++ b/lms/djangoapps/mfe_config_api/views.py @@ -71,6 +71,6 @@ def get(self, request): if not settings.ENABLE_MFE_CONFIG_API: return HttpResponseNotFound() - merged_config = get_mfe_config(str(request.query_params.get("mfe"))) + merged_config = get_mfe_config(request.query_params.get("mfe")) return JsonResponse(merged_config, status=status.HTTP_200_OK) diff --git a/lms/templates/courseware/courseware-chromeless.html b/lms/templates/courseware/courseware-chromeless.html index 366e3a3e98d2..1fc524930154 100644 --- a/lms/templates/courseware/courseware-chromeless.html +++ b/lms/templates/courseware/courseware-chromeless.html @@ -33,9 +33,11 @@ <%block name="headextra"> <%static:css group='style-course-vendor'/> -% for css_url in paragon_css_urls: - -% endfor +% if paragon_css_urls: + % for css_url in paragon_css_urls: + + % endfor +%endif ## Utility: Notes % if edx_notes_enabled: <%static:css group='style-student-notes'/> From dc33a0ed0859ed0b928065be2fe14c2f0a87af08 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Mon, 25 May 2026 10:24:07 +0700 Subject: [PATCH 5/8] fix: Issues with laod styles --- lms/djangoapps/mfe_config_api/api.py | 8 ++++---- lms/templates/courseware/courseware-chromeless.html | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/api.py b/lms/djangoapps/mfe_config_api/api.py index be7fd6cf7e9f..fa0c1fe88dd4 100644 --- a/lms/djangoapps/mfe_config_api/api.py +++ b/lms/djangoapps/mfe_config_api/api.py @@ -45,7 +45,7 @@ def _get_legacy_config() -> dict: return { "ENABLE_COURSE_SORTING_BY_START_DATE": configuration_helpers.get_value( "ENABLE_COURSE_SORTING_BY_START_DATE", - settings.FEATURES["ENABLE_COURSE_SORTING_BY_START_DATE"] + settings.FEATURES.get("ENABLE_COURSE_SORTING_BY_START_DATE") ), "HOMEPAGE_PROMO_VIDEO_YOUTUBE_ID": configuration_helpers.get_value( "homepage_promo_video_youtube_id", @@ -53,12 +53,12 @@ def _get_legacy_config() -> dict: ), "HOMEPAGE_COURSE_MAX": configuration_helpers.get_value( "HOMEPAGE_COURSE_MAX", - settings.HOMEPAGE_COURSE_MAX + getattr(settings, 'HOMEPAGE_COURSE_MAX', None), ), "COURSE_ABOUT_TWITTER_ACCOUNT": configuration_helpers.get_value( "course_about_twitter_account", - settings.PLATFORM_TWITTER_ACCOUNT + getattr(settings, 'PLATFORM_TWITTER_ACCOUNT', None), ), "NON_BROWSABLE_COURSES": not settings.FEATURES.get("COURSES_ARE_BROWSABLE"), - "ENABLE_COURSE_DISCOVERY": settings.FEATURES["ENABLE_COURSE_DISCOVERY"], + "ENABLE_COURSE_DISCOVERY": settings.FEATURES.get("ENABLE_COURSE_DISCOVERY"), } diff --git a/lms/templates/courseware/courseware-chromeless.html b/lms/templates/courseware/courseware-chromeless.html index 1fc524930154..c227265958d3 100644 --- a/lms/templates/courseware/courseware-chromeless.html +++ b/lms/templates/courseware/courseware-chromeless.html @@ -37,6 +37,8 @@ % for css_url in paragon_css_urls: % endfor + ## LMS CSS used to hide dialogs marked as aria-hidden; Paragon doesn't, so we add it back explicitly. + %endif ## Utility: Notes % if edx_notes_enabled: From 7c1f0a63d625a9378576c56bca9ef98e5b9d0edb Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Mon, 25 May 2026 10:42:56 +0700 Subject: [PATCH 6/8] fix: Broken lint --- lms/djangoapps/courseware/views/views.py | 2 +- lms/djangoapps/mfe_config_api/api.py | 2 +- lms/djangoapps/mfe_config_api/views.py | 1 + lms/envs/common.py | 8 ++++---- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 8501fd249008..3b93f49d2995 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -1761,7 +1761,7 @@ def _get_paragon_css_urls(): Calls the MFE Config API. If the API lives in the current server (relative path), calls the Python Api directly. If - the server lives in a remote server, makes a request to + the server lives in a remote server, makes a request to the API. """ diff --git a/lms/djangoapps/mfe_config_api/api.py b/lms/djangoapps/mfe_config_api/api.py index fa0c1fe88dd4..25680c538e54 100644 --- a/lms/djangoapps/mfe_config_api/api.py +++ b/lms/djangoapps/mfe_config_api/api.py @@ -13,6 +13,7 @@ "get_mfe_config", ] + def get_mfe_config(mfe=None): """ Return the merged MFE configuration for the given MFE app. @@ -37,7 +38,6 @@ def get_mfe_config(mfe=None): return legacy_config | mfe_config | mfe_config_overrides - def _get_legacy_config() -> dict: """ Return legacy configuration values available in either site configuration or django settings. diff --git a/lms/djangoapps/mfe_config_api/views.py b/lms/djangoapps/mfe_config_api/views.py index 624c7bcc13ff..df112fe244b6 100644 --- a/lms/djangoapps/mfe_config_api/views.py +++ b/lms/djangoapps/mfe_config_api/views.py @@ -12,6 +12,7 @@ from .api import get_mfe_config + class MFEConfigView(APIView): """ Provides an API endpoint to get the MFE configuration from settings (or site configuration). diff --git a/lms/envs/common.py b/lms/envs/common.py index be9c59da1a27..0989d573f96c 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1680,13 +1680,13 @@ }, 'style-main-v1': { 'source_filenames': [ - # 'css/lms-main-v1.css', + # 'css/lms-main-v1.css', ], 'output_filename': 'css/lms-main-v1.css', }, 'style-main-v1-rtl': { 'source_filenames': [ - # 'css/lms-main-v1-rtl.css', + # 'css/lms-main-v1-rtl.css', ], 'output_filename': 'css/lms-main-v1-rtl.css', }, @@ -1700,13 +1700,13 @@ }, 'style-course': { 'source_filenames': [ - # 'css/lms-course.css', + # 'css/lms-course.css', ], 'output_filename': 'css/lms-course.css', }, 'style-course-rtl': { 'source_filenames': [ - # 'css/lms-course-rtl.css', + # 'css/lms-course-rtl.css', ], 'output_filename': 'css/lms-course-rtl.css', }, From f671e7a511e8e64a311b8f2736d74705d284a283 Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Tue, 26 May 2026 10:32:46 +0700 Subject: [PATCH 7/8] fix: xss lint comments --- lms/templates/courseware/courseware-chromeless.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/templates/courseware/courseware-chromeless.html b/lms/templates/courseware/courseware-chromeless.html index c227265958d3..13311e02fe47 100644 --- a/lms/templates/courseware/courseware-chromeless.html +++ b/lms/templates/courseware/courseware-chromeless.html @@ -35,7 +35,7 @@ <%static:css group='style-course-vendor'/> % if paragon_css_urls: % for css_url in paragon_css_urls: - + % endfor ## LMS CSS used to hide dialogs marked as aria-hidden; Paragon doesn't, so we add it back explicitly. From 1d6cb2ff95405fc9542927d31c70aeff6713d27b Mon Sep 17 00:00:00 2001 From: XnpioChV Date: Tue, 26 May 2026 11:59:20 +0700 Subject: [PATCH 8/8] fix: Not related and strange lint error in settings --- openedx/envs/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openedx/envs/common.py b/openedx/envs/common.py index 33275894910d..47d24e866324 100644 --- a/openedx/envs/common.py +++ b/openedx/envs/common.py @@ -1577,7 +1577,7 @@ def _make_locale_paths(settings): SOCIAL_AUTH_SAML_SP_PUBLIC_CERT_DICT = {} # .. setting_name: SAML_METADATA_URL_ALLOW_PRIVATE_IPS -# .. setting_default: False +# .. setting_default: false # .. setting_description: When False (the default), fetching SAML metadata from # private IP address ranges (RFC 1918: 10.x, 172.16.x, 192.168.x) is blocked # as a defense against SSRF attacks. Set to True only in deployments where the