diff --git a/cms/djangoapps/contentstore/rest_api/v0/tests/test_assets.py b/cms/djangoapps/contentstore/rest_api/v0/tests/test_assets.py index 46b636916df8..3772dbb64e77 100644 --- a/cms/djangoapps/contentstore/rest_api/v0/tests/test_assets.py +++ b/cms/djangoapps/contentstore/rest_api/v0/tests/test_assets.py @@ -60,13 +60,8 @@ def send_request(self, _url, _data): } ), ) - @patch( - f"cms.djangoapps.contentstore.rest_api.{VERSION}.views.xblock.toggles.use_studio_content_api", - return_value=True, - ) def make_request( self, - mock_use_studio_content_api, mock_handle_assets, run_assertions=None, course_id=None, @@ -125,13 +120,6 @@ def assert_assets_handler_called(self, *, mock_handle_assets, response): def send_request(self, url, data): return self.client.get(url) - def test_api_behind_feature_flag(self): - # should return 404 if the feature flag is not enabled - url = self.get_url() - - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_assets_handler_called_with_correct_arguments(self): self.client.login( username=self.course_instructor.username, password=self.password @@ -182,13 +170,6 @@ def assert_assets_handler_called(self, *, mock_handle_assets, response): def send_request(self, url, data): return self.client.post(url, data=data, format="multipart") - def test_api_behind_feature_flag(self): - # should return 404 if the feature flag is not enabled - url = self.get_url() - - response = self.client.post(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_assets_handler_called_with_correct_arguments(self): self.client.login( username=self.course_instructor.username, password=self.password @@ -232,13 +213,6 @@ def assert_assets_handler_called(self, *, mock_handle_assets, response): def send_request(self, url, data): return self.client.put(url, data=data, format="json") - def test_api_behind_feature_flag(self): - # should return 404 if the feature flag is not enabled - url = self.get_url() - - response = self.client.put(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_assets_handler_called_with_correct_arguments(self): self.client.login( username=self.course_instructor.username, password=self.password @@ -277,13 +251,6 @@ def assert_assets_handler_called(self, *, mock_handle_assets, response): def send_request(self, url, data): return self.client.delete(url) - def test_api_behind_feature_flag(self): - # should return 404 if the feature flag is not enabled - url = self.get_url() - - response = self.client.delete(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_assets_handler_called_with_correct_arguments(self): self.client.login( username=self.course_instructor.username, password=self.password diff --git a/cms/djangoapps/contentstore/rest_api/v0/tests/test_xblock.py b/cms/djangoapps/contentstore/rest_api/v0/tests/test_xblock.py index e4c21eb353b1..512c92f6ffe9 100644 --- a/cms/djangoapps/contentstore/rest_api/v0/tests/test_xblock.py +++ b/cms/djangoapps/contentstore/rest_api/v0/tests/test_xblock.py @@ -55,13 +55,8 @@ def send_request(self, _url, _data): } ), ) - @patch( - f"cms.djangoapps.contentstore.rest_api.{VERSION}.views.xblock.toggles.use_studio_content_api", - return_value=True, - ) def make_request( self, - mock_use_studio_content_api, mock_handle_xblock, run_assertions=None, course_id=None, @@ -111,13 +106,6 @@ def assert_xblock_handler_called(self, *, mock_handle_xblock, response): def send_request(self, url, data): return self.client.get(url) - def test_api_behind_feature_flag(self): - # should return 404 if the feature flag is not enabled - url = self.get_url() - - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_xblock_handler_called_with_correct_arguments(self): self.client.login( username=self.course_instructor.username, password=self.password @@ -167,13 +155,6 @@ def assert_xblock_handler_called(self, *, mock_handle_xblock, response): def send_request(self, url, data): return self.client.post(url, data=data, format="json") - def test_api_behind_feature_flag(self): - # should return 404 if the feature flag is not enabled - url = self.get_url() - - response = self.client.post(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_xblock_handler_called_with_correct_arguments(self): self.client.login( username=self.course_instructor.username, password=self.password @@ -218,13 +199,6 @@ def assert_xblock_handler_called(self, *, mock_handle_xblock, response): def send_request(self, url, data): return self.client.put(url, data=data, format="json") - def test_api_behind_feature_flag(self): - # should return 404 if the feature flag is not enabled - url = self.get_url() - - response = self.client.put(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_xblock_handler_called_with_correct_arguments(self): self.client.login( username=self.course_instructor.username, password=self.password @@ -269,13 +243,6 @@ def assert_xblock_handler_called(self, *, mock_handle_xblock, response): def send_request(self, url, data): return self.client.patch(url, data=data, format="json") - def test_api_behind_feature_flag(self): - # should return 404 if the feature flag is not enabled - url = self.get_url() - - response = self.client.patch(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_xblock_handler_called_with_correct_arguments(self): self.client.login( username=self.course_instructor.username, password=self.password @@ -310,13 +277,6 @@ def assert_xblock_handler_called(self, *, mock_handle_xblock, response): def send_request(self, url, data): return self.client.delete(url) - def test_api_behind_feature_flag(self): - # should return 404 if the feature flag is not enabled - url = self.get_url() - - response = self.client.delete(url) - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_xblock_handler_called_with_correct_arguments(self): self.client.login( username=self.course_instructor.username, password=self.password diff --git a/cms/djangoapps/contentstore/rest_api/v0/views/api_heartbeat.py b/cms/djangoapps/contentstore/rest_api/v0/views/api_heartbeat.py index f8b539557e5e..f322f2360939 100644 --- a/cms/djangoapps/contentstore/rest_api/v0/views/api_heartbeat.py +++ b/cms/djangoapps/contentstore/rest_api/v0/views/api_heartbeat.py @@ -5,7 +5,6 @@ from rest_framework.response import Response from rest_framework import status from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes -import cms.djangoapps.contentstore.toggles as toggles class APIHeartBeatView(DeveloperErrorViewMixin, APIView): @@ -43,6 +42,4 @@ def get(self, request: Request): } ``` """ - if toggles.use_studio_content_api(): - return Response({'status': 'heartbeat successful'}, status=status.HTTP_200_OK) - return Response(status=status.HTTP_403_FORBIDDEN) + return Response({'status': 'heartbeat successful'}, status=status.HTTP_200_OK) diff --git a/cms/djangoapps/contentstore/rest_api/v0/views/assets.py b/cms/djangoapps/contentstore/rest_api/v0/views/assets.py index 0c0c24aeab69..a7a495256514 100644 --- a/cms/djangoapps/contentstore/rest_api/v0/views/assets.py +++ b/cms/djangoapps/contentstore/rest_api/v0/views/assets.py @@ -4,7 +4,6 @@ import logging from rest_framework.generics import CreateAPIView, RetrieveAPIView, UpdateAPIView, DestroyAPIView from django.views.decorators.csrf import csrf_exempt -from django.http import Http404 from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes from common.djangoapps.util.json_request import expect_json_in_class_view @@ -12,7 +11,6 @@ from cms.djangoapps.contentstore.api import course_author_access_required from cms.djangoapps.contentstore.asset_storage_handlers import handle_assets -import cms.djangoapps.contentstore.toggles as contentstore_toggles from ..serializers.assets import AssetSerializer from .utils import validate_request_with_serializer @@ -20,7 +18,6 @@ from openedx.core.lib.api.parsers import TypedFileUploadParser log = logging.getLogger(__name__) -toggles = contentstore_toggles @view_auth_classes() @@ -33,17 +30,6 @@ class AssetsCreateRetrieveView(DeveloperErrorViewMixin, CreateAPIView, RetrieveA serializer_class = AssetSerializer parser_classes = (JSONParser, MultiPartParser, FormParser, TypedFileUploadParser) - def dispatch(self, request, *args, **kwargs): - # TODO: probably want to refactor this to a decorator. - """ - The dispatch method of a View class handles HTTP requests in general - and calls other methods to handle specific HTTP methods. - We use this to raise a 404 if the content api is disabled. - """ - if not toggles.use_studio_content_api(): - raise Http404 - return super().dispatch(request, *args, **kwargs) - @csrf_exempt @course_author_access_required @validate_request_with_serializer @@ -66,17 +52,6 @@ class AssetsUpdateDestroyView(DeveloperErrorViewMixin, UpdateAPIView, DestroyAPI serializer_class = AssetSerializer parser_classes = (JSONParser, MultiPartParser, FormParser, TypedFileUploadParser) - def dispatch(self, request, *args, **kwargs): - # TODO: probably want to refactor this to a decorator. - """ - The dispatch method of a View class handles HTTP requests in general - and calls other methods to handle specific HTTP methods. - We use this to raise a 404 if the content api is disabled. - """ - if not toggles.use_studio_content_api(): - raise Http404 - return super().dispatch(request, *args, **kwargs) - @course_author_access_required @expect_json_in_class_view @validate_request_with_serializer diff --git a/cms/djangoapps/contentstore/rest_api/v0/views/authoring_videos.py b/cms/djangoapps/contentstore/rest_api/v0/views/authoring_videos.py index 8fb66070f3bf..f97bf7a98d16 100644 --- a/cms/djangoapps/contentstore/rest_api/v0/views/authoring_videos.py +++ b/cms/djangoapps/contentstore/rest_api/v0/views/authoring_videos.py @@ -9,7 +9,6 @@ ) from rest_framework.parsers import (MultiPartParser, FormParser) from django.views.decorators.csrf import csrf_exempt -from django.http import Http404 from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes from openedx.core.lib.api.parsers import TypedFileUploadParser @@ -27,12 +26,10 @@ VideoUploadSerializer, VideoImageSerializer, ) -import cms.djangoapps.contentstore.toggles as contentstore_toggles from .utils import validate_request_with_serializer log = logging.getLogger(__name__) -toggles = contentstore_toggles @view_auth_classes() @@ -44,17 +41,6 @@ class VideosUploadsView(DeveloperErrorViewMixin, RetrieveAPIView, DestroyAPIView """ serializer_class = VideoUploadSerializer - def dispatch(self, request, *args, **kwargs): - # TODO: probably want to refactor this to a decorator. - """ - The dispatch method of a View class handles HTTP requests in general - and calls other methods to handle specific HTTP methods. - We use this to raise a 404 if the content api is disabled. - """ - if not toggles.use_studio_content_api(): - raise Http404 - return super().dispatch(request, *args, **kwargs) - @course_author_access_required def retrieve(self, request, course_key, edx_video_id=None): # pylint: disable=arguments-differ return handle_videos(request, course_key.html_id(), edx_video_id) @@ -73,17 +59,6 @@ class VideosCreateUploadView(DeveloperErrorViewMixin, CreateAPIView): """ serializer_class = VideoUploadSerializer - def dispatch(self, request, *args, **kwargs): - # TODO: probably want to refactor this to a decorator. - """ - The dispatch method of a View class handles HTTP requests in general - and calls other methods to handle specific HTTP methods. - We use this to raise a 404 if the content api is disabled. - """ - if not toggles.use_studio_content_api(): - raise Http404 - return super().dispatch(request, *args, **kwargs) - @csrf_exempt @course_author_access_required @expect_json_in_class_view @@ -102,17 +77,6 @@ class VideoImagesView(DeveloperErrorViewMixin, CreateAPIView): serializer_class = VideoImageSerializer parser_classes = (MultiPartParser, FormParser, TypedFileUploadParser) - def dispatch(self, request, *args, **kwargs): - # TODO: probably want to refactor this to a decorator. - """ - The dispatch method of a View class handles HTTP requests in general - and calls other methods to handle specific HTTP methods. - We use this to raise a 404 if the content api is disabled. - """ - if not toggles.use_studio_content_api(): - raise Http404 - return super().dispatch(request, *args, **kwargs) - @csrf_exempt @course_author_access_required @expect_json_in_class_view @@ -133,17 +97,6 @@ class VideoEncodingsDownloadView(DeveloperErrorViewMixin, RetrieveAPIView): # does not specify a serializer class. swagger_schema = None - def dispatch(self, request, *args, **kwargs): - # TODO: probably want to refactor this to a decorator. - """ - The dispatch method of a View class handles HTTP requests in general - and calls other methods to handle specific HTTP methods. - We use this to raise a 404 if the content api is disabled. - """ - if not toggles.use_studio_content_api(): - raise Http404 - return super().dispatch(request, *args, **kwargs) - @csrf_exempt @course_author_access_required def retrieve(self, request, course_key): # pylint: disable=arguments-differ @@ -161,17 +114,6 @@ class VideoFeaturesView(DeveloperErrorViewMixin, RetrieveAPIView): # does not specify a serializer class. swagger_schema = None - def dispatch(self, request, *args, **kwargs): - # TODO: probably want to refactor this to a decorator. - """ - The dispatch method of a View class handles HTTP requests in general - and calls other methods to handle specific HTTP methods. - We use this to raise a 404 if the content api is disabled. - """ - if not toggles.use_studio_content_api(): - raise Http404 - return super().dispatch(request, *args, **kwargs) - @csrf_exempt def retrieve(self, request): # pylint: disable=arguments-differ return enabled_video_features(request) diff --git a/cms/djangoapps/contentstore/rest_api/v0/views/transcripts.py b/cms/djangoapps/contentstore/rest_api/v0/views/transcripts.py index 9a63693e12b9..3344fdbd1f43 100644 --- a/cms/djangoapps/contentstore/rest_api/v0/views/transcripts.py +++ b/cms/djangoapps/contentstore/rest_api/v0/views/transcripts.py @@ -8,7 +8,6 @@ DestroyAPIView ) from django.views.decorators.csrf import csrf_exempt -from django.http import Http404 from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes from common.djangoapps.util.json_request import expect_json_in_class_view @@ -20,7 +19,6 @@ delete_video_transcript_or_404, handle_transcript_download, ) -import cms.djangoapps.contentstore.toggles as contentstore_toggles from ..serializers import TranscriptSerializer, YoutubeTranscriptCheckSerializer, YoutubeTranscriptUploadSerializer from rest_framework.parsers import (MultiPartParser, FormParser) from openedx.core.lib.api.parsers import TypedFileUploadParser @@ -28,7 +26,6 @@ from cms.djangoapps.contentstore.rest_api.v0.views.utils import validate_request_with_serializer log = logging.getLogger(__name__) -toggles = contentstore_toggles @view_auth_classes() @@ -42,11 +39,6 @@ class TranscriptView(DeveloperErrorViewMixin, CreateAPIView, RetrieveAPIView, De serializer_class = TranscriptSerializer parser_classes = (MultiPartParser, FormParser, TypedFileUploadParser) - def dispatch(self, request, *args, **kwargs): - if not toggles.use_studio_content_api(): - raise Http404 - return super().dispatch(request, *args, **kwargs) - @csrf_exempt @course_author_access_required @expect_json_in_class_view @@ -81,11 +73,6 @@ class YoutubeTranscriptCheckView(DeveloperErrorViewMixin, RetrieveAPIView): serializer_class = YoutubeTranscriptCheckSerializer parser_classes = (MultiPartParser, FormParser, TypedFileUploadParser) - def dispatch(self, request, *args, **kwargs): - if not toggles.use_studio_content_api(): - raise Http404 - return super().dispatch(request, *args, **kwargs) - @course_author_access_required def retrieve(self, request, course_key_string): # pylint: disable=arguments-differ """ @@ -104,11 +91,6 @@ class YoutubeTranscriptUploadView(DeveloperErrorViewMixin, RetrieveAPIView): serializer_class = YoutubeTranscriptUploadSerializer parser_classes = (MultiPartParser, FormParser, TypedFileUploadParser) - def dispatch(self, request, *args, **kwargs): - if not toggles.use_studio_content_api(): - raise Http404 - return super().dispatch(request, *args, **kwargs) - @course_author_access_required def retrieve(self, request, course_key_string): # pylint: disable=arguments-differ """ diff --git a/cms/djangoapps/contentstore/rest_api/v0/views/xblock.py b/cms/djangoapps/contentstore/rest_api/v0/views/xblock.py index cc26619fb83a..8e678ae845e0 100644 --- a/cms/djangoapps/contentstore/rest_api/v0/views/xblock.py +++ b/cms/djangoapps/contentstore/rest_api/v0/views/xblock.py @@ -4,21 +4,18 @@ import logging from rest_framework.generics import RetrieveUpdateDestroyAPIView, CreateAPIView from django.views.decorators.csrf import csrf_exempt -from django.http import Http404 from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes from common.djangoapps.util.json_request import expect_json_in_class_view from cms.djangoapps.contentstore.api import course_author_access_required from cms.djangoapps.contentstore.xblock_storage_handlers import view_handlers -import cms.djangoapps.contentstore.toggles as contentstore_toggles from ..serializers import XblockSerializer from .utils import validate_request_with_serializer log = logging.getLogger(__name__) -toggles = contentstore_toggles handle_xblock = view_handlers.handle_xblock @@ -32,17 +29,6 @@ class XblockView(DeveloperErrorViewMixin, RetrieveUpdateDestroyAPIView): """ serializer_class = XblockSerializer - def dispatch(self, request, *args, **kwargs): - # TODO: probably want to refactor this to a decorator. - """ - The dispatch method of a View class handles HTTP requests in general - and calls other methods to handle specific HTTP methods. - We use this to raise a 404 if the content api is disabled. - """ - if not toggles.use_studio_content_api(): - raise Http404 - return super().dispatch(request, *args, **kwargs) - # pylint: disable=arguments-differ @course_author_access_required @expect_json_in_class_view @@ -77,17 +63,6 @@ class XblockCreateView(DeveloperErrorViewMixin, CreateAPIView): """ serializer_class = XblockSerializer - def dispatch(self, request, *args, **kwargs): - # TODO: probably want to refactor this to a decorator. - """ - The dispatch method of a View class handles HTTP requests in general - and calls other methods to handle specific HTTP methods. - We use this to raise a 404 if the content api is disabled. - """ - if not toggles.use_studio_content_api(): - raise Http404 - return super().dispatch(request, *args, **kwargs) - # pylint: disable=arguments-differ @csrf_exempt @course_author_access_required diff --git a/cms/djangoapps/contentstore/toggles.py b/cms/djangoapps/contentstore/toggles.py index af125e0d9b7a..4fcc8372c594 100644 --- a/cms/djangoapps/contentstore/toggles.py +++ b/cms/djangoapps/contentstore/toggles.py @@ -5,6 +5,7 @@ from openedx.core.djangoapps.content.search import api as search_api from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag + # .. toggle_name: FEATURES['ENABLE_EXPORT_GIT'] # .. toggle_implementation: SettingDictToggle # .. toggle_default: False @@ -160,37 +161,6 @@ def use_new_problem_editor(): return ENABLE_NEW_PROBLEM_EDITOR_FLAG.is_enabled() -# .. toggle_name: new_core_editors.use_advanced_problem_editor -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: This flag enables the use of the new core problem xblock advanced editor as the default -# .. toggle_use_cases: temporary -# .. toggle_creation_date: 2024-07-25 -# .. toggle_target_removal_date: 2024-08-31 -# .. toggle_tickets: TNL-11694 -# .. toggle_warning: -ENABLE_DEFAULT_ADVANCED_PROBLEM_EDITOR_FLAG = WaffleFlag('new_core_editors.use_advanced_problem_editor', __name__) - - -# .. toggle_name: new_editors.add_game_block_button -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: This flag enables the creation of the new games block -# .. toggle_use_cases: temporary -# .. toggle_creation_date: 2023-07-26 -# .. toggle_target_removal_date: 2023-09-31 -# .. toggle_tickets: TNL-10924 -# .. toggle_warning: -ENABLE_ADD_GAME_BLOCK_FLAG = WaffleFlag('new_editors.add_game_block_button', __name__) - - -def use_add_game_block(): - """ - Returns a boolean if add game block button is enabled - """ - return ENABLE_ADD_GAME_BLOCK_FLAG.is_enabled() - - # .. toggle_name: contentstore.individualize_anonymous_user_id # .. toggle_implementation: CourseWaffleFlag # .. toggle_default: False @@ -211,30 +181,6 @@ def individualize_anonymous_user_id(course_id): return INDIVIDUALIZE_ANONYMOUS_USER_ID.is_enabled(course_id) -# .. toggle_name: contentstore.enable_studio_content_api -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: Enables the new (experimental and unsafe!) Studio Content REST API for course authors, -# .. which provides CRUD capabilities for course content and xblock editing. -# .. Use at your own peril - you can easily delete learner data when editing running courses. -# .. This can be triggered by deleting blocks, editing subsections, problems, assignments, discussions, -# .. creating new problems or graded sections, and by other things you do. -# .. toggle_use_cases: open_edx -# .. toggle_creation_date: 2023-05-26 -# .. toggle_tickets: TNL-10208 -ENABLE_STUDIO_CONTENT_API = WaffleFlag( - f'{CONTENTSTORE_NAMESPACE}.enable_studio_content_api', - __name__, -) - - -def use_studio_content_api(): - """ - Returns a boolean if studio editing API is enabled - """ - return ENABLE_STUDIO_CONTENT_API.is_enabled() - - # .. toggle_name: new_studio_mfe.use_new_home_page # .. toggle_implementation: WaffleFlag # .. toggle_default: False diff --git a/cms/djangoapps/contentstore/views/tests/test_block.py b/cms/djangoapps/contentstore/views/tests/test_block.py index 93e382fb7fba..ef85c446c81e 100644 --- a/cms/djangoapps/contentstore/views/tests/test_block.py +++ b/cms/djangoapps/contentstore/views/tests/test_block.py @@ -560,9 +560,6 @@ def assert_xblock_info(xblock, xblock_info): else: self.assertNotIn("ancestors", response) xblock_info = get_block_info(xblock) - # TODO: remove after beta testing for the new problem editor parser - if xblock_info["category"] == "problem": - xblock_info["metadata"]["default_to_advanced"] = False self.assertEqual(xblock_info, response) diff --git a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py index 19e20804c2fb..3506825ae357 100644 --- a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py +++ b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py @@ -33,7 +33,6 @@ from xblock.fields import Scope from cms.djangoapps.contentstore.config.waffle import SHOW_REVIEW_RULES_FLAG -from cms.djangoapps.contentstore.toggles import ENABLE_DEFAULT_ADVANCED_PROBLEM_EDITOR_FLAG from cms.djangoapps.models.settings.course_grading import CourseGradingModel from cms.lib.ai_aside_summary_config import AiAsideSummaryConfig from cms.lib.xblock.upstream_sync import BadUpstream, sync_from_upstream @@ -187,11 +186,6 @@ def handle_xblock(request, usage_key_string=None): # TODO: pass fields to get_block_info and only return those with modulestore().bulk_operations(usage_key.course_key): response = get_block_info(get_xblock(usage_key, request.user)) - # TODO: remove after beta testing for the new problem editor parser - if response["category"] == "problem": - response["metadata"]["default_to_advanced"] = ( - ENABLE_DEFAULT_ADVANCED_PROBLEM_EDITOR_FLAG.is_enabled() - ) if "customReadToken" in fields: parent_children = _get_block_parent_children(get_xblock(usage_key, request.user)) response.update(parent_children) diff --git a/cms/envs/common.py b/cms/envs/common.py index 6cc193827170..9be3eb9e0956 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -1317,16 +1317,6 @@ ##### custom vendor plugin variables ##### -# .. setting_name: JS_ENV_EXTRA_CONFIG -# .. setting_default: {} -# .. setting_description: JavaScript code can access this dictionary using `process.env.JS_ENV_EXTRA_CONFIG` -# One of the current use cases for this is enabling custom TinyMCE plugins -# (TINYMCE_ADDITIONAL_PLUGINS) and overriding the TinyMCE configuration (TINYMCE_CONFIG_OVERRIDES). -# .. setting_warning: This Django setting is DEPRECATED! Starting in Sumac, Webpack will no longer -# use Django settings. Please set the JS_ENV_EXTRA_CONFIG environment variable to an equivalent JSON -# string instead. For details, see: https://github.com/openedx/edx-platform/issues/31895 -JS_ENV_EXTRA_CONFIG = json.loads(os.environ.get('JS_ENV_EXTRA_CONFIG', '{}')) - ############################### PIPELINE ####################################### PIPELINE = { @@ -1508,14 +1498,6 @@ } } -# .. setting_name: WEBPACK_CONFIG_PATH -# .. setting_default: "webpack.prod.config.js" -# .. setting_description: Path to the Webpack configuration file. Used by Paver scripts. -# .. setting_warning: This Django setting is DEPRECATED! Starting in Sumac, Webpack will no longer -# use Django settings. Please set the WEBPACK_CONFIG_PATH environment variable instead. For details, -# see: https://github.com/openedx/edx-platform/issues/31895 -WEBPACK_CONFIG_PATH = os.environ.get('WEBPACK_CONFIG_PATH', 'webpack.prod.config.js') - ############################ SERVICE_VARIANT ################################## diff --git a/cms/envs/devstack.py b/cms/envs/devstack.py index ac21c2ee01a3..63dff1fbb9c2 100644 --- a/cms/envs/devstack.py +++ b/cms/envs/devstack.py @@ -66,9 +66,6 @@ 'django.contrib.staticfiles.finders.AppDirectoriesFinder', ] -# Load development webpack donfiguration -WEBPACK_CONFIG_PATH = 'webpack.dev.config.js' - ############################ PYFS XBLOCKS SERVICE ############################# # Set configuration for Django pyfilesystem diff --git a/cms/envs/mock.yml b/cms/envs/mock.yml index 6e37b1818066..fb2233501ef4 100644 --- a/cms/envs/mock.yml +++ b/cms/envs/mock.yml @@ -501,7 +501,6 @@ IDA_LOGOUT_URI_LIST: - https://discovery.localhost/logout/ - https://commerce-coordinator.localhost/logout/ ID_VERIFICATION_SUPPORT_LINK: https://support.localhost/hc/en-us/articles/206503858-How-do-I-complete-photo-verification- -JS_ENV_EXTRA_CONFIG: {} JWT_AUTH: JWT_AUDIENCE: test_jwt_audience JWT_AUTH_COOKIE_HEADER_PAYLOAD: jwt-cookie-header-payload diff --git a/common/templates/xblock_v2/xblock_iframe.html b/common/templates/xblock_v2/xblock_iframe.html index add3c46bce55..69af639da2a8 100644 --- a/common/templates/xblock_v2/xblock_iframe.html +++ b/common/templates/xblock_v2/xblock_iframe.html @@ -180,8 +180,8 @@ - -
+ +
{{ fragment.body_html | safe }} @@ -360,7 +360,7 @@ error_message_div.html('Error: '+response.message); error_message_div.css('display', 'block'); } - }); + }); }); }); } @@ -437,21 +437,55 @@ const rootNode = document.querySelector('.xblock, .xblock-v1'); // will always return the first matching element initializeXBlockAndChildren(rootNode, () => { }); + (function() { + // If this view is rendered in an iframe within the authoring microfrontend app + // it will report the height of its contents to the parent window when the + // document loads, window resizes, or DOM mutates. + if (window !== window.parent) { + var lastHeight = window.parent[0].offsetHeight; + var lastWidth = window.parent[0].offsetWidth; - let lastHeight = -1; - function checkFrameHeight() { - const newHeight = document.documentElement.scrollHeight; - if (newHeight !== lastHeight) { - lastHeight = newHeight; + function dispatchResizeMessage(event) { + // Note: event is actually an Array of MutationRecord objects when fired from the MutationObserver + var newHeight = rootNode.scrollHeight; + var newWidth = rootNode.offsetWidth; + + window.parent.postMessage( + { + type: 'plugin.resize', + payload: { + width: newWidth, + height: newHeight, + } + }, document.referrer + ); + + lastHeight = newHeight; + lastWidth = newWidth; + + // Within the authoring microfrontend the iframe resizes to match the + // height of this document and it should never scroll. It does scroll + // ocassionally when javascript is used to focus elements on the page + // before the parent iframe has been resized to match the content + // height. This window.scrollTo is an attempt to keep the content at the + // top of the page. + window.scrollTo(0, 0); + } + + // Create an observer instance linked to the callback function + const observer = new MutationObserver(dispatchResizeMessage); + + // Start observing the target node for configured mutations + observer.observe(rootNode, { attributes: true, childList: true, subtree: true }); + + const resizeObserver = new ResizeObserver(dispatchResizeMessage); + resizeObserver.observe(rootNode); } - } - // Check the size whenever the DOM changes: - new MutationObserver(checkFrameHeight).observe(document.body, { attributes: true, childList: true, subtree: true }); - // And whenever the IFrame is resized - window.addEventListener('resize', checkFrameHeight); + }()); } window.addEventListener('load', blockFrameJS); + diff --git a/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py b/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py index b6840ca2c0b0..7b3f3f598bfe 100644 --- a/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py +++ b/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py @@ -49,6 +49,9 @@ def send_ace_message(goal, session_id): Returns true if sent, false if it absorbed an exception and did not send """ user = goal.user + if not user.has_usable_password(): + log.info(f'Goal Reminder User is disabled {user.username} course {goal.course_key}') + return False try: course = CourseOverview.get_from_id(goal.course_key) except CourseOverview.DoesNotExist: diff --git a/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py b/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py index 214fb58219ea..46d46832a6d9 100644 --- a/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py +++ b/lms/djangoapps/course_goals/management/commands/tests/test_goal_reminder_email.py @@ -1,5 +1,5 @@ """Tests for the goal_reminder_email command""" - +import uuid from datetime import datetime from botocore.exceptions import NoCredentialsError @@ -18,7 +18,7 @@ from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory -from lms.djangoapps.course_goals.management.commands.goal_reminder_email import send_email_using_ses +from lms.djangoapps.course_goals.management.commands.goal_reminder_email import send_email_using_ses, send_ace_message from lms.djangoapps.course_goals.models import CourseGoalReminderStatus from lms.djangoapps.course_goals.tests.factories import ( CourseGoalFactory, CourseGoalReminderStatusFactory, UserActivityFactory, @@ -220,6 +220,21 @@ def test_params_without_ses(self, mock_ace): assert 'override_default_channel' not in msg.options assert 'from_address' not in msg.options + @ddt.data(True, False) + @mock.patch('lms.djangoapps.course_goals.management.commands.goal_reminder_email.ace.send') + def test_goal_reminder_email_sent_to_disable_user(self, value, mock_ace): + """ + Test that the goal reminder email is not sent to disabled users. + """ + goal = self.make_valid_goal() + if value: + goal.user.set_password("12345678") + else: + goal.user.set_unusable_password() + goal.user.save() + send_ace_message(goal, str(uuid.uuid4())) + assert mock_ace.called is value + class TestGoalReminderEmailSES(TestCase): """ diff --git a/lms/djangoapps/course_home_api/outline/tests/test_view.py b/lms/djangoapps/course_home_api/outline/tests/test_view.py index 76928846f080..fd1680ba533d 100644 --- a/lms/djangoapps/course_home_api/outline/tests/test_view.py +++ b/lms/djangoapps/course_home_api/outline/tests/test_view.py @@ -31,7 +31,6 @@ from openedx.features.course_duration_limits.models import CourseDurationLimitConfig from openedx.features.course_experience import ( COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, - DISPLAY_COURSE_SOCK_FLAG, ENABLE_COURSE_GOALS ) from openedx.features.discounts.applicability import ( @@ -362,12 +361,6 @@ def test_visibility(self, is_enrolled, is_staff, course_visibility): assert (data['access_expiration'] is not None) == show_enrolled assert (data['resume_course']['url'] is not None) == show_enrolled - @ddt.data(True, False) - def test_can_show_upgrade_sock(self, sock_enabled): - with override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=sock_enabled): - response = self.client.get(self.url) - assert response.data['can_show_upgrade_sock'] == sock_enabled - def test_verified_mode(self): enrollment = CourseEnrollment.enroll(self.user, self.course.id) CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1)) diff --git a/lms/djangoapps/course_home_api/serializers.py b/lms/djangoapps/course_home_api/serializers.py index d3aa5617331e..44023c9d50db 100644 --- a/lms/djangoapps/course_home_api/serializers.py +++ b/lms/djangoapps/course_home_api/serializers.py @@ -9,7 +9,6 @@ from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link from openedx.core.djangoapps.courseware_api.utils import serialize_upgrade_info from openedx.features.content_type_gating.models import ContentTypeGatingConfig -from openedx.features.course_experience import DISPLAY_COURSE_SOCK_FLAG from openedx.features.course_experience.utils import dates_banner_should_display @@ -59,13 +58,8 @@ class VerifiedModeSerializer(ReadOnlySerializer): Requires 'course_overview', 'enrollment', and 'request' from self.context. """ - can_show_upgrade_sock = serializers.SerializerMethodField() verified_mode = serializers.SerializerMethodField() - def get_can_show_upgrade_sock(self, _): - course_overview = self.context['course_overview'] - return DISPLAY_COURSE_SOCK_FLAG.is_enabled(course_overview.id) - def get_verified_mode(self, _): """Return verified mode information, or None.""" course_overview = self.context['course_overview'] diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index 62bd6bf5c982..5dc4a7ce9107 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -795,10 +795,11 @@ def get_assignments_grades(user, course_id, cache_timeout): collected_block_structure = get_block_structure_manager(course_id).get_collected() total_bytes_in_one_mb = 1024 * 1024 - data_size_in_mbs = round(len(pickle.dumps(collected_block_structure)) / total_bytes_in_one_mb, 2) - if data_size_in_mbs < total_bytes_in_one_mb * 2: + data_size_in_bytes = len(pickle.dumps(collected_block_structure)) + if data_size_in_bytes < total_bytes_in_one_mb * 2: cache.set(cache_key, collected_block_structure, cache_timeout) else: + data_size_in_mbs = round(data_size_in_bytes / total_bytes_in_one_mb, 2) # .. custom_attribute_name: collected_block_structure_size_in_mbs # .. custom_attribute_description: contains the data chunk size in MBs. The size on which # the memcached client failed to store value in cache. diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index da31ebccce45..6303f07cf351 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -341,7 +341,7 @@ def test_index_query_counts(self): self.client.login(username=self.user.username, password=self.user_password) CourseEnrollment.enroll(self.user, course.id) - with self.assertNumQueries(154, table_ignorelist=QUERY_COUNT_TABLE_IGNORELIST): + with self.assertNumQueries(152, table_ignorelist=QUERY_COUNT_TABLE_IGNORELIST): with check_mongo_calls(3): url = reverse( 'courseware_section', diff --git a/lms/djangoapps/courseware/views/index.py b/lms/djangoapps/courseware/views/index.py index 7a9242f595ef..e3bd2b34634f 100644 --- a/lms/djangoapps/courseware/views/index.py +++ b/lms/djangoapps/courseware/views/index.py @@ -48,7 +48,6 @@ default_course_url ) from openedx.features.course_experience.url_helpers import make_learning_mfe_courseware_url -from openedx.features.course_experience.views.course_sock import CourseSockFragmentView from openedx.features.enterprise_support.api import data_sharing_consent_required from ..access import has_access @@ -454,9 +453,6 @@ def _create_courseware_context(self, request): table_of_contents['chapters'], ) - courseware_context['course_sock_fragment'] = CourseSockFragmentView().render_to_fragment( - request, course=self.course) - # entrance exam data self._add_entrance_exam_to_context(courseware_context) diff --git a/lms/envs/common.py b/lms/envs/common.py index 316dad98da3f..719e12fb8b0d 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -2806,14 +2806,6 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring } } -# .. setting_name: WEBPACK_CONFIG_PATH -# .. setting_default: "webpack.prod.config.js" -# .. setting_description: Path to the Webpack configuration file. Used by Paver scripts. -# .. setting_warning: This Django setting is DEPRECATED! Starting in Sumac, Webpack will no longer -# use Django settings. Please set the WEBPACK_CONFIG_PATH environment variable instead. For details, -# see: https://github.com/openedx/edx-platform/issues/31895 -WEBPACK_CONFIG_PATH = os.environ.get('WEBPACK_CONFIG_PATH', 'webpack.prod.config.js') - ########################## DJANGO DEBUG TOOLBAR ############################### # We don't enable Django Debug Toolbar universally, but whenever we do, we want diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py index d607073b9a29..e024e81edfef 100644 --- a/lms/envs/devstack.py +++ b/lms/envs/devstack.py @@ -136,9 +136,6 @@ def should_show_debug_toolbar(request): # lint-amnesty, pylint: disable=missing PIPELINE['SASS_ARGUMENTS'] = '--debug-info' -# Load development webpack configuration -WEBPACK_CONFIG_PATH = 'webpack.dev.config.js' - ########################### VERIFIED CERTIFICATES ################################# FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True diff --git a/lms/static/sass/_build-course.scss b/lms/static/sass/_build-course.scss index 732eb2caf888..47fde243c7f6 100644 --- a/lms/static/sass/_build-course.scss +++ b/lms/static/sass/_build-course.scss @@ -66,6 +66,3 @@ // responsive @import 'base/layouts'; // temporary spot for responsive course @import 'header'; - -// features -@import 'features/course-sock'; diff --git a/lms/static/sass/bootstrap/lms-main.scss b/lms/static/sass/bootstrap/lms-main.scss index 70bd33da28eb..1299b84fa289 100644 --- a/lms/static/sass/bootstrap/lms-main.scss +++ b/lms/static/sass/bootstrap/lms-main.scss @@ -28,7 +28,6 @@ $static-path: '../..'; @import 'features/bookmarks'; @import 'features/course-experience'; @import 'features/course-search'; -@import 'features/course-sock'; @import 'features/course-upgrade-message'; @import 'features/course-duration-limits'; diff --git a/lms/static/sass/features/_course-sock.scss b/lms/static/sass/features/_course-sock.scss deleted file mode 100644 index a3a1f1484d75..000000000000 --- a/lms/static/sass/features/_course-sock.scss +++ /dev/null @@ -1,170 +0,0 @@ -.verification-sock { - display: inline-block; - position: relative; - width: 100%; - max-width: map-get($container-max-widths, xl); - margin: $baseline auto 0; - -webkit-transition: all 0.4s ease-out; - -moz-transition: all 0.4s ease-out; - -o-transition: all 0.4s ease-out; - -ms-transition: all 0.4s ease-out; - transition: all 0.4s ease-out; - - .action-toggle-verification-sock { - @include left(50%); - @include margin-left(-1 * $baseline * 15/2); - - position: absolute; - top: (-1 * $baseline); - width: ($baseline * 15); - color: theme-color("inverse"); - background-color: theme-color("success"); - border-color: theme-color("success"); - background-image: none; - box-shadow: none; - -webkit-transition: background-color 0.5s; - transition: background-color 0.5s; - cursor: pointer; - - &.active, - &:focus, - &:hover { - color: theme-color("success"); - background-color: theme-color("inverse"); - border-color: theme-color("success"); - background-image: none; - box-shadow: none; - } - } - - .verification-main-panel { - display: none; - overflow: hidden; - border-top: 1px solid $border-color; - padding: ($baseline * 5/2) ($baseline * 2); - -webkit-transition: height ease-out; - transition: height ease-out; - - .verification-desc-panel { - color: $black-t3; - position: relative; - - h2 { - font-size: 1.5rem; - font-weight: $font-weight-bold; - } - - h3 { - font-size: 1.25rem; - } - - @media (max-width: 960px) { - .mini-cert { - display: none; - border: 1px solid $black-t0; - } - } - - .mini-cert { - @include right($baseline); - - position: absolute; - top: $baseline; - width: ($baseline * 13); - } - - .learner-story-container { - display: flex; - max-width: 630px; - - .student-image { - margin: ($baseline / 4) $baseline 0 0; - height: ($baseline * 5/2); - width: ($baseline * 5/2); - } - - .story-quote > .author { - display: block; - margin-top: ($baseline / 4); - font-weight: 600; - } - - &:not(:first-child) { - margin-top: ($baseline * 2); - } - } - - .action-upgrade-certificate { - position: absolute; - right: $baseline; - background-color: theme-color("success"); - border-color: theme-color("success"); - color: theme-color("inverse"); - background-image: none; - box-shadow: none; - cursor: pointer; - - &:hover { - background-color: theme-color("inverse"); - color: theme-color("success"); - } - - @media (max-width: 960px) { - & { - position: relative; - margin-top: ($baseline * 2); - } - } - - @media (min-width: 960px) { - &.stuck-top { - bottom: auto; - top: $baseline * (52 / 5); - } - - &.stuck-bottom { - top: auto; - bottom: $baseline * (-1 * 3/2); - } - - &.attached { - @include right($baseline); - - position: fixed; - bottom: $baseline; - top: auto; - } - } - } - } - } -} - -// Overrides for the courseware page. -.view-courseware { - .verification-sock { - margin-top: 0; - border-top: none; - border-bottom: none; - - .action-toggle-verification-sock { - top: (-1 * $baseline * 5/4); - - &:not(.active) { - color: theme-color("inverse"); - background-color: theme-color("success"); - box-shadow: none; - border: 1px solid theme-color("success"); - - &:hover { - background-color: $success-color-hover; - } - } - } - - .verification-main-panel { - border-top: 0; - border-bottom: 1px solid $border-color; - } - } -} diff --git a/lms/templates/courseware/courseware.html b/lms/templates/courseware/courseware.html index eb94fab17c91..ce9dd19b2934 100644 --- a/lms/templates/courseware/courseware.html +++ b/lms/templates/courseware/courseware.html @@ -290,7 +290,6 @@
- ${HTML(course_sock_fragment.body_html())}