From 6725fa607a1c5507b7fc94675a0f46a13d01289d Mon Sep 17 00:00:00 2001 From: Taras Lytvynenko <69678257+Inferato@users.noreply.github.com> Date: Fri, 10 Nov 2023 01:04:49 +0200 Subject: [PATCH 01/18] fix: course mode added to the metadata --- .../course_home_api/course_metadata/serializers.py | 9 +++++++++ lms/djangoapps/course_home_api/course_metadata/views.py | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/lms/djangoapps/course_home_api/course_metadata/serializers.py b/lms/djangoapps/course_home_api/course_metadata/serializers.py index b140938f7e2d..2bafd7427a2e 100644 --- a/lms/djangoapps/course_home_api/course_metadata/serializers.py +++ b/lms/djangoapps/course_home_api/course_metadata/serializers.py @@ -29,6 +29,14 @@ def get_url(self, tab): return request.build_absolute_uri(tab.link_func(self.context.get('course'), reverse)) +class CourseModeSerrializer(serializers.Serializer): + """ + Serializer for the Course Mode + """ + slug = serializers.CharField() + name = serializers.CharField() + + class CourseHomeMetadataSerializer(VerifiedModeSerializer): """ Serializer for the Course Home Course Metadata @@ -48,3 +56,4 @@ class CourseHomeMetadataSerializer(VerifiedModeSerializer): username = serializers.CharField() user_timezone = serializers.CharField() can_view_certificate = serializers.BooleanField() + course_modes = CourseModeSerrializer(many=True) diff --git a/lms/djangoapps/course_home_api/course_metadata/views.py b/lms/djangoapps/course_home_api/course_metadata/views.py index ba789ea25458..e42c3ea3b71d 100644 --- a/lms/djangoapps/course_home_api/course_metadata/views.py +++ b/lms/djangoapps/course_home_api/course_metadata/views.py @@ -14,6 +14,7 @@ from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser from openedx.core.djangoapps.courseware_api.utils import get_celebrations_dict +from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.models import CourseEnrollment from lms.djangoapps.course_api.api import course_detail from lms.djangoapps.course_goals.models import UserActivity @@ -117,6 +118,8 @@ def get(self, request, *args, **kwargs): # Record course goals user activity for (web) learning mfe course tabs UserActivity.record_user_activity(request.user, course_key) + course_modes = CourseMode.modes_for_course(course_key, include_expired=True, only_selectable=False) + data = { 'course_id': course.id, 'username': username, @@ -133,6 +136,7 @@ def get(self, request, *args, **kwargs): 'celebrations': celebrations, 'user_timezone': user_timezone, 'can_view_certificate': certificates_viewable_for_course(course), + 'course_modes': course_modes, } context = self.get_serializer_context() context['course'] = course From 39e6faa5f83d49e4750000cec1809d62e1e591e2 Mon Sep 17 00:00:00 2001 From: Kaustav Banerjee Date: Tue, 6 Feb 2024 04:29:38 +0530 Subject: [PATCH 02/18] fix: add missing function import in certificate template (#33904) (#34170) * fix: add missing function import in certificate template * test: add test case to check certificates generated when GA4 is enabled (cherry picked from commit d0a49d1a01bda06722139513919b1aafcf739fa9) --- lms/djangoapps/certificates/tests/test_views.py | 16 ++++++++++++++++ .../certificates/accomplishment-base.html | 6 ++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/certificates/tests/test_views.py b/lms/djangoapps/certificates/tests/test_views.py index 32db30d12f61..520baa2a8fb2 100644 --- a/lms/djangoapps/certificates/tests/test_views.py +++ b/lms/djangoapps/certificates/tests/test_views.py @@ -165,3 +165,19 @@ def test_html_view_site_configuration_missing(self): response, 'This should not survive being overwritten by static content', ) + + @override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED, GOOGLE_ANALYTICS_4_ID='GA-abc') + @with_site_configuration(configuration={'platform_name': 'My Platform Site'}) + def test_html_view_with_g4(self): + test_url = get_certificate_url( + user_id=self.user.id, + course_id=str(self.course.id), + uuid=self.cert.verify_uuid + ) + self._add_course_certificates(count=1, signatory_count=2) + response = self.client.get(test_url) + self.assertContains( + response, + 'awarded this My Platform Site Honor Code Certificate of Completion', + ) + self.assertContains(response, 'googletagmanager') diff --git a/lms/templates/certificates/accomplishment-base.html b/lms/templates/certificates/accomplishment-base.html index e48cd5035407..c44baff1c2ae 100644 --- a/lms/templates/certificates/accomplishment-base.html +++ b/lms/templates/certificates/accomplishment-base.html @@ -1,7 +1,9 @@ <%page expression_filter="h"/> <%namespace name='static' file='/static_content.html'/> -<%! from django.utils.translation import gettext as _%> - +<%! +from django.utils.translation import gettext as _ +from openedx.core.djangolib.js_utils import js_escaped_string +%> <% # set doc language direction from django.utils.translation import get_language_bidi From 8290087f6e7713d83a465b5ce69b50f4586474d0 Mon Sep 17 00:00:00 2001 From: Paulo Viadanna Date: Mon, 5 Feb 2024 20:55:41 -0300 Subject: [PATCH 03/18] fix: youtube race condition when GTM loads (#33650) --- xmodule/js/src/video/01_initialize.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/xmodule/js/src/video/01_initialize.js b/xmodule/js/src/video/01_initialize.js index a3a4108e39f0..1aaddb552dfb 100644 --- a/xmodule/js/src/video/01_initialize.js +++ b/xmodule/js/src/video/01_initialize.js @@ -169,10 +169,9 @@ _oldOnYouTubeIframeAPIReady = window.onYouTubeIframeAPIReady || undefined; window.onYouTubeIframeAPIReady = function() { - window.onYouTubeIframeAPIReady.resolve(); + _youtubeApiDeferred.resolve(); }; - window.onYouTubeIframeAPIReady.resolve = _youtubeApiDeferred.resolve; window.onYouTubeIframeAPIReady.done = _youtubeApiDeferred.done; if (_oldOnYouTubeIframeAPIReady) { From 6a166c570635eeed4427ca9c68f38bf508a8dae1 Mon Sep 17 00:00:00 2001 From: magajh Date: Tue, 6 Feb 2024 13:23:45 -0400 Subject: [PATCH 04/18] chore: upgrade Django to 4.2.10 --- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 52d8b2224607..cfaecd0ca35a 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -181,7 +181,7 @@ defusedxml==0.7.1 # social-auth-core deprecated==1.2.14 # via jwcrypto -django==4.2.8 +django==4.2.10 # via # -r requirements/edx/kernel.in # django-appconf diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 1827527d0e5c..87c17c1e74ae 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -343,7 +343,7 @@ distlib==0.3.7 # via # -r requirements/edx/testing.txt # virtualenv -django==4.2.8 +django==4.2.10 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 603915ecf4ab..9ec73dc14a36 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -230,7 +230,7 @@ deprecated==1.2.14 # via # -r requirements/edx/base.txt # jwcrypto -django==4.2.8 +django==4.2.10 # via # -r requirements/edx/base.txt # django-appconf diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 6640fb31d5b2..a4b87c9178e4 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -263,7 +263,7 @@ dill==0.3.7 # via pylint distlib==0.3.7 # via virtualenv -django==4.2.8 +django==4.2.10 # via # -r requirements/edx/base.txt # django-appconf From ba4bcf115a9e1da3d03ee830b9bf2ca56532fa66 Mon Sep 17 00:00:00 2001 From: Dmytro <98233552+DmytroAlipov@users.noreply.github.com> Date: Wed, 7 Feb 2024 14:47:18 +0100 Subject: [PATCH 05/18] fix: ORA response with attached file (#33676) The details are described in this discussion: https://discuss.openedx.org/t/ora-grading-returns-error/11482 --- .../ora_staff_grader/serializers.py | 10 +++- .../tests/test_serializers.py | 47 +++++++++++++++---- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/lms/djangoapps/ora_staff_grader/serializers.py b/lms/djangoapps/ora_staff_grader/serializers.py index 643a4725e1a0..d3ffb46f1933 100644 --- a/lms/djangoapps/ora_staff_grader/serializers.py +++ b/lms/djangoapps/ora_staff_grader/serializers.py @@ -4,6 +4,8 @@ # pylint: disable=abstract-method # pylint: disable=missing-function-docstring +from urllib.parse import urljoin +from django.conf import settings from rest_framework import serializers from openedx.core.djangoapps.content.course_overviews.models import CourseOverview @@ -191,11 +193,17 @@ def get_isEnabled(self, obj): class UploadedFileSerializer(serializers.Serializer): """Serializer for a file uploaded as a part of a response""" - downloadUrl = serializers.URLField(source="download_url") + downloadUrl = serializers.SerializerMethodField(method_name="get_download_url") description = serializers.CharField() name = serializers.CharField() size = serializers.IntegerField() + def get_download_url(self, obj): + """ + Get the representation for SerializerMethodField `downloadUrl` + """ + return urljoin(settings.LMS_ROOT_URL, obj.get("download_url")) + class ResponseSerializer(serializers.Serializer): """Serializer for the responseData api construct, which represents the contents of a submitted learner response""" diff --git a/lms/djangoapps/ora_staff_grader/tests/test_serializers.py b/lms/djangoapps/ora_staff_grader/tests/test_serializers.py index 19939c0f9b85..d105cd649e89 100644 --- a/lms/djangoapps/ora_staff_grader/tests/test_serializers.py +++ b/lms/djangoapps/ora_staff_grader/tests/test_serializers.py @@ -2,8 +2,10 @@ Tests for ESG Serializers """ from unittest.mock import Mock, MagicMock, patch +from urllib.parse import urljoin import ddt +from django.conf import settings from django.test import TestCase from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase @@ -457,14 +459,37 @@ class TestUploadedFileSerializer(TestCase): def test_uploaded_file_serializer(self): """Base serialization behavior""" - input_data = MagicMock(size=89794) + input_data = { + "download_url": "/test.txt", + "description": "Test description", + "name": "Test name", + "size": 89111, + } data = UploadedFileSerializer(input_data).data expected_value = { - "downloadUrl": str(input_data.download_url), - "description": str(input_data.description), - "name": str(input_data.name), - "size": input_data.size, + "downloadUrl": f'{settings.LMS_ROOT_URL}{input_data["download_url"]}', + "description": input_data["description"], + "name": input_data["name"], + "size": input_data["size"], + } + assert data == expected_value + + def test_uploaded_file_serializer_with_full_url(self): + """Test UploadedFileSerializer with a full download URL""" + input_data = { + "download_url": f"{settings.LMS_ROOT_URL}/test.txt", + "description": "Test description", + "name": "Test name", + "size": 78222, + } + data = UploadedFileSerializer(input_data).data + + expected_value = { + "downloadUrl": input_data["download_url"], + "description": input_data["description"], + "name": input_data["name"], + "size": input_data["size"], } assert data == expected_value @@ -484,7 +509,11 @@ def test_response_serializer(self, has_text, has_files): """Base serialization behavior""" input_data = MagicMock() if has_files: - input_data.files = [Mock(size=111), Mock(size=222), Mock(size=333)] + input_data.files = [ + {"size": 111, "download_url": "/file1.txt", "description": Mock(), "name": Mock()}, + {"size": 222, "download_url": "/file2.txt", "description": Mock(), "name": Mock()}, + {"size": 333, "download_url": "/file3.txt", "description": Mock(), "name": Mock()}, + ] if has_text: input_data.text = [Mock(), Mock(), Mock()] @@ -520,12 +549,12 @@ def test_file_list_serializer(self): "files": [{ "name": Mock(), "description": Mock(), - "download_url": Mock(), + "download_url": f"{settings.LMS_ROOT_URL}/test-1.png", "size": 12345, }, { "name": Mock(), "description": Mock(), - "download_url": Mock(), + "download_url": "/test-2.png", "size": 54321, }], "text": "", @@ -540,7 +569,7 @@ def test_file_list_serializer(self): assert output_file["name"] == str(input_file["name"]) assert output_file["description"] == str(input_file["description"]) - assert output_file["downloadUrl"] == str(input_file["download_url"]) + assert output_file["downloadUrl"] == urljoin(settings.LMS_ROOT_URL, str(input_file["download_url"])) assert output_file["size"] == input_file["size"] From a0c97d6df887a252622eb604ce73d058031123e8 Mon Sep 17 00:00:00 2001 From: bogdan3d Date: Wed, 7 Feb 2024 22:45:22 -0500 Subject: [PATCH 06/18] fix: fixing the prompt view cancel button style for color and cursor shape backport to quince --- cms/static/sass/elements/_system-feedback.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cms/static/sass/elements/_system-feedback.scss b/cms/static/sass/elements/_system-feedback.scss index f3cfed83ef94..9f74e625fb17 100644 --- a/cms/static/sass/elements/_system-feedback.scss +++ b/cms/static/sass/elements/_system-feedback.scss @@ -325,6 +325,12 @@ .action-secondary { @extend %t-action4; + cursor: pointer; + color: $white; + + &:hover { + color: $gray-l3; + } } } } From 8bcfb1d69a52130ac11aa5d3404fbfac4c8e72f1 Mon Sep 17 00:00:00 2001 From: IrfanUddinAhmad Date: Mon, 11 Mar 2024 14:53:37 +0500 Subject: [PATCH 07/18] fix: removed waffle switch around oep-50 filter --- xmodule/tests/test_vertical.py | 6 ---- xmodule/vertical_block.py | 50 ++++++++++++---------------------- 2 files changed, 18 insertions(+), 38 deletions(-) diff --git a/xmodule/tests/test_vertical.py b/xmodule/tests/test_vertical.py index 223cf3889cc7..a3c596d253ac 100644 --- a/xmodule/tests/test_vertical.py +++ b/xmodule/tests/test_vertical.py @@ -14,12 +14,10 @@ import pytz from django.contrib.auth.models import AnonymousUser from django.test import override_settings -from edx_toggles.toggles.testutils import override_waffle_switch from fs.memoryfs import MemoryFS from openedx_filters import PipelineStep from openedx_filters.learning.filters import VerticalBlockChildRenderStarted, VerticalBlockRenderCompleted -from ..vertical_block import XBLOCK_SKILL_TAG_VERIFICATION_SWITCH from ..x_module import AUTHOR_VIEW, PUBLIC_VIEW, STUDENT_VIEW from . import prepare_block_runtime from .helpers import StubUserService @@ -362,7 +360,6 @@ def test_render_studio_view(self): }, }, ) - @override_waffle_switch(XBLOCK_SKILL_TAG_VERIFICATION_SWITCH, True) def test_vertical_block_child_render_started_filter_execution(self): """ Test the VerticalBlockChildRenderStarted filter's effects on student view. @@ -385,7 +382,6 @@ def test_vertical_block_child_render_started_filter_execution(self): }, }, ) - @override_waffle_switch(XBLOCK_SKILL_TAG_VERIFICATION_SWITCH, True) def test_vertical_block_child_render_is_skipped_on_filter_exception(self): """ Test VerticalBlockChildRenderStarted filter can be used to skip child blocks. @@ -409,7 +405,6 @@ def test_vertical_block_child_render_is_skipped_on_filter_exception(self): }, }, ) - @override_waffle_switch(XBLOCK_SKILL_TAG_VERIFICATION_SWITCH, True) def test_vertical_block_render_completed_filter_execution(self): """ Test the VerticalBlockRenderCompleted filter's execution. @@ -432,7 +427,6 @@ def test_vertical_block_render_completed_filter_execution(self): }, }, ) - @override_waffle_switch(XBLOCK_SKILL_TAG_VERIFICATION_SWITCH, True) def test_vertical_block_render_output_is_changed_on_filter_exception(self): """ Test VerticalBlockRenderCompleted filter can be used to prevent vertical block from rendering. diff --git a/xmodule/vertical_block.py b/xmodule/vertical_block.py index 10ff0a540870..2a10ae44497f 100644 --- a/xmodule/vertical_block.py +++ b/xmodule/vertical_block.py @@ -9,7 +9,6 @@ from functools import reduce import pytz -from edx_toggles.toggles import WaffleSwitch from lxml import etree from openedx_filters.learning.filters import VerticalBlockChildRenderStarted, VerticalBlockRenderCompleted from web_fragments.fragment import Fragment @@ -34,17 +33,6 @@ # HACK: This shouldn't be hard-coded to two types # OBSOLETE: This obsoletes 'type' CLASS_PRIORITY = ['video', 'problem'] -WAFFLE_NAMESPACE = 'xblocks' -# .. toggle_name: xblocks.xblock_skill_tag_verification' -# .. toggle_implementation: WaffleSwitch -# .. toggle_default: False -# .. toggle_description: Set to True to get learner verification of the skills associated with the xblock. -# .. toggle_use_cases: open_edx -# .. toggle_creation_date: 2023-10-04 -# .. toggle_target_removal_date: None -XBLOCK_SKILL_TAG_VERIFICATION_SWITCH = WaffleSwitch( # lint-amnesty, pylint: disable=toggle-missing-annotation - f'{WAFFLE_NAMESPACE}.xblock_skill_tag_verification', __name__ -) class VerticalFields: @@ -129,16 +117,15 @@ def _student_or_public_view(self, context, view): # lint-amnesty, pylint: disab child_block_context['wrap_xblock_data'] = { 'mark-completed-on-view-after-delay': complete_on_view_delay } - if XBLOCK_SKILL_TAG_VERIFICATION_SWITCH.is_enabled(): - try: - # .. filter_implemented_name: VerticalBlockChildRenderStarted - # .. filter_type: org.openedx.learning.vertical_block_child.render.started.v1 - child, child_block_context = VerticalBlockChildRenderStarted.run_filter( - block=child, context=child_block_context - ) - except VerticalBlockChildRenderStarted.PreventChildBlockRender as exc: - log.info("Skipping %s from vertical block. Reason: %s", child, exc.message) - continue + try: + # .. filter_implemented_name: VerticalBlockChildRenderStarted + # .. filter_type: org.openedx.learning.vertical_block_child.render.started.v1 + child, child_block_context = VerticalBlockChildRenderStarted.run_filter( + block=child, context=child_block_context + ) + except VerticalBlockChildRenderStarted.PreventChildBlockRender as exc: + log.info("Skipping %s from vertical block. Reason: %s", child, exc.message) + continue rendered_child = child.render(view, child_block_context) fragment.add_fragment_resources(rendered_child) @@ -180,16 +167,15 @@ def _student_or_public_view(self, context, view): # lint-amnesty, pylint: disab add_webpack_js_to_fragment(fragment, 'VerticalStudentView') fragment.initialize_js('VerticalStudentView') - if XBLOCK_SKILL_TAG_VERIFICATION_SWITCH.is_enabled(): - try: - # .. filter_implemented_name: VerticalBlockRenderCompleted - # .. filter_type: org.openedx.learning.vertical_block.render.completed.v1 - _, fragment, context, view = VerticalBlockRenderCompleted.run_filter( - block=self, fragment=fragment, context=context, view=view - ) - except VerticalBlockRenderCompleted.PreventVerticalBlockRender as exc: - log.info("VerticalBlock rendering stopped. Reason: %s", exc.message) - fragment.content = exc.message + try: + # .. filter_implemented_name: VerticalBlockRenderCompleted + # .. filter_type: org.openedx.learning.vertical_block.render.completed.v1 + _, fragment, context, view = VerticalBlockRenderCompleted.run_filter( + block=self, fragment=fragment, context=context, view=view + ) + except VerticalBlockRenderCompleted.PreventVerticalBlockRender as exc: + log.info("VerticalBlock rendering stopped. Reason: %s", exc.message) + fragment.content = exc.message return fragment From 3da1cdf01c5ee922f74a59382a30edfc36828f0b Mon Sep 17 00:00:00 2001 From: Braden MacDonald Date: Tue, 19 Mar 2024 22:20:14 -0700 Subject: [PATCH 08/18] fix: handle paste of library content blocks correctly (#34274) (backport) --- cms/djangoapps/contentstore/helpers.py | 11 ++- .../views/tests/test_clipboard_paste.py | 86 ++++++++++++++++++- .../core/djangoapps/content_libraries/api.py | 12 ++- 3 files changed, 103 insertions(+), 6 deletions(-) diff --git a/cms/djangoapps/contentstore/helpers.py b/cms/djangoapps/contentstore/helpers.py index 0d66070b1120..2bfdd6574aad 100644 --- a/cms/djangoapps/contentstore/helpers.py +++ b/cms/djangoapps/contentstore/helpers.py @@ -17,6 +17,7 @@ from xmodule.contentstore.content import StaticContent from xmodule.contentstore.django import contentstore from xmodule.exceptions import NotFoundError +from xmodule.library_content_block import LibraryContentBlock from xmodule.modulestore.django import modulestore from xmodule.xml_block import XmlMixin @@ -336,8 +337,14 @@ def _import_xml_node_to_parent( new_xblock = store.update_item(temp_xblock, user_id, allow_not_found=True) parent_xblock.children.append(new_xblock.location) store.update_item(parent_xblock, user_id) - for child_node in child_nodes: - _import_xml_node_to_parent(child_node, new_xblock, store, user_id=user_id) + if isinstance(new_xblock, LibraryContentBlock): + # Special case handling for library content. If we need this for other blocks in the future, it can be made into + # an API, and we'd call new_block.studio_post_paste() instead of this code. + # In this case, we want to pull the children from the library and let library_tools assign their IDs. + new_xblock.tools.update_children(new_xblock, version=new_xblock.source_library_version) + else: + for child_node in child_nodes: + _import_xml_node_to_parent(child_node, new_xblock, store, user_id=user_id) return new_xblock diff --git a/cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py b/cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py index a447799c2950..bdf22532fd5f 100644 --- a/cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py +++ b/cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py @@ -4,11 +4,15 @@ APIs. """ import ddt +from django.test import LiveServerTestCase from opaque_keys.edx.keys import UsageKey from rest_framework.test import APIClient -from xmodule.modulestore.django import contentstore +from xmodule.modulestore.django import contentstore, modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, upload_file_to_course -from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory, ToyCourseFactory +from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory, LibraryFactory, ToyCourseFactory + +from cms.djangoapps.contentstore.utils import reverse_usage_url +from cms.djangoapps.contentstore.tests.utils import AjaxEnabledTestClient CLIPBOARD_ENDPOINT = "/api/content-staging/v1/clipboard/" XBLOCK_ENDPOINT = "/xblock/" @@ -109,7 +113,7 @@ def test_copy_and_paste_component(self, block_args): """ Test copying a component (XBlock) from one course into another """ - source_course = CourseFactory.create(display_name='Destination Course') + source_course = CourseFactory.create(display_name='Source Course') source_block = BlockFactory.create(parent_location=source_course.location, **block_args) dest_course = CourseFactory.create(display_name='Destination Course') @@ -204,3 +208,79 @@ def test_paste_with_assets(self): source_pic2_hash = contentstore().find(source_course.id.make_asset_key("asset", "picture2.jpg")).content_digest dest_pic2_hash = contentstore().find(dest_course_key.make_asset_key("asset", "picture2.jpg")).content_digest assert source_pic2_hash != dest_pic2_hash # Because there was a conflict, this file was unchanged. + + +class ClipboardLibraryContentPasteTestCase(LiveServerTestCase, ModuleStoreTestCase): + """ + Test Clipboard Paste functionality with library content + """ + + def setUp(self): + """ + Set up a v2 Content Library and a library content block + """ + super().setUp() + self.client = AjaxEnabledTestClient() + self.client.login(username=self.user.username, password=self.user_password) + self.store = modulestore() + + def test_paste_library_content_block_v1(self): + """ + Same as the above test, but uses modulestore (v1) content library + """ + library = LibraryFactory.create() + data = { + 'parent_locator': str(library.location), + 'category': 'html', + 'display_name': 'HTML Content', + } + response = self.client.ajax_post(XBLOCK_ENDPOINT, data) + self.assertEqual(response.status_code, 200) + course = CourseFactory.create(display_name='Course') + orig_lc_block = BlockFactory.create( + parent=course, + category="library_content", + source_library_id=str(library.location.library_key), + display_name="LC Block", + publish_item=False, + ) + orig_lc_block.refresh_children() + orig_child = self.store.get_item(orig_lc_block.children[0]) + assert orig_child.display_name == "HTML Content" + # Copy a library content block that has children: + copy_response = self.client.post(CLIPBOARD_ENDPOINT, { + "usage_key": str(orig_lc_block.location) + }, format="json") + assert copy_response.status_code == 200 + + # Paste the Library content block: + paste_response = self.client.ajax_post(XBLOCK_ENDPOINT, { + "parent_locator": str(course.location), + "staged_content": "clipboard", + }) + assert paste_response.status_code == 200 + dest_lc_block_key = UsageKey.from_string(paste_response.json()["locator"]) + + # Get the ID of the new child: + dest_lc_block = self.store.get_item(dest_lc_block_key) + dest_child = self.store.get_item(dest_lc_block.children[0]) + assert dest_child.display_name == "HTML Content" + + # Importantly, the ID of the child must not changed when the library content is synced. + # Otherwise, user state saved against this child will be lost when it syncs. + dest_lc_block.refresh_children() + updated_dest_child = self.store.get_item(dest_lc_block.children[0]) + assert dest_child.location == updated_dest_child.location + + def _sync_lc_block_from_library(self, attr_name): + """ + Helper method to "sync" a Library Content Block by [re-]fetching its + children from the library. + """ + usage_key = getattr(self, attr_name).location + # It's easiest to do this via the REST API: + handler_url = reverse_usage_url('preview_handler', usage_key, kwargs={'handler': 'upgrade_and_sync'}) + response = self.client.post(handler_url) + assert response.status_code == 200 + # Now reload the block and make sure the child is in place + setattr(self, attr_name, self.store.get_item(usage_key)) # we must reload after upgrade_and_sync diff --git a/openedx/core/djangoapps/content_libraries/api.py b/openedx/core/djangoapps/content_libraries/api.py index 737673b32e3f..d020a7635b97 100644 --- a/openedx/core/djangoapps/content_libraries/api.py +++ b/openedx/core/djangoapps/content_libraries/api.py @@ -91,7 +91,15 @@ from xblock.exceptions import XBlockNotFoundError from edx_rest_api_client.client import OAuthAPIClient from openedx.core.djangoapps.content_libraries import permissions -from openedx.core.djangoapps.content_libraries.constants import DRAFT_NAME, COMPLEX +# pylint: disable=unused-import +from openedx.core.djangoapps.content_libraries.constants import ( + ALL_RIGHTS_RESERVED, + CC_4_BY, + COMPLEX, + DRAFT_NAME, + PROBLEM, + VIDEO, +) from openedx.core.djangoapps.content_libraries.library_bundle import LibraryBundle from openedx.core.djangoapps.content_libraries.libraries_index import ContentLibraryIndexer, LibraryBlockIndexer from openedx.core.djangoapps.content_libraries.models import ( @@ -425,6 +433,8 @@ def create_library( allow_public_read: Allow anyone to view blocks (including source) in Studio? + library_type: Deprecated parameter, not really used. Set to COMPLEX. + Returns a ContentLibraryMetadata instance. """ assert isinstance(collection_uuid, UUID) From 7aae617836dd5d5e5069ebac10803daea26d5cd0 Mon Sep 17 00:00:00 2001 From: Stanislav Date: Wed, 20 Mar 2024 15:04:11 +0200 Subject: [PATCH 09/18] fix: Space around techers images on course about page (#34358) --- common/test/data/course_after_rename/about/overview.html | 4 ++-- common/test/data/course_before_rename/about/overview.html | 4 ++-- common/test/data/course_info_updates/about/overview.html | 4 ++-- common/test/data/manual-testing-complete/about/overview.html | 4 ++-- common/test/data/scoreable/about/overview.html | 4 ++-- xmodule/templates/about/overview.yaml | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/common/test/data/course_after_rename/about/overview.html b/common/test/data/course_after_rename/about/overview.html index 0f9a54de9a4a..b20cf86c6d2f 100644 --- a/common/test/data/course_after_rename/about/overview.html +++ b/common/test/data/course_after_rename/about/overview.html @@ -14,7 +14,7 @@

Requirements

Course Staff

- Course Staff Image #1 + Course Staff Image #1

Staff Member #1

@@ -23,7 +23,7 @@

Staff Member #1

- Course Staff Image #2 + Course Staff Image #2

Staff Member #2

diff --git a/common/test/data/course_before_rename/about/overview.html b/common/test/data/course_before_rename/about/overview.html index 0f9a54de9a4a..b20cf86c6d2f 100644 --- a/common/test/data/course_before_rename/about/overview.html +++ b/common/test/data/course_before_rename/about/overview.html @@ -14,7 +14,7 @@

Requirements

Course Staff

- Course Staff Image #1 + Course Staff Image #1

Staff Member #1

@@ -23,7 +23,7 @@

Staff Member #1

- Course Staff Image #2 + Course Staff Image #2

Staff Member #2

diff --git a/common/test/data/course_info_updates/about/overview.html b/common/test/data/course_info_updates/about/overview.html index 6b4fe1a8636d..632157bfa99c 100644 --- a/common/test/data/course_info_updates/about/overview.html +++ b/common/test/data/course_info_updates/about/overview.html @@ -14,7 +14,7 @@

Prerequisites

Course Staff

- Course Staff Image #1 + Course Staff Image #1

Staff Member #1

@@ -23,7 +23,7 @@

Staff Member #1

- Course Staff Image #2 + Course Staff Image #2

Staff Member #2

diff --git a/common/test/data/manual-testing-complete/about/overview.html b/common/test/data/manual-testing-complete/about/overview.html index 6b4fe1a8636d..632157bfa99c 100644 --- a/common/test/data/manual-testing-complete/about/overview.html +++ b/common/test/data/manual-testing-complete/about/overview.html @@ -14,7 +14,7 @@

Prerequisites

Course Staff

- Course Staff Image #1 + Course Staff Image #1

Staff Member #1

@@ -23,7 +23,7 @@

Staff Member #1

- Course Staff Image #2 + Course Staff Image #2

Staff Member #2

diff --git a/common/test/data/scoreable/about/overview.html b/common/test/data/scoreable/about/overview.html index f829d3c8c177..d554b6a01843 100644 --- a/common/test/data/scoreable/about/overview.html +++ b/common/test/data/scoreable/about/overview.html @@ -14,7 +14,7 @@

Requirements

Course Staff

- Course Staff Image #1 + Course Staff Image #1

Staff Member #1

@@ -23,7 +23,7 @@

Staff Member #1

- Course Staff Image #2 + Course Staff Image #2

Staff Member #2

diff --git a/xmodule/templates/about/overview.yaml b/xmodule/templates/about/overview.yaml index 29ed75b96339..c5ddfcd97ee7 100644 --- a/xmodule/templates/about/overview.yaml +++ b/xmodule/templates/about/overview.yaml @@ -19,7 +19,7 @@ data: |

Course Staff

- Course Staff Image #1 + Course Staff Image #1

Staff Member #1

@@ -28,7 +28,7 @@ data: |
- Course Staff Image #2 + Course Staff Image #2

Staff Member #2

From e18418166b9794eff08441d55b2dd0dc1976012c Mon Sep 17 00:00:00 2001 From: Dmytro <98233552+DmytroAlipov@users.noreply.github.com> Date: Fri, 22 Mar 2024 01:00:20 +0100 Subject: [PATCH 10/18] fix: email templates caching (#34409) Authored-by: Dima Alipov --- .../core/djangoapps/theming/template_loaders.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/openedx/core/djangoapps/theming/template_loaders.py b/openedx/core/djangoapps/theming/template_loaders.py index 92326711d20a..760f1dd1adb8 100644 --- a/openedx/core/djangoapps/theming/template_loaders.py +++ b/openedx/core/djangoapps/theming/template_loaders.py @@ -27,13 +27,17 @@ class ThemeFilesystemLoader(FilesystemLoader): is_usable = True _accepts_engine_in_init = True - def __init__(self, engine, dirs=None): - if not dirs: - self.dirs = engine.dirs + def get_dirs(self): + """ + Override get_dirs method. + + Make the theme templates a priority, avoiding cashing templates for django ones. + """ + dirs = super().get_dirs() theme_dirs = self.get_theme_template_sources() - if isinstance(theme_dirs, list): - self.dirs = theme_dirs + self.dirs - super().__init__(engine, self.dirs) + if theme_dirs: + dirs = theme_dirs + dirs + return dirs @staticmethod def get_theme_template_sources(): From 620516cb48c62d0c21d660fbae0be867040b3f06 Mon Sep 17 00:00:00 2001 From: magajh Date: Fri, 29 Mar 2024 09:36:33 -0400 Subject: [PATCH 11/18] chore: upgrade Django to 4.2.11 --- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index cfaecd0ca35a..7b1e102ea4ee 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -181,7 +181,7 @@ defusedxml==0.7.1 # social-auth-core deprecated==1.2.14 # via jwcrypto -django==4.2.10 +django==4.2.11 # via # -r requirements/edx/kernel.in # django-appconf diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 87c17c1e74ae..4a118511706b 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -343,7 +343,7 @@ distlib==0.3.7 # via # -r requirements/edx/testing.txt # virtualenv -django==4.2.10 +django==4.2.11 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 9ec73dc14a36..11fe51833e3f 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -230,7 +230,7 @@ deprecated==1.2.14 # via # -r requirements/edx/base.txt # jwcrypto -django==4.2.10 +django==4.2.11 # via # -r requirements/edx/base.txt # django-appconf diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index a4b87c9178e4..8addb5189810 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -263,7 +263,7 @@ dill==0.3.7 # via pylint distlib==0.3.7 # via virtualenv -django==4.2.10 +django==4.2.11 # via # -r requirements/edx/base.txt # django-appconf From 900fad09a24466e70d3bda9d9858e50b738aae5f Mon Sep 17 00:00:00 2001 From: magajh Date: Sun, 31 Mar 2024 22:31:38 -0400 Subject: [PATCH 12/18] chore: compile requirements --- requirements/common_constraints.txt | 11 ++++++++++- requirements/edx/base.txt | 5 ++++- requirements/edx/development.txt | 2 ++ requirements/edx/doc.txt | 2 ++ requirements/edx/testing.txt | 2 ++ requirements/pip-tools.txt | 4 +++- 6 files changed, 23 insertions(+), 3 deletions(-) diff --git a/requirements/common_constraints.txt b/requirements/common_constraints.txt index 7313473a1675..4abc9ae22cb3 100644 --- a/requirements/common_constraints.txt +++ b/requirements/common_constraints.txt @@ -17,10 +17,19 @@ # using LTS django version - +Django<5.0 # elasticsearch>=7.14.0 includes breaking changes in it which caused issues in discovery upgrade process. # elastic search changelog: https://www.elastic.co/guide/en/enterprise-search/master/release-notes-7.14.0.html elasticsearch<7.14.0 # django-simple-history>3.0.0 adds indexing and causes a lot of migrations to be affected + +# opentelemetry requires version 6.x at the moment: +# https://github.com/open-telemetry/opentelemetry-python/issues/3570 +# Normally this could be added as a constraint in edx-django-utils, where we're +# adding the opentelemetry dependency. However, when we compile pip-tools.txt, +# that uses version 7.x, and then there's no undoing that when compiling base.txt. +# So we need to pin it globally, for now. +# Ticket for unpinning: https://github.com/openedx/edx-lint/issues/407 +importlib-metadata<7 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 7b1e102ea4ee..393d016a73e7 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -183,6 +183,7 @@ deprecated==1.2.14 # via jwcrypto django==4.2.11 # via + # -c requirements/edx/../common_constraints.txt # -r requirements/edx/kernel.in # django-appconf # django-celery-results @@ -610,7 +611,9 @@ idna==3.4 # snowflake-connector-python # yarl importlib-metadata==6.8.0 - # via markdown + # via + # -c requirements/edx/../common_constraints.txt + # markdown importlib-resources==6.1.0 # via # jsonschema diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 4a118511706b..7383f5747be7 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -345,6 +345,7 @@ distlib==0.3.7 # virtualenv django==4.2.11 # via + # -c requirements/edx/../common_constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # django-appconf @@ -1011,6 +1012,7 @@ import-linter==1.12.0 # via -r requirements/edx/testing.txt importlib-metadata==6.8.0 # via + # -c requirements/edx/../common_constraints.txt # -r requirements/edx/../pip-tools.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index 11fe51833e3f..026ffa4a9f7a 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -232,6 +232,7 @@ deprecated==1.2.14 # jwcrypto django==4.2.11 # via + # -c requirements/edx/../common_constraints.txt # -r requirements/edx/base.txt # django-appconf # django-celery-results @@ -711,6 +712,7 @@ imagesize==1.4.1 # via sphinx importlib-metadata==6.8.0 # via + # -c requirements/edx/../common_constraints.txt # -r requirements/edx/base.txt # markdown # sphinx diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 8addb5189810..202e33a40ee7 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -265,6 +265,7 @@ distlib==0.3.7 # via virtualenv django==4.2.11 # via + # -c requirements/edx/../common_constraints.txt # -r requirements/edx/base.txt # django-appconf # django-celery-results @@ -767,6 +768,7 @@ import-linter==1.12.0 # via -r requirements/edx/testing.in importlib-metadata==6.8.0 # via + # -c requirements/edx/../common_constraints.txt # -r requirements/edx/base.txt # markdown # pytest-randomly diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt index ea283f4ad3ad..d2e5211cc4bd 100644 --- a/requirements/pip-tools.txt +++ b/requirements/pip-tools.txt @@ -11,7 +11,9 @@ click==8.1.6 # -c requirements/constraints.txt # pip-tools importlib-metadata==6.8.0 - # via build + # via + # -c requirements/common_constraints.txt + # build packaging==23.2 # via build pip-tools==7.3.0 From 0906265588cd7e227878c59146204cf78e94f811 Mon Sep 17 00:00:00 2001 From: Navin Karkera Date: Thu, 16 Nov 2023 16:09:08 +0530 Subject: [PATCH 13/18] fix: session_language url renamed to update_language The url was renamed from session_language to update_language but it was still referred to in some html templates --- cms/templates/widgets/header.html | 2 +- lms/templates/header/header.html | 2 +- lms/templates/navigation/navigation.html | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index 6dd94ec78c55..34ea5584c428 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -324,7 +324,7 @@

${_("Tools")} % else: - + % endif