From db0b5adc691f3e05d0b1bec2dba939d79a335270 Mon Sep 17 00:00:00 2001 From: Ahtisham Shahid Date: Wed, 12 Feb 2025 11:33:25 +0500 Subject: [PATCH 1/7] fix: resolved issues with ses impl in goal reminder email (#36233) --- .../commands/goal_reminder_email.py | 14 +++-- .../tests/test_goal_reminder_email.py | 55 ++++++++++++++++++- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py b/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py index 944a18feff2e..b6840ca2c0b0 100644 --- a/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py +++ b/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py @@ -321,20 +321,21 @@ def send_email_using_ses(user, msg): """ Send email using AWS SES """ - msg = presentation.render(DjangoEmailChannel, msg) + render_msg = presentation.render(DjangoEmailChannel, msg) # send rendered email using SES + sender = EmailChannelMixin.get_from_address(msg) - recipient = user.email - subject = EmailChannelMixin.get_subject(msg) - body_text = msg.body - body_html = msg.body_html + + subject = EmailChannelMixin.get_subject(render_msg) + body_text = render_msg.body + body_html = render_msg.body_html try: # Send email response = boto3.client('ses', settings.AWS_SES_REGION_NAME).send_email( Source=sender, Destination={ - 'ToAddresses': [recipient], + 'ToAddresses': [user.email], }, Message={ 'Subject': { @@ -358,3 +359,4 @@ def send_email_using_ses(user, msg): send_ace_message_sent_signal(DjangoEmailChannel, msg) except Exception as e: # pylint: disable=broad-exception-caught log.error(f"Goal Reminder Email: Error sending email using SES: {e}") + raise e diff --git a/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py b/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py index 31804037919c..214fb58219ea 100644 --- a/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py +++ b/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py @@ -1,6 +1,10 @@ """Tests for the goal_reminder_email command""" from datetime import datetime + +from botocore.exceptions import NoCredentialsError +from django.contrib.sites.models import Site +from edx_ace import Recipient, Message from pytz import UTC from unittest import mock # lint-amnesty, pylint: disable=wrong-import-order @@ -13,14 +17,17 @@ from waffle import get_waffle_flag_model # pylint: disable=invalid-django-waffle-import from common.djangoapps.student.models import CourseEnrollment -from common.djangoapps.student.tests.factories import CourseEnrollmentFactory +from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory +from lms.djangoapps.course_goals.management.commands.goal_reminder_email import send_email_using_ses from lms.djangoapps.course_goals.models import CourseGoalReminderStatus from lms.djangoapps.course_goals.tests.factories import ( CourseGoalFactory, CourseGoalReminderStatusFactory, UserActivityFactory, ) from lms.djangoapps.certificates.data import CertificateStatuses from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory +from openedx.core.djangoapps.ace_common.template_context import get_base_template_context from openedx.core.djangolib.testing.utils import skip_unless_lms +from openedx.core.lib.celery.task_utils import emulate_http_request from openedx.features.course_experience import ENABLE_COURSE_GOALS, ENABLE_SES_FOR_GOALREMINDER # Some constants just for clarity of tests (assuming week starts on a Monday, as March 2021 used below does) @@ -43,6 +50,7 @@ class TestGoalReminderEmailCommand(TestCase): A lot of these methods will hardcode references to March 2021. This is just a convenient anchor point for us because it started on a Monday. Calls to the management command will freeze time so it's during March. """ + def make_valid_goal(self, **kwargs): """Creates a goal that will cause an email to be sent as the goal is valid but has been missed""" kwargs.setdefault('days_per_week', 6) @@ -211,3 +219,48 @@ def test_params_without_ses(self, mock_ace): assert msg.options['transactional'] is True assert 'override_default_channel' not in msg.options assert 'from_address' not in msg.options + + +class TestGoalReminderEmailSES(TestCase): + """ + Tests for the send_email_using_ses function + """ + def test_send_email_using_ses(self): + """ + Test that the send_email_using_ses function sends an email using the SES channel + """ + user = UserFactory() + + options = { + 'transactional': True, + 'from_address': settings.LMS_COMM_DEFAULT_FROM_EMAIL, + 'override_default_channel': 'django_email', + } + site = Site.objects.get_current() + message_context = get_base_template_context(site) + message_context.update({ + 'email': user.email, + 'platform_name': 'edx', + 'course_name': 'zombie survival', + 'course_id': 'course.101', + 'days_per_week': 3, + 'course_url': 'test.com', + 'goals_unsubscribe_url': 'test.com', + 'image_url': 'test', + 'unsubscribe_url': None, + 'omit_unsubscribe_link': True, + 'courses_url': 'course.example.com', + 'programs_url': 'course.example.com', + }) + with emulate_http_request(site, user): + msg = Message( + name="goalreminder", + app_label="course_goals", + recipient=Recipient(user.id, user.email), + language='en', + context=message_context, + options=options, + ) + # expect an exception here + with self.assertRaises(NoCredentialsError): + send_email_using_ses(user, msg) From 9ae7566b1c62bd6d54ee79862c4c61e7ae8a5b50 Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Wed, 12 Feb 2025 11:39:25 +0500 Subject: [PATCH 2/7] fix: studio edx.org-next theme Attaching jira ticket for reference: https://2u-internal.atlassian.net/browse/TNL-11731 - compile_sass script was generating the `studio-main-v1.scss` file but it wasn't correct and wasn't picking the changes from paragon. As a result, `edx.org-next` theme wasn't being applied to the studio. And the reason was this line: https://github.com/openedx/edx-platform/pull/35233/files#:~:text=//%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D-,%40import%20%27_builtin%2Dblock%2Dvariables%27%3B,-%40import%20%27bourbon. - This PR fixes the studio theme as I have rearranged `studio-main-v1.scss` file so that theme changes can be applied correctly. --- cms/static/sass/studio-main-v1.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/static/sass/studio-main-v1.scss b/cms/static/sass/studio-main-v1.scss index 5d0cdda2ea5f..a325010a7c61 100644 --- a/cms/static/sass/studio-main-v1.scss +++ b/cms/static/sass/studio-main-v1.scss @@ -16,7 +16,7 @@ // +Libs and Resets - *do not edit* // ==================== -@import '_builtin-block-variables'; @import 'bourbon/bourbon'; // lib - bourbon @import 'vendor/bi-app/bi-app-ltr'; // set the layout for left to right languages @import 'build-v1'; // shared app style assets/rendering +@import '_builtin-block-variables'; From a8ad9c8d1a7ea8e0a3f1650beee06c7c604a9bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Wed, 12 Feb 2025 14:41:46 -0300 Subject: [PATCH 3/7] fix: error when loading survey block without user in new runtime (#36226) * fix: always define a student_data_store to prevent errors on XBlock load * chore: bump xblock version to 5.1.2 --- openedx/core/djangoapps/xblock/runtime/runtime.py | 8 +++++--- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/openedx/core/djangoapps/xblock/runtime/runtime.py b/openedx/core/djangoapps/xblock/runtime/runtime.py index 2bf84e995417..8829cedac77b 100644 --- a/openedx/core/djangoapps/xblock/runtime/runtime.py +++ b/openedx/core/djangoapps/xblock/runtime/runtime.py @@ -18,7 +18,7 @@ from web_fragments.fragment import Fragment from xblock.core import XBlock from xblock.exceptions import NoSuchServiceError -from xblock.field_data import FieldData, SplitFieldData +from xblock.field_data import DictFieldData, FieldData, SplitFieldData from xblock.fields import Scope, ScopeIds from xblock.runtime import IdReader, KvsFieldData, MemoryIdManager, Runtime @@ -351,8 +351,10 @@ def _init_field_data_for_block(self, block: XBlock) -> FieldData: Initialize the FieldData implementation for the specified XBlock """ if self.user is None: - # No user is specified, so we want to throw an error if anything attempts to read/write user-specific fields - student_data_store = None + # No user is specified, so we want to ignore any user-specific data. We cannot throw an + # error here because the XBlock loading process will write to the user_state if we have + # mutable fields. + student_data_store = DictFieldData({}) elif self.user.is_anonymous: # This is an anonymous (non-registered) user: assert isinstance(self.user_id, str) and self.user_id.startswith("anon") diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index fafc6ff17c8d..32f3638340ff 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -1252,7 +1252,7 @@ wheel==0.45.1 # via django-pipeline wrapt==1.17.2 # via -r requirements/edx/kernel.in -xblock[django]==5.1.1 +xblock[django]==5.1.2 # via # -r requirements/edx/kernel.in # acid-xblock diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 318464d44812..93396ff1a38d 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -2239,7 +2239,7 @@ wrapt==1.17.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -xblock[django]==5.1.1 +xblock[django]==5.1.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index ddda43eb5b70..b67891daf4f1 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -1573,7 +1573,7 @@ wheel==0.45.1 # django-pipeline wrapt==1.17.2 # via -r requirements/edx/base.txt -xblock[django]==5.1.1 +xblock[django]==5.1.2 # via # -r requirements/edx/base.txt # acid-xblock diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 6a727650fe71..843e5b3757b7 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -1663,7 +1663,7 @@ wheel==0.45.1 # django-pipeline wrapt==1.17.2 # via -r requirements/edx/base.txt -xblock[django]==5.1.1 +xblock[django]==5.1.2 # via # -r requirements/edx/base.txt # acid-xblock From 368c980becad35975377b791418e8268c88bd8a8 Mon Sep 17 00:00:00 2001 From: katrinan029 <71999631+katrinan029@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:04:00 +0000 Subject: [PATCH 4/7] feat: Upgrade Python dependency edx-enterprise version bump Commit generated by workflow `openedx/edx-platform/.github/workflows/upgrade-one-python-dependency.yml@refs/heads/master` --- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 52198ab43149..3d9f7c08e88b 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -80,7 +80,7 @@ django-storages<1.14.4 # The team that owns this package will manually bump this package rather than having it pulled in automatically. # This is to allow them to better control its deployment and to do it in a process that works better # for them. -edx-enterprise==5.6.10 +edx-enterprise==5.6.11 # Date: 2024-05-09 # This has to be constrained as well because newer versions of edx-i18n-tools need the diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 32f3638340ff..f76379c69237 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -467,7 +467,7 @@ edx-drf-extensions==10.5.0 # edx-when # edxval # openedx-learning -edx-enterprise==5.6.10 +edx-enterprise==5.6.11 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 93396ff1a38d..2635c6c3c7fa 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -745,7 +745,7 @@ edx-drf-extensions==10.5.0 # edx-when # edxval # openedx-learning -edx-enterprise==5.6.10 +edx-enterprise==5.6.11 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index b67891daf4f1..cd68ea1c26bf 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -552,7 +552,7 @@ edx-drf-extensions==10.5.0 # edx-when # edxval # openedx-learning -edx-enterprise==5.6.10 +edx-enterprise==5.6.11 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 843e5b3757b7..21adf5f8bde0 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -574,7 +574,7 @@ edx-drf-extensions==10.5.0 # edx-when # edxval # openedx-learning -edx-enterprise==5.6.10 +edx-enterprise==5.6.11 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt From a8a8ae3286db2a76d36c7aa6743e1d5f96729c9f Mon Sep 17 00:00:00 2001 From: Irtaza Akram <51848298+irtazaakram@users.noreply.github.com> Date: Thu, 13 Feb 2025 17:43:07 +0500 Subject: [PATCH 5/7] fix: replace pkg_resources with importlib.resources (#36213) --- cms/djangoapps/contentstore/tasks.py | 4 ++-- cms/pytest.ini | 4 ---- common/djangoapps/edxmako/paths.py | 8 ++++--- common/test/pytest.ini | 4 ---- openedx/core/lib/logsettings.py | 10 -------- openedx/core/lib/xblock_pipeline/finder.py | 23 ++++++++++--------- .../test_external_xblocks.py | 4 ++-- scripts/xblock/list-installed.py | 9 ++++---- setup.cfg | 4 ---- xmodule/modulestore/django.py | 8 +++---- .../modulestore/tests/test_django_utils.py | 7 +++--- xmodule/x_module.py | 21 +++++++++-------- 12 files changed, 45 insertions(+), 61 deletions(-) diff --git a/cms/djangoapps/contentstore/tasks.py b/cms/djangoapps/contentstore/tasks.py index 88f3ad77f188..68e0c7827948 100644 --- a/cms/djangoapps/contentstore/tasks.py +++ b/cms/djangoapps/contentstore/tasks.py @@ -10,11 +10,11 @@ import shutil import tarfile from datetime import datetime, timezone +from importlib.metadata import entry_points from tempfile import NamedTemporaryFile, mkdtemp import aiohttp import olxcleaner -import pkg_resources from ccx_keys.locator import CCXLocator from celery import shared_task from celery.utils.log import get_task_logger @@ -95,7 +95,7 @@ FILE_READ_CHUNK = 1024 # bytes FULL_COURSE_REINDEX_THRESHOLD = 1 ALL_ALLOWED_XBLOCKS = frozenset( - [entry_point.name for entry_point in pkg_resources.iter_entry_points("xblock.v1")] + [entry_point.name for entry_point in entry_points(group="xblock.v1")] ) diff --git a/cms/pytest.ini b/cms/pytest.ini index c18bf1d66f31..ee9263d63c27 100644 --- a/cms/pytest.ini +++ b/cms/pytest.ini @@ -16,10 +16,6 @@ filterwarnings = ignore:.*You can remove default_app_config.*:PendingDeprecationWarning # ABC deprecation Warning comes from libsass ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated.*:DeprecationWarning:sass - # declare_namespace Warning comes from XBlock https://github.com/openedx/XBlock/issues/641 - # and also due to dependency: https://github.com/PyFilesystem/pyfilesystem2 - ignore:Deprecated call to `pkg_resources.declare_namespace.*:DeprecationWarning - ignore:.*pkg_resources is deprecated as an API.*:DeprecationWarning ignore:'etree' is deprecated. Use 'xml.etree.ElementTree' instead.:DeprecationWarning:wiki norecursedirs = envs diff --git a/common/djangoapps/edxmako/paths.py b/common/djangoapps/edxmako/paths.py index 8a1ab6ea75de..3da9844eedc2 100644 --- a/common/djangoapps/edxmako/paths.py +++ b/common/djangoapps/edxmako/paths.py @@ -4,8 +4,8 @@ import contextlib import hashlib import os +import importlib.resources as resources -import pkg_resources from django.conf import settings from mako.exceptions import TopLevelLookupException from mako.lookup import TemplateLookup @@ -122,7 +122,7 @@ def add_lookup(namespace, directory, package=None, prepend=False): """ Adds a new mako template lookup directory to the given namespace. - If `package` is specified, `pkg_resources` is used to look up the directory + If `package` is specified, `importlib.resources` is used to look up the directory inside the given package. Otherwise `directory` is assumed to be a path in the filesystem. """ @@ -136,7 +136,9 @@ def add_lookup(namespace, directory, package=None, prepend=False): encoding_errors='replace', ) if package: - directory = pkg_resources.resource_filename(package, directory) + with resources.as_file(resources.files(package.rsplit('.', 1)[0]) / directory) as dir_path: + directory = str(dir_path) + templates.add_directory(directory, prepend=prepend) diff --git a/common/test/pytest.ini b/common/test/pytest.ini index 5df48c9325ee..d93a7421722b 100644 --- a/common/test/pytest.ini +++ b/common/test/pytest.ini @@ -15,10 +15,6 @@ filterwarnings = ignore:.*You can remove default_app_config.*:PendingDeprecationWarning # ABC deprecation Warning comes from libsass ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated.*:DeprecationWarning:sass - # declare_namespace Warning comes from XBlock https://github.com/openedx/XBlock/issues/641 - # and also due to dependency: https://github.com/PyFilesystem/pyfilesystem2 - ignore:Deprecated call to `pkg_resources.declare_namespace.*:DeprecationWarning - ignore:.*pkg_resources is deprecated as an API.*:DeprecationWarning ignore:'etree' is deprecated. Use 'xml.etree.ElementTree' instead.:DeprecationWarning:wiki norecursedirs = .cache diff --git a/openedx/core/lib/logsettings.py b/openedx/core/lib/logsettings.py index f813be5c8fb7..ee78b26623ad 100644 --- a/openedx/core/lib/logsettings.py +++ b/openedx/core/lib/logsettings.py @@ -144,16 +144,6 @@ def log_python_warnings(): category=DeprecationWarning, module="sass", ) - warnings.filterwarnings( - 'ignore', - 'Deprecated call to `pkg_resources.declare_namespace.*', - category=DeprecationWarning, - ) - warnings.filterwarnings( - 'ignore', - '.*pkg_resources is deprecated as an API.*', - category=DeprecationWarning, - ) warnings.filterwarnings( 'ignore', "'etree' is deprecated. Use 'xml.etree.ElementTree' instead.", category=DeprecationWarning, module='wiki' diff --git a/openedx/core/lib/xblock_pipeline/finder.py b/openedx/core/lib/xblock_pipeline/finder.py index da5f4e53303b..4893e5e0480d 100644 --- a/openedx/core/lib/xblock_pipeline/finder.py +++ b/openedx/core/lib/xblock_pipeline/finder.py @@ -4,13 +4,13 @@ import os from datetime import datetime +import importlib.resources as resources from django.contrib.staticfiles import utils from django.contrib.staticfiles.finders import BaseFinder from django.contrib.staticfiles.storage import FileSystemStorage from django.core.files.storage import Storage from django.utils import timezone -from pkg_resources import resource_exists, resource_filename, resource_isdir, resource_listdir from xblock.core import XBlock from openedx.core.lib.xblock_utils import xblock_resource_pkg @@ -38,7 +38,8 @@ def path(self, name): """ Returns a file system filename for the specified file name. """ - return resource_filename(self.module, os.path.join(self.base_dir, name)) + with resources.as_file(resources.files(self.module.rsplit('.', 1)[0]) / self.base_dir / name) as file_path: + return str(file_path) def exists(self, path): # lint-amnesty, pylint: disable=arguments-differ """ @@ -46,8 +47,7 @@ def exists(self, path): # lint-amnesty, pylint: disable=arguments-differ """ if self.base_dir is None: return False - - return resource_exists(self.module, os.path.join(self.base_dir, path)) + return (resources.files(self.module.rsplit('.', 1)[0]) / self.base_dir / path).exists() def listdir(self, path): """ @@ -55,13 +55,14 @@ def listdir(self, path): """ directories = [] files = [] - for item in resource_listdir(self.module, os.path.join(self.base_dir, path)): - __, file_extension = os.path.splitext(item) - if file_extension not in [".py", ".pyc", ".scss"]: - if resource_isdir(self.module, os.path.join(self.base_dir, path, item)): - directories.append(item) - else: - files.append(item) + base_path = resources.files(self.module.rsplit('.', 1)[0]) / self.base_dir / path + if base_path.is_dir(): + for item in base_path.iterdir(): + if item.suffix not in [".py", ".pyc", ".scss"]: + if item.is_dir(): + directories.append(item.name) + else: + files.append(item.name) return directories, files def open(self, name, mode='rb'): diff --git a/openedx/tests/xblock_integration/test_external_xblocks.py b/openedx/tests/xblock_integration/test_external_xblocks.py index c4fc4b74e397..01bdf2133cce 100644 --- a/openedx/tests/xblock_integration/test_external_xblocks.py +++ b/openedx/tests/xblock_integration/test_external_xblocks.py @@ -9,7 +9,7 @@ """ -import pkg_resources +from importlib.metadata import entry_points class DuplicateXBlockTest(Exception): @@ -37,7 +37,7 @@ class InvalidTestName(Exception): xblock_loaded = False # pylint: disable=invalid-name -for entrypoint in pkg_resources.iter_entry_points(group="xblock.test.v0"): +for entrypoint in entry_points(group="xblock.test.v0"): plugin = entrypoint.load() classname = plugin.__name__ if classname in globals(): diff --git a/scripts/xblock/list-installed.py b/scripts/xblock/list-installed.py index c1152d749d58..fb19bf6db922 100644 --- a/scripts/xblock/list-installed.py +++ b/scripts/xblock/list-installed.py @@ -1,7 +1,7 @@ """ Lookup list of installed XBlocks, to aid XBlock developers """ -import pkg_resources +from importlib.metadata import entry_points def get_without_builtins(): @@ -12,11 +12,10 @@ def get_without_builtins(): """ xblocks = [ entry_point.name - for entry_point in pkg_resources.iter_entry_points('xblock.v1') - if not entry_point.module_name.startswith('xmodule') + for entry_point in entry_points(group='xblock.v1') + if not entry_point.value.startswith('xmodule') ] - xblocks = sorted(xblocks) - return xblocks + return sorted(xblocks) def main(): diff --git a/setup.cfg b/setup.cfg index 987f4474531f..bf789997c030 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,10 +17,6 @@ filterwarnings = ignore:Instead access HTTPResponse.headers directly.*:DeprecationWarning:elasticsearch # ABC deprecation Warning comes from libsass ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated.*:DeprecationWarning:sass - # declare_namespace Warning comes from XBlock https://github.com/openedx/XBlock/issues/641 - # and also due to dependency: https://github.com/PyFilesystem/pyfilesystem2 - ignore:Deprecated call to `pkg_resources.declare_namespace.*:DeprecationWarning - ignore:.*pkg_resources is deprecated as an API.*:DeprecationWarning ignore:'etree' is deprecated. Use 'xml.etree.ElementTree' instead.:DeprecationWarning:wiki junit_family = xunit2 diff --git a/xmodule/modulestore/django.py b/xmodule/modulestore/django.py index f36c0c35a90d..c15f03a4e33d 100644 --- a/xmodule/modulestore/django.py +++ b/xmodule/modulestore/django.py @@ -6,10 +6,10 @@ from contextlib import contextmanager from importlib import import_module +import importlib.resources as resources import gettext import logging -from pkg_resources import resource_filename import re # lint-amnesty, pylint: disable=wrong-import-order from django.conf import settings @@ -422,9 +422,9 @@ def get_python_locale(self, block): return 'django', xblock_locale_path # Pre-OEP-58 translations within the XBlock pip packages are deprecated but supported. - deprecated_xblock_locale_path = resource_filename(xblock_module_name, 'translations') - # The `text` domain was used for XBlocks pre-OEP-58. - return 'text', deprecated_xblock_locale_path + with resources.as_file(resources.files(xblock_module_name) / 'translations') as deprecated_xblock_locale_path: + # The `text` domain was used for XBlocks pre-OEP-58. + return 'text', str(deprecated_xblock_locale_path) def get_javascript_i18n_catalog_url(self, block): """ diff --git a/xmodule/modulestore/tests/test_django_utils.py b/xmodule/modulestore/tests/test_django_utils.py index 86c3a08df503..f8fba427b4ee 100644 --- a/xmodule/modulestore/tests/test_django_utils.py +++ b/xmodule/modulestore/tests/test_django_utils.py @@ -2,6 +2,7 @@ Tests for the modulestore.django module """ +from pathlib import Path from unittest.mock import patch import django.utils.translation @@ -23,19 +24,19 @@ def test_get_python_locale_with_atlas_oep58_translations(mock_modern_xblock): assert domain == 'django', 'Uses django domain when atlas locale is found.' -@patch('xmodule.modulestore.django.resource_filename', return_value='/lib/my_legacy_xblock/translations') +@patch('importlib.resources.files', return_value=Path('/lib/my_legacy_xblock')) def test_get_python_locale_with_bundled_translations(mock_modern_xblock): """ Ensure that get_python_locale() falls back to XBlock internal translations if atlas translations weren't pulled. Pre-OEP-58 translations were stored in the `translations` directory of the XBlock which is - accessible via the `pkg_resources.resource_filename` function. + accessible via the `importlib.resources.files` function. """ i18n_service = XBlockI18nService() block = mock_modern_xblock['legacy_xblock'] domain, path = i18n_service.get_python_locale(block) - assert path == '/lib/my_legacy_xblock/translations', 'Backward compatible with pe-OEP-58.' + assert path == '/lib/my_legacy_xblock/translations', 'Backward compatible with pre-OEP-58.' assert domain == 'text', 'Use the legacy `text` domain for backward compatibility with old XBlocks.' diff --git a/xmodule/x_module.py b/xmodule/x_module.py index d25012275a5e..d1d23342b7a9 100644 --- a/xmodule/x_module.py +++ b/xmodule/x_module.py @@ -1,5 +1,6 @@ # lint-amnesty, pylint: disable=missing-module-docstring +import importlib.resources as resources import logging import os import time @@ -13,7 +14,6 @@ from lxml import etree from opaque_keys.edx.asides import AsideDefinitionKeyV2, AsideUsageKeyV2 from opaque_keys.edx.keys import UsageKey -from pkg_resources import resource_isdir, resource_filename from web_fragments.fragment import Fragment from webob import Response from webob.multidict import MultiDict @@ -856,16 +856,16 @@ def templates(cls): def get_template_dir(cls): # lint-amnesty, pylint: disable=missing-function-docstring if getattr(cls, 'template_dir_name', None): dirname = os.path.join('templates', cls.template_dir_name) # lint-amnesty, pylint: disable=no-member - if not resource_isdir(__name__, dirname): + template_path = resources.files(__name__.rsplit('.', 1)[0]) / dirname + + if not template_path.is_dir(): log.warning("No resource directory {dir} found when loading {cls_name} templates".format( dir=dirname, cls_name=cls.__name__, )) - return None - else: - return dirname - else: - return None + return + return dirname + return @classmethod def get_template_dirpaths(cls): @@ -874,8 +874,11 @@ def get_template_dirpaths(cls): """ template_dirpaths = [] template_dirname = cls.get_template_dir() - if template_dirname and resource_isdir(__name__, template_dirname): - template_dirpaths.append(resource_filename(__name__, template_dirname)) + if template_dirname: + template_path = resources.files(__name__.rsplit('.', 1)[0]) / template_dirname + if template_path.is_dir(): + with resources.as_file(template_path) as template_real_path: + template_dirpaths.append(str(template_real_path)) custom_template_dir = cls.get_custom_template_dir() if custom_template_dir: From c9dfb6edebba7133d2227f33d42eef7a6cbbb7e4 Mon Sep 17 00:00:00 2001 From: Ahtisham Shahid Date: Thu, 13 Feb 2025 19:49:44 +0500 Subject: [PATCH 6/7] chore: added debug logs in instructor enrolment email (#36250) --- lms/djangoapps/instructor/enrollment.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/instructor/enrollment.py b/lms/djangoapps/instructor/enrollment.py index ed344876eb42..4f9247ab91cb 100644 --- a/lms/djangoapps/instructor/enrollment.py +++ b/lms/djangoapps/instructor/enrollment.py @@ -16,7 +16,8 @@ from django.template.loader import render_to_string from django.urls import reverse from django.utils.translation import override as override_language -from edx_ace import ace +from edx_ace import ace, presentation +from edx_ace.channel.django_email import DjangoEmailChannel from edx_ace.recipient import Recipient from eventtracking import tracker from submissions import api as sub_api # installed from the edx-submissions repository @@ -591,6 +592,19 @@ def send_mail_to_student(student, param_dict, language=None): user_context=param_dict, ) + render_msg = presentation.render(DjangoEmailChannel, message) + if not render_msg.body_html.count(student): + log.error( + { + 'message': 'Email template does not contain required email address', + 'email_address': student, + 'message_type': message_type, + 'render_msg': render_msg.body, + 'count': render_msg.body_html.count(student), + 'lms_user_id': lms_user_id, + **param_dict + } + ) ace.send(message) From 0bede61246223511d2907aa4c9f4292aab996b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Ch=C3=A1vez?= Date: Thu, 13 Feb 2025 11:21:32 -0500 Subject: [PATCH 7/7] fix: Advanced editor styles [FC-0076] (#36221) * Adds padding to the text of the competition block editor. * Fix the style of the Save and Cancel buttons of the PDF block editor. * Moves the Save and Cancel buttons to the bottom of the modal. * Moves the Save and Cancel button to the right. * Which edX user roles will this change impact? "Course Author" --- .../sass/course-unit-mfe-iframe-bundle.scss | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/cms/static/sass/course-unit-mfe-iframe-bundle.scss b/cms/static/sass/course-unit-mfe-iframe-bundle.scss index 1f813fac253d..c9b111b91283 100644 --- a/cms/static/sass/course-unit-mfe-iframe-bundle.scss +++ b/cms/static/sass/course-unit-mfe-iframe-bundle.scss @@ -316,6 +316,7 @@ body { .openassessment_save_button, .save-button, + .action-save, .continue-button { color: $white; background-color: $primary; @@ -347,6 +348,7 @@ body { } .openassessment_cancel_button, + .action-cancel, .cancel-button { color: $text-color; background-color: $transparent; @@ -384,6 +386,8 @@ body { // Additions for the xblock editor on the Library Authoring &.xblock-iframe-content { + height: 100%; + // Reset the max-height to allow the settings list to grow .wrapper-comp-settings .list-input.settings-list { max-height: unset; @@ -411,6 +415,31 @@ body { } } } + + .xblock-v1-studio_view { + height: 100%; + + .editor-with-buttons { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + + .list-input { + height: 90vh; + } + } + + &.xmodule_DoneXBlock { + margin-top: 60px; + padding: 0 20px; + } + + .xblock-actions { + display: flex; + justify-content: flex-end; + } + } } }