From ab4db05f98540ffe355f8663e62d34a69ea8cf0f Mon Sep 17 00:00:00 2001 From: Beto Fandino Date: Mon, 10 Jul 2023 18:13:33 -0400 Subject: [PATCH 01/13] fix: order in the view of the signatures in the certificate when choice print --- lms/static/certificates/sass/_layouts.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lms/static/certificates/sass/_layouts.scss b/lms/static/certificates/sass/_layouts.scss index 35b2ab4521..3ef14b178d 100644 --- a/lms/static/certificates/sass/_layouts.scss +++ b/lms/static/certificates/sass/_layouts.scss @@ -281,6 +281,13 @@ @include text-align(center); } + @media print{ + .list-signatories{ + justify-content: center; + display: inline-flex !important; + } + } + .signatory { display: inline-block; vertical-align: middle; From cc8722d1c0a94ddece55fb3c6297f3f60b690694 Mon Sep 17 00:00:00 2001 From: Justin Hynes Date: Thu, 21 Sep 2023 17:55:08 +0000 Subject: [PATCH 02/13] feat: publish `CERTIFICATE_REVOKED` events to the event bus This PR adds the ability for the LMS to publish `CERTIFICATE_REVOKED` events to the Event Bus. There is also work in progress for Credentials to consume these events. --- lms/djangoapps/certificates/config.py | 13 +++ lms/djangoapps/certificates/models.py | 4 + lms/djangoapps/certificates/signals.py | 21 ++++- .../certificates/tests/test_signals.py | 82 +++++++++++++------ 4 files changed, 93 insertions(+), 27 deletions(-) diff --git a/lms/djangoapps/certificates/config.py b/lms/djangoapps/certificates/config.py index fa9b3f2f82..5de6a51265 100644 --- a/lms/djangoapps/certificates/config.py +++ b/lms/djangoapps/certificates/config.py @@ -28,3 +28,16 @@ AUTO_CERTIFICATE_GENERATION = WaffleSwitch(f"{WAFFLE_NAMESPACE}.auto_certificate # .. toggle_target_removal_date: 2023-07-31 # .. toggle_tickets: TODO SEND_CERTIFICATE_CREATED_SIGNAL = SettingToggle('SEND_CERTIFICATE_CREATED_SIGNAL', default=False, module_name=__name__) + + +# .. toggle_name: SEND_CERTIFICATE_REVOKED_SIGNAL +# .. toggle_implementation: SettingToggle +# .. toggle_default: False +# .. toggle_description: When True, the system will publish `CERTIFICATE_REVOKED` signals to the event bus. The +# `CERTIFICATE_REVOKED` signal is emit when a certificate has been awarded to a learner and the creation process has +# completed. +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2023-09-15 +# .. toggle_target_removal_date: 2024-01-01 +# .. toggle_tickets: TODO +SEND_CERTIFICATE_REVOKED_SIGNAL = SettingToggle('SEND_CERTIFICATE_REVOKED_SIGNAL', default=False, module_name=__name__) diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index feb387cb6a..7b5677a690 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -378,6 +378,10 @@ class GeneratedCertificate(models.Model): if not grade: grade = '' + # the grade can come through revocation as a float, so we must convert it to a string to be compatible with the + # `CERTIFICATE_REVOKED` event definition + elif isinstance(grade, float): + grade = str(grade) if not mode: mode = self.mode diff --git a/lms/djangoapps/certificates/signals.py b/lms/djangoapps/certificates/signals.py index 676c10fb9c..4d43f0ed49 100644 --- a/lms/djangoapps/certificates/signals.py +++ b/lms/djangoapps/certificates/signals.py @@ -11,7 +11,7 @@ from openedx_events.event_bus import get_producer from common.djangoapps.course_modes import api as modes_api from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.signals import ENROLLMENT_TRACK_UPDATED -from lms.djangoapps.certificates.config import SEND_CERTIFICATE_CREATED_SIGNAL +from lms.djangoapps.certificates.config import SEND_CERTIFICATE_CREATED_SIGNAL, SEND_CERTIFICATE_REVOKED_SIGNAL from lms.djangoapps.certificates.generation_handler import ( CertificateGenerationNotAllowed, generate_allowlist_certificate_task, @@ -32,7 +32,7 @@ from openedx.core.djangoapps.signals.signals import ( COURSE_GRADE_NOW_PASSED, LEARNER_NOW_VERIFIED ) -from openedx_events.learning.signals import CERTIFICATE_CREATED +from openedx_events.learning.signals import CERTIFICATE_CREATED, CERTIFICATE_REVOKED log = logging.getLogger(__name__) @@ -162,7 +162,7 @@ def _listen_for_enrollment_mode_change(sender, user, course_key, mode, **kwargs) @receiver(CERTIFICATE_CREATED) -def listen_for_certificate_created_event(sender, signal, **kwargs): +def listen_for_certificate_created_event(sender, signal, **kwargs): # pylint: disable=unused-argument """ Publish `CERTIFICATE_CREATED` events to the event bus. """ @@ -174,3 +174,18 @@ def listen_for_certificate_created_event(sender, signal, **kwargs): event_data={'certificate': kwargs['certificate']}, event_metadata=kwargs['metadata'] ) + + +@receiver(CERTIFICATE_REVOKED) +def listen_for_certificate_revoked_event(sender, signal, **kwargs): # pylint: disable=unused-argument + """ + Publish `CERTIFICATE_REVOKED` events to the event bus. + """ + if SEND_CERTIFICATE_REVOKED_SIGNAL.is_enabled(): + get_producer().send( + signal=CERTIFICATE_REVOKED, + topic='learning-certificate-lifecycle', + event_key_field='certificate.course.course_key', + event_data={'certificate': kwargs['certificate']}, + event_metadata=kwargs['metadata'] + ) diff --git a/lms/djangoapps/certificates/tests/test_signals.py b/lms/djangoapps/certificates/tests/test_signals.py index b5993eb623..4eb8cf27f4 100644 --- a/lms/djangoapps/certificates/tests/test_signals.py +++ b/lms/djangoapps/certificates/tests/test_signals.py @@ -21,13 +21,16 @@ from lms.djangoapps.certificates.models import ( CertificateGenerationConfiguration, GeneratedCertificate ) -from lms.djangoapps.certificates.signals import listen_for_certificate_created_event +from lms.djangoapps.certificates.signals import ( + listen_for_certificate_created_event, + listen_for_certificate_revoked_event +) from lms.djangoapps.certificates.tests.factories import CertificateAllowlistFactory, GeneratedCertificateFactory from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory from lms.djangoapps.grades.tests.utils import mock_passing_grade from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification from openedx_events.data import EventsMetadata -from openedx_events.learning.signals import CERTIFICATE_CREATED +from openedx_events.learning.signals import CERTIFICATE_CREATED, CERTIFICATE_REVOKED from openedx_events.learning.data import CourseData, UserData, UserPersonalData, CertificateData @@ -458,22 +461,9 @@ class CertificateEventBusTests(ModuleStoreTestCase): mode='verified', ) - @override_settings(SEND_CERTIFICATE_CREATED_SIGNAL=False) - @mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True) - def test_event_disabled(self, mock_producer): + def _create_event_data(self, event_type, certificate_status): """ - Test to verify that we do not push `CERTIFICATE_CREATED` events to the event bus if the - `SEND_CERTIFICATE_CREATED_SIGNAL` setting is disabled. - """ - listen_for_certificate_created_event(None, CERTIFICATE_CREATED) - mock_producer.assert_not_called() - - @override_settings(SEND_CERTIFICATE_CREATED_SIGNAL=True) - @mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True) - def test_event_enabled(self, mock_producer): - """ - Test to verify that we push `CERTIFICATE_CREATED` events to the event bus if the - `SEND_CERTIFICATE_CREATED_SIGNAL` setting is enabled. + Utility function to create test data for unit tests. """ expected_course_data = CourseData(course_key=self.course.id) expected_user_data = UserData( @@ -490,12 +480,12 @@ class CertificateEventBusTests(ModuleStoreTestCase): course=expected_course_data, mode='verified', grade='', - current_status='downloadable', + current_status=certificate_status, download_url='', name='', ) - event_metadata = EventsMetadata( - event_type=CERTIFICATE_CREATED.event_type, + expected_event_metadata = EventsMetadata( + event_type=event_type.event_type, id=uuid4(), minorversion=0, source='openedx/lms/web', @@ -503,15 +493,59 @@ class CertificateEventBusTests(ModuleStoreTestCase): time=datetime.now(timezone.utc) ) - event_kwargs = { + return { 'certificate': expected_certificate_data, - 'metadata': event_metadata + 'metadata': expected_event_metadata, } - listen_for_certificate_created_event(None, CERTIFICATE_CREATED, **event_kwargs) + @override_settings(SEND_CERTIFICATE_CREATED_SIGNAL=False) + @mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True) + def test_certificate_created_event_disabled(self, mock_producer): + """ + Test to verify that we do not publish `CERTIFICATE_CREATED` events to the event bus if the + `SEND_CERTIFICATE_CREATED_SIGNAL` setting is disabled. + """ + listen_for_certificate_created_event(None, CERTIFICATE_CREATED) + mock_producer.assert_not_called() + + @override_settings(SEND_CERTIFICATE_REVOKED_SIGNAL=False) + @mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True) + def test_certificate_revoked_event_disabled(self, mock_producer): + """ + Test to verify that we do not publish `CERTIFICATE_REVOKED` events to the event bus if the + `SEND_CERTIFICATE_REVOKED_SIGNAL` setting is disabled. + """ + listen_for_certificate_created_event(None, CERTIFICATE_REVOKED) + mock_producer.assert_not_called() + + @override_settings(SEND_CERTIFICATE_CREATED_SIGNAL=True) + @mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True) + def test_certificate_created_event_enabled(self, mock_producer): + """ + Test to verify that we push `CERTIFICATE_CREATED` events to the event bus if the + `SEND_CERTIFICATE_CREATED_SIGNAL` setting is enabled. + """ + event_data = self._create_event_data(CERTIFICATE_CREATED, CertificateStatuses.downloadable) + listen_for_certificate_created_event(None, CERTIFICATE_CREATED, **event_data) # verify that the data sent to the event bus matches what we expect data = mock_producer.return_value.send.call_args.kwargs assert data['signal'].event_type == CERTIFICATE_CREATED.event_type - assert data['event_data']['certificate'] == expected_certificate_data + assert data['event_data']['certificate'] == event_data['certificate'] + assert data['topic'] == 'learning-certificate-lifecycle' + assert data['event_key_field'] == 'certificate.course.course_key' + + @override_settings(SEND_CERTIFICATE_REVOKED_SIGNAL=True) + @mock.patch('lms.djangoapps.certificates.signals.get_producer', autospec=True) + def test_certificate_revoked_event_enabled(self, mock_producer): + """ + Test to verify that we push `CERTIFICATE_REVOKED` events to the event bus if the + `SEND_CERTIFICATE_REVOKED_SIGNAL` setting is enabled. + """ + event_data = self._create_event_data(CERTIFICATE_REVOKED, CertificateStatuses.notpassing) + listen_for_certificate_revoked_event(None, CERTIFICATE_REVOKED, **event_data) + # verify that the data sent to the event bus matches what we expect + data = mock_producer.return_value.send.call_args.kwargs + assert data['signal'].event_type == CERTIFICATE_REVOKED.event_type + assert data['event_data']['certificate'] == event_data['certificate'] assert data['topic'] == 'learning-certificate-lifecycle' assert data['event_key_field'] == 'certificate.course.course_key' From 4c50ad0a435dfc4598d8b41965782393042a4672 Mon Sep 17 00:00:00 2001 From: Kyrylo Kireiev Date: Fri, 8 Sep 2023 12:15:24 +0000 Subject: [PATCH 03/13] feat: [AXIM-44] Adapt Delete Account to Bearer Authorization --- .../accounts/tests/test_retirement_views.py | 17 +++++++++++++++++ .../core/djangoapps/user_api/accounts/views.py | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py index 8f34d4ba42..6ec6e3694f 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py @@ -74,6 +74,7 @@ from common.djangoapps.student.tests.factories import ( from openedx.core.djangolib.testing.utils import skip_unless_lms from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order +from openedx.core.djangoapps.oauth_dispatch.tests.factories import ApplicationFactory, AccessTokenFactory from ...tests.factories import UserOrgTagFactory from ..views import USER_PROFILE_PII, AccountRetirementView @@ -263,6 +264,22 @@ class TestDeactivateLogout(RetirementTestCase): response = self.client.post(self.url, self.build_post(self.test_password), **headers) assert response.status_code == status.HTTP_403_FORBIDDEN + def test_bearer_auth(self): + """ + Test the account deactivation/logout endpoint using Bearer auth + """ + # testing with broken token + headers = {'HTTP_AUTHORIZATION': 'Bearer broken_token'} + response = self.client.post(self.url, self.build_post(self.test_password), **headers) + assert response.status_code == status.HTTP_401_UNAUTHORIZED + # testing with correct token + access_token = AccessTokenFactory(user=self.test_user, + application=ApplicationFactory(name="test_bearer", + user=self.test_user)).token + headers = {'HTTP_AUTHORIZATION': f'Bearer {access_token}'} + response = self.client.post(self.url, self.build_post(self.test_password), **headers) + assert response.status_code == status.HTTP_204_NO_CONTENT + @skip_unless_lms class TestPartnerReportingCleanup(ModuleStoreTestCase): diff --git a/openedx/core/djangoapps/user_api/accounts/views.py b/openedx/core/djangoapps/user_api/accounts/views.py index a2598013f6..ab178d07eb 100644 --- a/openedx/core/djangoapps/user_api/accounts/views.py +++ b/openedx/core/djangoapps/user_api/accounts/views.py @@ -21,6 +21,7 @@ from django.utils.translation import gettext as _ from edx_ace import ace from edx_ace.recipient import Recipient from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication +from openedx.core.lib.api.authentication import BearerAuthentication from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser from enterprise.models import EnterpriseCourseEnrollment, EnterpriseCustomerUser, PendingEnterpriseCustomerUser from integrated_channels.degreed.models import DegreedLearnerDataTransmissionAudit @@ -567,7 +568,7 @@ class DeactivateLogoutView(APIView): - Log the user out - Create a row in the retirement table for that user """ - authentication_classes = (JwtAuthentication, SessionAuthentication,) + authentication_classes = (JwtAuthentication, SessionAuthentication, BearerAuthentication) permission_classes = (permissions.IsAuthenticated,) def post(self, request): From f4c7f9803460e460f87947d141bd2ff843fa6ac5 Mon Sep 17 00:00:00 2001 From: Justin Hynes Date: Fri, 22 Sep 2023 17:20:00 +0000 Subject: [PATCH 04/13] fix: update toggle description to fix copy/paste issue --- lms/djangoapps/certificates/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/certificates/config.py b/lms/djangoapps/certificates/config.py index 5de6a51265..6df62ae563 100644 --- a/lms/djangoapps/certificates/config.py +++ b/lms/djangoapps/certificates/config.py @@ -34,8 +34,8 @@ SEND_CERTIFICATE_CREATED_SIGNAL = SettingToggle('SEND_CERTIFICATE_CREATED_SIGNAL # .. toggle_implementation: SettingToggle # .. toggle_default: False # .. toggle_description: When True, the system will publish `CERTIFICATE_REVOKED` signals to the event bus. The -# `CERTIFICATE_REVOKED` signal is emit when a certificate has been awarded to a learner and the creation process has -# completed. +# `CERTIFICATE_REVOKED` signal is emit when a certificate has been revoked from a learner and the revocation process +# has completed. # .. toggle_use_cases: temporary # .. toggle_creation_date: 2023-09-15 # .. toggle_target_removal_date: 2024-01-01 From bceba32f0a6410be9828a8cf3e88a8a87fe60402 Mon Sep 17 00:00:00 2001 From: Awais Qureshi Date: Mon, 25 Sep 2023 15:16:59 +0500 Subject: [PATCH 05/13] Upgrade django-storages to 1.14 ( max ver ) (#33312) * feat!: upgrading `django-storages` to `1.14` --- .../contentstore/tests/test_video_utils.py | 38 +++++++++---------- requirements/constraints.txt | 9 ++--- requirements/edx/base.txt | 23 +++++------ requirements/edx/development.txt | 23 +++++------ requirements/edx/doc.txt | 23 +++++------ requirements/edx/paver.txt | 2 +- requirements/edx/testing.txt | 24 +++++------- 7 files changed, 62 insertions(+), 80 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_video_utils.py b/cms/djangoapps/contentstore/tests/test_video_utils.py index 80d55b92b8..c81761a283 100644 --- a/cms/djangoapps/contentstore/tests/test_video_utils.py +++ b/cms/djangoapps/contentstore/tests/test_video_utils.py @@ -5,7 +5,7 @@ Unit tests for video utils. from datetime import datetime from unittest import TestCase -from unittest.mock import MagicMock, patch +from unittest import mock import ddt import pytz @@ -144,7 +144,7 @@ class ScrapeVideoThumbnailsTestCase(CourseTestCase): return mocked_response @override_settings(AWS_ACCESS_KEY_ID='test_key_id', AWS_SECRET_ACCESS_KEY='test_secret') - @patch('requests.get') + @mock.patch('requests.get') @ddt.data( ( { @@ -228,7 +228,7 @@ class ScrapeVideoThumbnailsTestCase(CourseTestCase): self.assertEqual(thumbnail_content_type, 'image/jpeg') @override_settings(AWS_ACCESS_KEY_ID='test_key_id', AWS_SECRET_ACCESS_KEY='test_secret') - @patch('requests.get') + @mock.patch('requests.get') def test_scrape_youtube_thumbnail(self, mocked_request): """ Test that youtube thumbnails are correctly scrapped. @@ -273,8 +273,8 @@ class ScrapeVideoThumbnailsTestCase(CourseTestCase): ) ) @override_settings(AWS_ACCESS_KEY_ID='test_key_id', AWS_SECRET_ACCESS_KEY='test_secret') - @patch('cms.djangoapps.contentstore.video_utils.LOGGER') - @patch('requests.get') + @mock.patch('cms.djangoapps.contentstore.video_utils.LOGGER') + @mock.patch('requests.get') @ddt.unpack def test_scrape_youtube_thumbnail_logging( self, @@ -333,8 +333,8 @@ class ScrapeVideoThumbnailsTestCase(CourseTestCase): ) ), ) - @patch('cms.djangoapps.contentstore.video_utils.LOGGER') - @patch('cms.djangoapps.contentstore.video_utils.download_youtube_video_thumbnail') + @mock.patch('cms.djangoapps.contentstore.video_utils.LOGGER') + @mock.patch('cms.djangoapps.contentstore.video_utils.download_youtube_video_thumbnail') @ddt.unpack def test_no_video_thumbnail_downloaded( self, @@ -376,7 +376,7 @@ class S3Boto3TestCase(TestCase): def setUp(self): self.storage = S3Boto3Storage() - self.storage._connections.connection = MagicMock() # pylint: disable=protected-access + self.storage._connections.connection = mock.MagicMock() # pylint: disable=protected-access def order_dict(self, dictionary): """ @@ -417,18 +417,18 @@ class S3Boto3TestCase(TestCase): content = ContentFile('new content') storage = S3Boto3Storage(**{'bucket_name': 'test'}) - storage._connections.connection = MagicMock() # pylint: disable=protected-access + storage._connections.connection = mock.MagicMock() # pylint: disable=protected-access storage.save(name, content) storage.bucket.Object.assert_called_once_with(name) obj = storage.bucket.Object.return_value obj.upload_fileobj.assert_called_with( - content, + mock.ANY, ExtraArgs=self.order_dict({ 'ContentType': 'text/plain', }), - Config=storage._transfer_config # pylint: disable=protected-access + Config=storage.transfer_config # pylint: disable=protected-access ) @override_settings(AWS_DEFAULT_ACL='public-read') @@ -445,7 +445,7 @@ class S3Boto3TestCase(TestCase): name = 'test_storage_save.txt' content = ContentFile('new content') storage = S3Boto3Storage(**{'bucket_name': 'test', 'default_acl': default_acl}) - storage._connections.connection = MagicMock() # pylint: disable=protected-access + storage._connections.connection = mock.MagicMock() # pylint: disable=protected-access storage.save(name, content) storage.bucket.Object.assert_called_once_with(name) @@ -461,9 +461,9 @@ class S3Boto3TestCase(TestCase): del ExtraArgs['ACL'] obj.upload_fileobj.assert_called_with( - content, + mock.ANY, ExtraArgs=self.order_dict(ExtraArgs), - Config=storage._transfer_config # pylint: disable=protected-access + Config=storage.transfer_config # pylint: disable=protected-access ) @ddt.data('public-read', 'private') @@ -476,16 +476,16 @@ class S3Boto3TestCase(TestCase): content = ContentFile('new content') storage = S3Boto3Storage(**{'bucket_name': 'test', 'default_acl': None}) - storage._connections.connection = MagicMock() # pylint: disable=protected-access + storage._connections.connection = mock.MagicMock() # pylint: disable=protected-access storage.save(name, content) storage.bucket.Object.assert_called_once_with(name) obj = storage.bucket.Object.return_value obj.upload_fileobj.assert_called_with( - content, - ExtraArgs=self.order_dict({ + mock.ANY, + Config=storage.transfer_config, # pylint: disable=protected-access + ExtraArgs={ 'ContentType': 'text/plain', - }), - Config=storage._transfer_config # pylint: disable=protected-access + }, ) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 87fadf16e2..50c78c4851 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -20,9 +20,8 @@ celery>=5.2.2,<6.0.0 # required for celery>=5.2.0;<5.3.0 click>=8.0,<9.0 -# django-storages version upgrade -django-storages==1.13.2 - +# each version upgrade need release notes review. +django-storages==1.14 # 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 @@ -78,8 +77,7 @@ pylint<2.16.0 # greater version failing quality test. Fix them in seperate ticke # Deprecated version of the AWS SDK; # we should stop using this boto==2.39.0 -boto3==1.7.0 # Amazon Web Services SDK for Python -botocore==1.10.84 # via boto3, s3transfer + # adding these constraints to minimize boto3 and botocore changeset social-auth-core==4.3.0 @@ -134,3 +132,4 @@ openedx-learning==0.1.6 # existing custom parameter configurations unusable. # https://github.com/openedx/xblock-lti-consumer/issues/410 has been opened to track a fix lti-consumer-xblock==9.6.1 + diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 63a5befdd5..10891a48df 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -77,16 +77,14 @@ boto==2.39.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in -boto3==1.7.0 +boto3==1.28.53 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in # django-ses # fs-s3fs # ora2 -botocore==1.10.84 +botocore==1.31.53 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in # boto3 # s3transfer @@ -259,7 +257,7 @@ django-celery-results==2.5.1 # via -r requirements/edx/kernel.in django-classy-tags==4.1.0 # via django-sekizai -django-config-models==2.5.0 +django-config-models==2.5.1 # via # -r requirements/edx/kernel.in # edx-enterprise @@ -357,7 +355,7 @@ django-statici18n==2.4.0 # -r requirements/edx/kernel.in # lti-consumer-xblock # xblock-drag-and-drop-v2 -django-storages==1.13.2 +django-storages==1.14 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in @@ -402,10 +400,6 @@ djangorestframework==3.14.0 # super-csv djangorestframework-xml==2.0.0 # via edx-enterprise -docutils==0.19 - # via - # -c requirements/edx/../constraints.txt - # botocore done-xblock==2.1.0 # via -r requirements/edx/bundled.in drf-jwt==1.19.2 @@ -490,7 +484,7 @@ edx-enterprise==4.3.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in -edx-event-bus-kafka==5.4.0 +edx-event-bus-kafka==5.5.0 # via -r requirements/edx/kernel.in edx-event-bus-redis==0.3.1 # via -r requirements/edx/kernel.in @@ -500,7 +494,7 @@ edx-milestones==0.5.0 # via -r requirements/edx/kernel.in edx-name-affirmation==2.3.6 # via -r requirements/edx/kernel.in -edx-opaque-keys[django]==2.5.0 +edx-opaque-keys[django]==2.5.1 # via # -r requirements/edx/kernel.in # -r requirements/edx/paver.txt @@ -636,7 +630,7 @@ jinja2==3.1.2 # via # code-annotations # coreschema -jmespath==0.10.0 +jmespath==1.0.1 # via # boto3 # botocore @@ -1034,7 +1028,7 @@ rules==3.3 # edx-enterprise # edx-proctoring # openedx-learning -s3transfer==0.1.13 +s3transfer==0.6.2 # via boto3 sailthru-client==2.2.3 # via edx-ace @@ -1169,6 +1163,7 @@ urllib3==1.26.16 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/paver.txt + # botocore # elasticsearch # py2neo # requests diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 7c5173ccaa..28e9b4f9f1 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -145,17 +145,15 @@ boto==2.39.0 # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -boto3==1.7.0 +boto3==1.28.53 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # django-ses # fs-s3fs # ora2 -botocore==1.10.84 +botocore==1.31.53 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # boto3 @@ -431,7 +429,7 @@ django-classy-tags==4.1.0 # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # django-sekizai -django-config-models==2.5.0 +django-config-models==2.5.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -571,7 +569,7 @@ django-statici18n==2.4.0 # -r requirements/edx/testing.txt # lti-consumer-xblock # xblock-drag-and-drop-v2 -django-storages==1.13.2 +django-storages==1.14 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt @@ -640,8 +638,6 @@ docutils==0.19 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt - # -r requirements/edx/testing.txt - # botocore # pydata-sphinx-theme # sphinx # sphinx-mdinclude @@ -759,7 +755,7 @@ edx-enterprise==4.3.1 # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -edx-event-bus-kafka==5.4.0 +edx-event-bus-kafka==5.5.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -782,7 +778,7 @@ edx-name-affirmation==2.3.6 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -edx-opaque-keys[django]==2.5.0 +edx-opaque-keys[django]==2.5.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -950,7 +946,7 @@ gitdb==4.0.10 # via # -r requirements/edx/doc.txt # gitpython -gitpython==3.1.36 +gitpython==3.1.37 # via -r requirements/edx/doc.txt glob2==0.7 # via @@ -1064,7 +1060,7 @@ jinja2==3.1.2 # coreschema # diff-cover # sphinx -jmespath==0.10.0 +jmespath==1.0.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1792,7 +1788,7 @@ rules==3.3 # edx-enterprise # edx-proctoring # openedx-learning -s3transfer==0.1.13 +s3transfer==0.6.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -2117,6 +2113,7 @@ urllib3==1.26.16 # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt + # botocore # elasticsearch # pact-python # py2neo diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index d9cbb521ef..c168388afa 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -103,16 +103,14 @@ boto==2.39.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt -boto3==1.7.0 +boto3==1.28.53 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # django-ses # fs-s3fs # ora2 -botocore==1.10.84 +botocore==1.31.53 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # boto3 # s3transfer @@ -312,7 +310,7 @@ django-classy-tags==4.1.0 # via # -r requirements/edx/base.txt # django-sekizai -django-config-models==2.5.0 +django-config-models==2.5.1 # via # -r requirements/edx/base.txt # edx-enterprise @@ -420,7 +418,7 @@ django-statici18n==2.4.0 # -r requirements/edx/base.txt # lti-consumer-xblock # xblock-drag-and-drop-v2 -django-storages==1.13.2 +django-storages==1.14 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -470,8 +468,6 @@ djangorestframework-xml==2.0.0 docutils==0.19 # via # -c requirements/edx/../constraints.txt - # -r requirements/edx/base.txt - # botocore # pydata-sphinx-theme # sphinx # sphinx-mdinclude @@ -564,7 +560,7 @@ edx-enterprise==4.3.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt -edx-event-bus-kafka==5.4.0 +edx-event-bus-kafka==5.5.0 # via -r requirements/edx/base.txt edx-event-bus-redis==0.3.1 # via -r requirements/edx/base.txt @@ -576,7 +572,7 @@ edx-milestones==0.5.0 # via -r requirements/edx/base.txt edx-name-affirmation==2.3.6 # via -r requirements/edx/base.txt -edx-opaque-keys[django]==2.5.0 +edx-opaque-keys[django]==2.5.1 # via # -r requirements/edx/base.txt # edx-bulk-grades @@ -686,7 +682,7 @@ geoip2==4.7.0 # via -r requirements/edx/base.txt gitdb==4.0.10 # via gitpython -gitpython==3.1.36 +gitpython==3.1.37 # via -r requirements/edx/doc.in glob2==0.7 # via -r requirements/edx/base.txt @@ -744,7 +740,7 @@ jinja2==3.1.2 # code-annotations # coreschema # sphinx -jmespath==0.10.0 +jmespath==1.0.1 # via # -r requirements/edx/base.txt # boto3 @@ -1222,7 +1218,7 @@ rules==3.3 # edx-enterprise # edx-proctoring # openedx-learning -s3transfer==0.1.13 +s3transfer==0.6.2 # via # -r requirements/edx/base.txt # boto3 @@ -1424,6 +1420,7 @@ urllib3==1.26.16 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt + # botocore # elasticsearch # py2neo # requests diff --git a/requirements/edx/paver.txt b/requirements/edx/paver.txt index 962fdec088..46af4d76cc 100644 --- a/requirements/edx/paver.txt +++ b/requirements/edx/paver.txt @@ -10,7 +10,7 @@ charset-normalizer==2.0.12 # via # -c requirements/edx/../constraints.txt # requests -edx-opaque-keys==2.5.0 +edx-opaque-keys==2.5.1 # via -r requirements/edx/paver.in idna==3.4 # via requests diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 2a07c19de0..6dc45b85a7 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -110,16 +110,14 @@ boto==2.39.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt -boto3==1.7.0 +boto3==1.28.53 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # django-ses # fs-s3fs # ora2 -botocore==1.10.84 +botocore==1.31.53 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # boto3 # s3transfer @@ -345,7 +343,7 @@ django-classy-tags==4.1.0 # via # -r requirements/edx/base.txt # django-sekizai -django-config-models==2.5.0 +django-config-models==2.5.1 # via # -r requirements/edx/base.txt # edx-enterprise @@ -453,7 +451,7 @@ django-statici18n==2.4.0 # -r requirements/edx/base.txt # lti-consumer-xblock # xblock-drag-and-drop-v2 -django-storages==1.13.2 +django-storages==1.14 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -500,11 +498,6 @@ djangorestframework-xml==2.0.0 # via # -r requirements/edx/base.txt # edx-enterprise -docutils==0.19 - # via - # -c requirements/edx/../constraints.txt - # -r requirements/edx/base.txt - # botocore done-xblock==2.1.0 # via -r requirements/edx/base.txt drf-jwt==1.19.2 @@ -594,7 +587,7 @@ edx-enterprise==4.3.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt -edx-event-bus-kafka==5.4.0 +edx-event-bus-kafka==5.5.0 # via -r requirements/edx/base.txt edx-event-bus-redis==0.3.1 # via -r requirements/edx/base.txt @@ -609,7 +602,7 @@ edx-milestones==0.5.0 # via -r requirements/edx/base.txt edx-name-affirmation==2.3.6 # via -r requirements/edx/base.txt -edx-opaque-keys[django]==2.5.0 +edx-opaque-keys[django]==2.5.1 # via # -r requirements/edx/base.txt # edx-bulk-grades @@ -810,7 +803,7 @@ jinja2==3.1.2 # code-annotations # coreschema # diff-cover -jmespath==0.10.0 +jmespath==1.0.1 # via # -r requirements/edx/base.txt # boto3 @@ -1359,7 +1352,7 @@ rules==3.3 # edx-enterprise # edx-proctoring # openedx-learning -s3transfer==0.1.13 +s3transfer==0.6.2 # via # -r requirements/edx/base.txt # boto3 @@ -1564,6 +1557,7 @@ urllib3==1.26.16 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt + # botocore # elasticsearch # pact-python # py2neo From 6a1f126be917c69aaa6d0992b754701d701c09ff Mon Sep 17 00:00:00 2001 From: KyryloKireiev Date: Mon, 25 Sep 2023 14:12:17 +0300 Subject: [PATCH 06/13] fix: (review) Add comment to Delete Account view --- openedx/core/djangoapps/user_api/accounts/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openedx/core/djangoapps/user_api/accounts/views.py b/openedx/core/djangoapps/user_api/accounts/views.py index ab178d07eb..83bdde4f44 100644 --- a/openedx/core/djangoapps/user_api/accounts/views.py +++ b/openedx/core/djangoapps/user_api/accounts/views.py @@ -568,6 +568,9 @@ class DeactivateLogoutView(APIView): - Log the user out - Create a row in the retirement table for that user """ + # BearerAuthentication is added here to support account deletion + # from the mobile app until it moves to JWT Auth. + # See mobile roadmap issue https://github.com/openedx/edx-platform/issues/33307. authentication_classes = (JwtAuthentication, SessionAuthentication, BearerAuthentication) permission_classes = (permissions.IsAuthenticated,) From e7d2fa858974ee5ac5b5925552e8ba0e2e5aafba Mon Sep 17 00:00:00 2001 From: alex-sheehan-edx Date: Mon, 25 Sep 2023 16:07:38 +0000 Subject: [PATCH 07/13] feat: Upgrade Python dependency edx-enterprise 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 50c78c4851..f8b4c7d695 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -26,7 +26,7 @@ django-storages==1.14 # 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==4.3.1 +edx-enterprise==4.3.2 # 1. django-oauth-toolkit version >=2.0.0 has breaking changes. More details # mentioned on this issue https://github.com/openedx/edx-platform/issues/32884 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 10891a48df..9418049e50 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -480,7 +480,7 @@ edx-drf-extensions==8.10.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.3.1 +edx-enterprise==4.3.2 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 28e9b4f9f1..b9f3a949e1 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -750,7 +750,7 @@ edx-drf-extensions==8.10.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.3.1 +edx-enterprise==4.3.2 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index c168388afa..c5cccb0ddc 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -556,7 +556,7 @@ edx-drf-extensions==8.10.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.3.1 +edx-enterprise==4.3.2 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 6dc45b85a7..355a90b514 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -583,7 +583,7 @@ edx-drf-extensions==8.10.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.3.1 +edx-enterprise==4.3.2 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt From 8e513f01a1680c69de6df29068efab1656ec5f6c Mon Sep 17 00:00:00 2001 From: Tim McCormack Date: Mon, 25 Sep 2023 13:02:29 -0400 Subject: [PATCH 08/13] build: Short-circuit the consistency check when no requirements changes (#33330) This skips the `make compile-requirements` check when there have been no changes under `requirements/**`, but it does so in a way that still registers the action as having passed, not skipped. By doing so, we can make it a required check while also avoiding the 5-6 minutes of wasted worker time. This commit also removes the activation on push-to-master, since we really just need to check PRs. I don't expect there to be silent merge conflicts with this check, so if it passes on a branch it should also pass on a successful simple rebase or merge. It would be nice if there was a way to declare success and exit early, but GH hasn't implemented it: https://github.com/actions/runner/issues/662 Alternatively, it would be great if skipped checks could count as fulfilling the branch protection rules, but no luck there. The only alternative that uses GH's built-in paths/paths-ignore feature would be to add a second workflow with the same job name and the opposite path triggers and that always passes. It's not clear that this would be any less fragile or confusing than the `git diff | grep` and step-conditionals approach. --- .../check-consistent-dependencies.yml | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/.github/workflows/check-consistent-dependencies.yml b/.github/workflows/check-consistent-dependencies.yml index c2fc5fd1d5..b2466b8830 100644 --- a/.github/workflows/check-consistent-dependencies.yml +++ b/.github/workflows/check-consistent-dependencies.yml @@ -7,13 +7,6 @@ name: Consistent Python dependencies on: pull_request: - paths: - - 'requirements/**' - push: - branches: - - master - paths: - - 'requirements/**' defaults: run: @@ -25,16 +18,37 @@ jobs: runs-on: ubuntu-22.04 steps: + # Only run remaining steps if there are changes to requirements/** + - name: "Decide whether to short-circuit" + env: + GH_TOKEN: "${{ github.token }}" + PR_URL: "${{ github.event.pull_request.html_url }}" + run: | + paths=$(gh pr diff "$PR_URL" --name-only) + echo $'Paths touched in PR:\n'"$paths" + + # The ^"? is because git may quote weird file paths + matched="$(echo "$paths" | grep -P '^"?requirements/' || true)" + echo $'Relevant paths:\n'"$matched" + if [[ -n "$matched" ]]; then + echo "RELEVANT=true" >> "$GITHUB_ENV" + fi + - uses: actions/checkout@v3 + if: ${{ env.RELEVANT == 'true' }} - uses: actions/setup-python@v4 + if: ${{ env.RELEVANT == 'true' }} with: python-version: '3.8' - - run: | + - name: "Recompile requirements" + if: ${{ env.RELEVANT == 'true' }} + run: | make compile-requirements - name: Fail if compiling requirements caused changes + if: ${{ env.RELEVANT == 'true' }} run: | SUMMARY_HELP=$(cat <<'EOMARKDOWN' # Inconsistent Python dependencies From 5d1a778a1d97b99f551b2375e727fbb08d57e187 Mon Sep 17 00:00:00 2001 From: Kristin Aoki <42981026+KristinAoki@users.noreply.github.com> Date: Mon, 25 Sep 2023 13:08:19 -0400 Subject: [PATCH 09/13] feat: add new library util for split studio view (#33283) --- .../contentstore/asset_storage_handlers.py | 14 +--- cms/djangoapps/contentstore/utils.py | 66 ++++++++++++++++++- cms/djangoapps/contentstore/views/course.py | 24 +------ 3 files changed, 68 insertions(+), 36 deletions(-) diff --git a/cms/djangoapps/contentstore/asset_storage_handlers.py b/cms/djangoapps/contentstore/asset_storage_handlers.py index 29c8800fa0..8808dc4f0b 100644 --- a/cms/djangoapps/contentstore/asset_storage_handlers.py +++ b/cms/djangoapps/contentstore/asset_storage_handlers.py @@ -32,7 +32,7 @@ from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disa from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order from .exceptions import AssetNotFoundException, AssetSizeTooLargeException -from .utils import reverse_course_url, get_files_uploads_url +from .utils import reverse_course_url, get_files_uploads_url, get_response_format, request_response_format_is_json from .toggles import use_new_files_uploads_page @@ -73,8 +73,8 @@ def handle_assets(request, course_key_string=None, asset_key_string=None): if not has_course_author_access(request.user, course_key): raise PermissionDenied() - response_format = _get_response_format(request) - if _request_response_format_is_json(request, response_format): + response_format = get_response_format(request) + if request_response_format_is_json(request, response_format): if request.method == 'GET': return _assets_json(request, course_key) @@ -133,14 +133,6 @@ def get_asset_usage_path(request, course_key, asset_key_string): return JsonResponse({'usage_locations': usage_locations}) -def _get_response_format(request): - return request.GET.get('format') or request.POST.get('format') or 'html' - - -def _request_response_format_is_json(request, response_format): - return response_format == 'json' or 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json') - - def _asset_index(request, course_key): ''' Display an editable asset library. diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 97b70e828c..1a4b709622 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -1390,9 +1390,63 @@ def get_help_urls(): return help_tokens +def get_response_format(request): + return request.GET.get('format') or request.POST.get('format') or 'html' + + +def request_response_format_is_json(request, response_format): + return response_format == 'json' or 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json') + + +def get_library_context(request, request_is_json=False): + """ + Utils is used to get context of course home library tab. + It is used for both DRF and django views. + """ + from cms.djangoapps.contentstore.views.course import ( + get_allowed_organizations, + get_allowed_organizations_for_libraries, + user_can_create_organizations, + _accessible_libraries_iter, + _get_course_creator_status, + _format_library_for_view, + ) + from cms.djangoapps.contentstore.views.library import ( + LIBRARIES_ENABLED, + ) + + libraries = _accessible_libraries_iter(request.user) if LIBRARIES_ENABLED else [] + data = { + 'libraries': [_format_library_for_view(lib, request) for lib in libraries], + } + + if not request_is_json: + return { + **data, + 'in_process_course_actions': [], + 'courses': [], + 'libraries_enabled': LIBRARIES_ENABLED, + 'show_new_library_button': LIBRARIES_ENABLED and request.user.is_active, + 'user': request.user, + 'request_course_creator_url': reverse('request_course_creator'), + 'course_creator_status': _get_course_creator_status(request.user), + 'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False), + 'archived_courses': True, + 'allow_course_reruns': settings.FEATURES.get('ALLOW_COURSE_RERUNS', True), + 'rerun_creator_status': GlobalStaff().has_user(request.user), + 'split_studio_home': split_library_view_on_dashboard(), + 'active_tab': 'libraries', + 'allowed_organizations_for_libraries': get_allowed_organizations_for_libraries(request.user), + 'allowed_organizations': get_allowed_organizations(request.user), + 'can_create_organizations': user_can_create_organizations(request.user), + } + + return data + + def get_home_context(request): """ - Utils is used to get context of course grading. + Utils is used to get context of course home. It is used for both DRF and django views. """ @@ -1420,8 +1474,14 @@ def get_home_context(request): courses_iter, in_process_course_actions = get_courses_accessible_to_user(request, org) user = request.user libraries = [] + response_format = get_response_format(request) + if not split_library_view_on_dashboard() and LIBRARIES_ENABLED: - libraries = _accessible_libraries_iter(request.user) + accessible_libraries = _accessible_libraries_iter(user) + libraries = [_format_library_for_view(lib, request) for lib in accessible_libraries] + + if split_library_view_on_dashboard() and request_response_format_is_json(request, response_format): + libraries = get_library_context(request, True)['libraries'] def format_in_process_course_view(uca): """ @@ -1456,7 +1516,7 @@ def get_home_context(request): 'libraries_enabled': LIBRARIES_ENABLED, 'redirect_to_library_authoring_mfe': should_redirect_to_library_authoring_mfe(), 'library_authoring_mfe_url': LIBRARY_AUTHORING_MICROFRONTEND_URL, - 'libraries': [_format_library_for_view(lib, request) for lib in libraries], + 'libraries': libraries, 'show_new_library_button': user_can_create_library(user) and not should_redirect_to_library_authoring_mfe(), 'user': user, 'request_course_creator_url': reverse('request_course_creator'), diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index d74a99a8e3..4a70e5028c 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -89,7 +89,6 @@ from ..courseware_index import CoursewareSearchIndexer, SearchIndexingError from ..tasks import rerun_course as rerun_course_task from ..toggles import ( default_enable_flexible_peer_openassessments, - split_library_view_on_dashboard, use_new_course_outline_page, use_new_home_page, use_new_updates_page, @@ -102,6 +101,7 @@ from ..utils import ( get_course_settings, get_course_grading, get_home_context, + get_library_context, get_lms_link_for_item, get_proctored_exam_settings_url, get_course_outline_url, @@ -121,7 +121,6 @@ from ..utils import ( update_course_discussions_settings, ) from .component import ADVANCED_COMPONENT_TYPES -from .library import LIBRARIES_ENABLED log = logging.getLogger(__name__) User = get_user_model() @@ -551,26 +550,7 @@ def library_listing(request): """ List all Libraries available to the logged in user """ - libraries = _accessible_libraries_iter(request.user) if LIBRARIES_ENABLED else [] - data = { - 'in_process_course_actions': [], - 'courses': [], - 'libraries_enabled': LIBRARIES_ENABLED, - 'libraries': [_format_library_for_view(lib, request) for lib in libraries], - 'show_new_library_button': LIBRARIES_ENABLED and request.user.is_active, - 'user': request.user, - 'request_course_creator_url': reverse('request_course_creator'), - 'course_creator_status': _get_course_creator_status(request.user), - 'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False), - 'archived_courses': True, - 'allow_course_reruns': settings.FEATURES.get('ALLOW_COURSE_RERUNS', True), - 'rerun_creator_status': GlobalStaff().has_user(request.user), - 'split_studio_home': split_library_view_on_dashboard(), - 'active_tab': 'libraries', - 'allowed_organizations': get_allowed_organizations(request.user), - 'allowed_organizations_for_libraries': get_allowed_organizations_for_libraries(request.user), - 'can_create_organizations': user_can_create_organizations(request.user), - } + data = get_library_context(request) return render_to_response('index.html', data) From f707bb1ac4d7f0c5e987ab0ce8d27b30aa65d839 Mon Sep 17 00:00:00 2001 From: Muhammad Umar Khan <42294172+mumarkhan999@users.noreply.github.com> Date: Tue, 26 Sep 2023 14:06:17 +0500 Subject: [PATCH 10/13] chore: upgrade dot (#33341) --- requirements/constraints.txt | 9 +++------ requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/doc.txt | 2 +- requirements/edx/testing.txt | 2 +- 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index f8b4c7d695..ee3c12f7a1 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -28,11 +28,9 @@ django-storages==1.14 # for them. edx-enterprise==4.3.2 -# 1. django-oauth-toolkit version >=2.0.0 has breaking changes. More details -# mentioned on this issue https://github.com/openedx/edx-platform/issues/32884 -# 2. Versions from 1.5.0 to 2.0.0 have some migrations related changes. -# so we're upgrading minor versions one by one. -django-oauth-toolkit==1.6.2 +# django-oauth-toolkit version >=2.0.0 has breaking changes. More details +# mentioned on this issue https://github.com/openedx/edx-platform/issues/32884 +django-oauth-toolkit==1.7.1 # constrained in opaque_keys. migration guide here: https://pymongo.readthedocs.io/en/4.0/migrate-to-pymongo4.html @@ -132,4 +130,3 @@ openedx-learning==0.1.6 # existing custom parameter configurations unusable. # https://github.com/openedx/xblock-lti-consumer/issues/410 has been opened to track a fix lti-consumer-xblock==9.6.1 - diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 9418049e50..5074dbadd0 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -323,7 +323,7 @@ django-multi-email-field==0.7.0 # via edx-enterprise django-mysql==4.11.0 # via -r requirements/edx/kernel.in -django-oauth-toolkit==1.6.2 +django-oauth-toolkit==1.7.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index b9f3a949e1..257daff4fd 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -522,7 +522,7 @@ django-mysql==4.11.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -django-oauth-toolkit==1.6.2 +django-oauth-toolkit==1.7.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index c5cccb0ddc..bcc1381772 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -384,7 +384,7 @@ django-multi-email-field==0.7.0 # edx-enterprise django-mysql==4.11.0 # via -r requirements/edx/base.txt -django-oauth-toolkit==1.6.2 +django-oauth-toolkit==1.7.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 355a90b514..2368bc59aa 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -417,7 +417,7 @@ django-multi-email-field==0.7.0 # edx-enterprise django-mysql==4.11.0 # via -r requirements/edx/base.txt -django-oauth-toolkit==1.6.2 +django-oauth-toolkit==1.7.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt From c7c2d916de2846a5cce481255e08e279fe4217a4 Mon Sep 17 00:00:00 2001 From: Ahtisham Shahid Date: Tue, 26 Sep 2023 14:27:59 +0500 Subject: [PATCH 11/13] Revert "chore: added log to track changes in user notification pref (#33237)" (#33334) This reverts commit 90ca72a34e66f9a1ea6c9eac499972b90ed4a2a1. --- openedx/core/djangoapps/notifications/handlers.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/openedx/core/djangoapps/notifications/handlers.py b/openedx/core/djangoapps/notifications/handlers.py index 8444d1896f..c0b85d7c8d 100644 --- a/openedx/core/djangoapps/notifications/handlers.py +++ b/openedx/core/djangoapps/notifications/handlers.py @@ -11,8 +11,6 @@ from openedx_events.learning.signals import ( COURSE_UNENROLLMENT_COMPLETED, USER_NOTIFICATION_REQUESTED ) -from django.db.models.signals import post_save -import traceback from openedx.core.djangoapps.notifications.config.waffle import ENABLE_NOTIFICATIONS from openedx.core.djangoapps.notifications.models import CourseNotificationPreference @@ -61,16 +59,3 @@ def generate_user_notifications(signal, sender, notification_data, metadata, **k notification_data = notification_data.__dict__ notification_data['course_key'] = str(notification_data['course_key']) send_notifications.delay(**notification_data) - - -@receiver(post_save, sender=CourseNotificationPreference) -def notification_post_save(signal, sender, instance, created, **kwargs): - """ - Watches for post_save signal for update on the CourseNotificationPreference table. - Generate a log with traceback if CourseNotificationPreference is updated - """ - if not created: - # Get the stack trace - stack_trace = traceback.format_stack() - # Log the update along with the stack trace - log.info(f"{sender.__name__} (ID: {instance.pk}) was updated. Update induced by:\n{stack_trace}") From 0a636918c3eb556ea508ad0c5bc9ec3dbd47b162 Mon Sep 17 00:00:00 2001 From: Yusuf Musleh Date: Tue, 26 Sep 2023 20:23:48 +0300 Subject: [PATCH 12/13] fix: Hide Copy menu button in ContentLibrary (#33276) Since the Copy/Paste functionality has not been implemented for ContentLibraries yet, the "Copy to Clipboard" button should not appear in both the ContentLibrary page. --- cms/djangoapps/contentstore/views/preview.py | 3 +- cms/static/js/views/pages/container.js | 53 +++++++++++--------- cms/templates/studio_xblock_wrapper.html | 12 +++-- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py index 5ee2740abe..48c323c1aa 100644 --- a/cms/djangoapps/contentstore/views/preview.py +++ b/cms/djangoapps/contentstore/views/preview.py @@ -313,7 +313,8 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False): 'selected_groups_label': selected_groups_label, 'can_add': context.get('can_add', True), 'can_move': context.get('can_move', is_course), - 'language': getattr(course, 'language', None) + 'language': getattr(course, 'language', None), + 'is_course': is_course } add_webpack_js_to_fragment(frag, "js/factories/xblock_validation") diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js index 122556c76a..8f296ebb6b 100644 --- a/cms/static/js/views/pages/container.js +++ b/cms/static/js/views/pages/container.js @@ -48,6 +48,7 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView BasePage.prototype.initialize.call(this, options); this.viewClass = options.viewClass || this.defaultViewClass; this.isLibraryPage = (this.model.attributes.category === 'library'); + this.isLibraryContentPage = (this.model.attributes.category === 'library_content'); this.nameEditor = new XBlockStringFieldEditor({ el: this.$('.wrapper-xblock-field'), model: this.model @@ -154,7 +155,7 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView self.delegateEvents(); // Show/hide the paste button - if (!self.isLibraryPage) { + if (!self.isLibraryPage && !self.isLibraryContentPage) { self.initializePasteButton(); } }, @@ -208,32 +209,36 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView * Given the latest information about the user's clipboard, hide or show the Paste button as appropriate. */ refreshPasteButton(data) { - // 'data' is the same data returned by the "get clipboard status" API endpoint - // i.e. /api/content-staging/v1/clipboard/ - if (this.options.canEdit && data.content) { - if (["vertical", "sequential", "chapter", "course"].includes(data.content.block_type)) { - // This is not suitable for pasting into a unit. - this.$(".paste-component").hide(); - } else if (data.content.status === "expired") { - // This has expired and can no longer be pasted. - this.$(".paste-component").hide(); - } else { - // The thing in the clipboard can be pasted into this unit: - const detailsPopupEl = this.$(".clipboard-details-popup")[0]; - detailsPopupEl.querySelector(".detail-block-name").innerText = data.content.display_name; - detailsPopupEl.querySelector(".detail-block-type").innerText = data.content.block_type_display; - detailsPopupEl.querySelector(".detail-course-name").innerText = data.source_context_title; - if (data.source_edit_url) { - detailsPopupEl.setAttribute("href", data.source_edit_url); - detailsPopupEl.classList.remove("no-edit-link"); + // Do not perform any changes on paste button since they are not + // rendered on Library or LibraryContent pages + if (!this.isLibraryPage && !this.isLibraryContentPage) { + // 'data' is the same data returned by the "get clipboard status" API endpoint + // i.e. /api/content-staging/v1/clipboard/ + if (this.options.canEdit && data.content) { + if (["vertical", "sequential", "chapter", "course"].includes(data.content.block_type)) { + // This is not suitable for pasting into a unit. + this.$(".paste-component").hide(); + } else if (data.content.status === "expired") { + // This has expired and can no longer be pasted. + this.$(".paste-component").hide(); } else { - detailsPopupEl.setAttribute("href", "#"); - detailsPopupEl.classList.add("no-edit-link"); + // The thing in the clipboard can be pasted into this unit: + const detailsPopupEl = this.$(".clipboard-details-popup")[0]; + detailsPopupEl.querySelector(".detail-block-name").innerText = data.content.display_name; + detailsPopupEl.querySelector(".detail-block-type").innerText = data.content.block_type_display; + detailsPopupEl.querySelector(".detail-course-name").innerText = data.source_context_title; + if (data.source_edit_url) { + detailsPopupEl.setAttribute("href", data.source_edit_url); + detailsPopupEl.classList.remove("no-edit-link"); + } else { + detailsPopupEl.setAttribute("href", "#"); + detailsPopupEl.classList.add("no-edit-link"); + } + this.$(".paste-component").show(); } - this.$(".paste-component").show(); + } else { + this.$(".paste-component").hide(); } - } else { - this.$(".paste-component").hide(); } }, diff --git a/cms/templates/studio_xblock_wrapper.html b/cms/templates/studio_xblock_wrapper.html index fa48a905cc..a027cc764b 100644 --- a/cms/templates/studio_xblock_wrapper.html +++ b/cms/templates/studio_xblock_wrapper.html @@ -104,9 +104,15 @@ block_is_unit = is_unit(xblock)