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; + } } } } 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/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"] 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 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 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) {