From 8bd43207ca6bdc7e36514cbb41e4974e47844998 Mon Sep 17 00:00:00 2001 From: Eugene Dyudyunov Date: Thu, 5 May 2022 19:03:10 +0300 Subject: [PATCH] refactor!: switch from LegacyWaffle* to modern waffles (#30330) This is a first stage for removing the LegacyWaffle* classes. LegacyWaffleFlag usage replaced with WaffleFlag; LegacyWaffleSwitche usage replaced with WaffleSwitch; New CourseWaffleFlag added to the temporary module __future__ as FutureCourseWaffleFlag; Updated all the imports to use CourseWaffleFlag from the __future__ module; BREAKING CHANGE: A number of toggle related constants (e.g. ENABLE_ACCESSIBILITY_POLICY_PAGE) changed types. They were strings, and are now toggle instances (e.g. WaffleSwitch). Although the entire refactor should be self-contained in edx-platform, if any plugins or dependencies were directly using these constants, they will break. If this is the case, try to find a better publicized way of exposing those toggles. --- cms/djangoapps/contentstore/config/waffle.py | 50 ++----- .../contentstore/tests/test_contentstore.py | 8 +- cms/djangoapps/contentstore/toggles.py | 26 ++-- cms/djangoapps/contentstore/views/course.py | 12 +- cms/djangoapps/contentstore/views/public.py | 16 +-- .../contentstore/views/tests/test_videos.py | 20 +-- cms/djangoapps/contentstore/views/videos.py | 25 ++-- cms/djangoapps/models/settings/waffle.py | 7 +- cms/templates/widgets/footer.html | 5 +- common/djangoapps/student/views/dashboard.py | 6 +- common/lib/xmodule/xmodule/seq_module.py | 8 +- .../xmodule/video_module/video_module.py | 4 +- lms/djangoapps/course_api/__init__.py | 9 +- lms/djangoapps/course_api/blocks/toggles.py | 10 +- lms/djangoapps/course_home_api/toggles.py | 11 +- lms/djangoapps/courseware/tests/test_about.py | 9 +- .../courseware/tests/test_video_mongo.py | 5 +- lms/djangoapps/courseware/tests/test_views.py | 3 +- lms/djangoapps/courseware/toggles.py | 22 +-- lms/djangoapps/courseware/views/index.py | 19 ++- lms/djangoapps/courseware/views/views.py | 3 +- lms/djangoapps/discussion/toggles.py | 22 +-- lms/djangoapps/experiments/flags.py | 7 +- .../experiments/tests/test_flags.py | 16 +-- lms/djangoapps/experiments/views_custom.py | 8 +- lms/djangoapps/grades/api.py | 8 +- lms/djangoapps/grades/config/__init__.py | 3 +- lms/djangoapps/grades/config/waffle.py | 95 +++---------- lms/djangoapps/grades/grade_utils.py | 8 +- .../0018_add_waffle_flag_defaults.py | 7 +- .../rest_api/v1/tests/test_gradebook_views.py | 6 +- lms/djangoapps/grades/tasks.py | 4 +- .../grades/tests/test_course_grade.py | 6 +- .../grades/tests/test_course_grade_factory.py | 4 +- lms/djangoapps/grades/tests/test_services.py | 18 +-- lms/djangoapps/grades/tests/test_tasks.py | 4 +- .../tests/views/test_instructor_dashboard.py | 12 +- lms/djangoapps/instructor/toggles.py | 18 +-- .../instructor_task/config/waffle.py | 50 +++---- lms/djangoapps/ora_staff_grader/views.py | 8 +- lms/djangoapps/teams/toggles.py | 4 +- lms/djangoapps/verify_student/toggles.py | 10 +- .../core/djangoapps/course_apps/toggles.py | 8 +- .../djangoapps/course_live/config/waffle.py | 12 +- .../djangoapps/discussions/config/waffle.py | 14 +- openedx/core/djangoapps/programs/__init__.py | 11 +- openedx/core/djangoapps/schedules/config.py | 21 ++- .../djangoapps/user_api/accounts/toggles.py | 7 +- .../djangoapps/user_authn/config/waffle.py | 15 +-- .../djangoapps/user_authn/views/register.py | 8 +- .../video_pipeline/config/waffle.py | 44 ++---- .../djangoapps/waffle_utils/__future__.py | 116 ++++++++++++++++ .../core/djangoapps/waffle_utils/__init__.py | 127 +++--------------- .../waffle_utils/tests/test_init.py | 66 ++++++--- .../features/course_experience/__init__.py | 26 ++-- openedx/features/course_experience/waffle.py | 18 +-- openedx/features/discounts/applicability.py | 8 +- openedx/features/effort_estimation/toggles.py | 8 +- openedx/features/enterprise_support/utils.py | 4 +- openedx/features/learner_profile/toggles.py | 6 +- 60 files changed, 471 insertions(+), 644 deletions(-) create mode 100644 openedx/core/djangoapps/waffle_utils/__future__.py diff --git a/cms/djangoapps/contentstore/config/waffle.py b/cms/djangoapps/contentstore/config/waffle.py index 3dc567a14f..9f086943f6 100644 --- a/cms/djangoapps/contentstore/config/waffle.py +++ b/cms/djangoapps/contentstore/config/waffle.py @@ -4,52 +4,26 @@ waffle switches for the contentstore app. """ -from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace, LegacyWaffleSwitchNamespace +from edx_toggles.toggles import WaffleFlag, WaffleSwitch -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag # Namespace WAFFLE_NAMESPACE = 'studio' +LOG_PREFIX = 'Studio: ' # Switches -# TODO: Replace with WaffleSwitch(). See waffle() docstring. -ENABLE_ACCESSIBILITY_POLICY_PAGE = 'enable_policy_page' - - -def waffle(): - """ - Deprecated: Returns the namespaced, cached, audited Waffle Switch class for Studio pages. - - IMPORTANT: Do NOT copy this pattern and do NOT use this to reference new switches. - Instead, replace the string constant above with the actual switch instance. - For example:: - - ENABLE_ACCESSIBILITY_POLICY_PAGE = WaffleSwitch(f'{WAFFLE_NAMESPACE}.enable_policy_page') - """ - return LegacyWaffleSwitchNamespace(name=WAFFLE_NAMESPACE, log_prefix='Studio: ') - - -def waffle_flags(): - """ - Deprecated: Returns the namespaced, cached, audited Waffle Flag class for Studio pages. - - IMPORTANT: Do NOT copy this pattern and do NOT use this to reference new flags. - See waffle() docstring for more details. - """ - return LegacyWaffleFlagNamespace(name=WAFFLE_NAMESPACE, log_prefix='Studio: ') - +ENABLE_ACCESSIBILITY_POLICY_PAGE = WaffleSwitch( # lint-amnesty, pylint: disable=toggle-missing-annotation + f'{WAFFLE_NAMESPACE}.enable_policy_page', __name__ +) # TODO: After removing this flag, add a migration to remove waffle flag in a follow-up deployment. ENABLE_CHECKLISTS_QUALITY = CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation - waffle_namespace=waffle_flags(), - flag_name='enable_checklists_quality', - module_name=__name__, + f'{WAFFLE_NAMESPACE}.enable_checklists_quality', __name__, LOG_PREFIX ) SHOW_REVIEW_RULES_FLAG = CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation - waffle_namespace=waffle_flags(), - flag_name='show_review_rules', - module_name=__name__, + f'{WAFFLE_NAMESPACE}.show_review_rules', __name__, LOG_PREFIX ) # Waffle flag to redirect to the library authoring MFE. @@ -62,10 +36,8 @@ SHOW_REVIEW_RULES_FLAG = CourseWaffleFlag( # lint-amnesty, pylint: disable=togg # .. toggle_target_removal_date: 2020-12-31 # .. toggle_warnings: Also set settings.LIBRARY_AUTHORING_MICROFRONTEND_URL and ENABLE_LIBRARY_AUTHORING_MICROFRONTEND. # .. toggle_tickets: https://openedx.atlassian.net/wiki/spaces/COMM/pages/1545011241/BD-14+Blockstore+Powered+Content+Libraries+Taxonomies -REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND = LegacyWaffleFlag( - waffle_namespace=waffle_flags(), - flag_name='library_authoring_mfe', - module_name=__name__, +REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND = WaffleFlag( + f'{WAFFLE_NAMESPACE}.library_authoring_mfe', __name__, LOG_PREFIX ) @@ -80,4 +52,4 @@ REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND = LegacyWaffleFlag( # .. toggle_target_removal_date: 2021-12-31 # .. toggle_warnings: Flag course_experience.relative_dates should also be active for relative dates functionalities to work. # .. toggle_tickets: https://openedx.atlassian.net/browse/AA-844 -CUSTOM_RELATIVE_DATES = CourseWaffleFlag(WAFFLE_NAMESPACE, 'custom_relative_dates', module_name=__name__,) +CUSTOM_RELATIVE_DATES = CourseWaffleFlag(f'{WAFFLE_NAMESPACE}.custom_relative_dates', __name__) diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index 5a37a96cce..03855403a9 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -1055,9 +1055,7 @@ class MiscCourseTests(ContentStoreTestCase): resp = self.client.get_html('/c4x/InvalidOrg/InvalidCourse/asset/invalid.png') self.assertEqual(resp.status_code, 404) - @override_switch( - f'{waffle.WAFFLE_NAMESPACE}.{waffle.ENABLE_ACCESSIBILITY_POLICY_PAGE}', - active=False) + @override_switch(waffle.ENABLE_ACCESSIBILITY_POLICY_PAGE.name, active=False) def test_disabled_accessibility_page(self): """ Test that accessibility page returns 404 when waffle switch is disabled @@ -2191,9 +2189,7 @@ class EntryPageTestCase(TestCase): # Logout redirects. self._test_page("/logout", 200) - @override_switch( - f'{waffle.WAFFLE_NAMESPACE}.{waffle.ENABLE_ACCESSIBILITY_POLICY_PAGE}', - active=True) + @override_switch(waffle.ENABLE_ACCESSIBILITY_POLICY_PAGE.name, active=True) def test_accessibility(self): self._test_page('/accessibility') diff --git a/cms/djangoapps/contentstore/toggles.py b/cms/djangoapps/contentstore/toggles.py index 2cda1199d8..8e2abc6419 100644 --- a/cms/djangoapps/contentstore/toggles.py +++ b/cms/djangoapps/contentstore/toggles.py @@ -1,7 +1,7 @@ """ CMS feature toggles. """ -from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace, SettingDictToggle, WaffleFlag +from edx_toggles.toggles import SettingDictToggle, WaffleFlag # .. toggle_name: FEATURES['ENABLE_EXPORT_GIT'] # .. toggle_implementation: SettingDictToggle @@ -19,23 +19,23 @@ EXPORT_GIT = SettingDictToggle( ) # Namespace for studio dashboard waffle flags. -WAFFLE_NAMESPACE = 'contentstore' -WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name=WAFFLE_NAMESPACE, log_prefix='Contentstore: ') +CONTENTSTORE_NAMESPACE = 'contentstore' +CONTENTSTORE_LOG_PREFIX = 'Contentstore: ' -# .. toggle_name: split_library_on_studio_dashboard +# .. toggle_name: contentstore.split_library_on_studio_dashboard # .. toggle_implementation: WaffleFlag # .. toggle_default: False # .. toggle_description: Enables data new view for library on studio dashboard. # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2020-07-8 # .. toggle_tickets: TNL-7536 -SPLIT_LIBRARY_ON_DASHBOARD = LegacyWaffleFlag( - waffle_namespace=LegacyWaffleFlagNamespace(name=WAFFLE_NAMESPACE), - flag_name='split_library_on_studio_dashboard', - module_name=__name__ +SPLIT_LIBRARY_ON_DASHBOARD = WaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.split_library_on_studio_dashboard', + __name__, + CONTENTSTORE_LOG_PREFIX, ) -# .. toggle_name: bypass_olx_failure +# .. toggle_name: contentstore.bypass_olx_failure # .. toggle_implementation: WaffleFlag # .. toggle_default: False # .. toggle_description: Enables bypassing olx validation failures during course import. @@ -43,10 +43,10 @@ SPLIT_LIBRARY_ON_DASHBOARD = LegacyWaffleFlag( # .. toggle_creation_date: 2021-04-15 # .. toggle_target_removal_date: 2021-05-15 # .. toggle_tickets: TNL-8214 -BYPASS_OLX_FAILURE = LegacyWaffleFlag( - waffle_namespace=LegacyWaffleFlagNamespace(name=WAFFLE_NAMESPACE), - flag_name='bypass_olx_failure', - module_name=__name__ +BYPASS_OLX_FAILURE = WaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.bypass_olx_failure', + __name__, + CONTENTSTORE_LOG_PREFIX, ) diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 7643a6a1e2..10b78596ae 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -25,7 +25,7 @@ from django.utils.translation import gettext as _ from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.http import require_GET, require_http_methods from edx_django_utils.monitoring import function_trace -from edx_toggles.toggles import LegacyWaffleSwitchNamespace +from edx_toggles.toggles import WaffleSwitch from milestones import api as milestones_api from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey @@ -72,7 +72,6 @@ from openedx.core.lib.courses import course_image_url from openedx.features.content_type_gating.models import ContentTypeGatingConfig from openedx.features.content_type_gating.partitions import CONTENT_TYPE_GATING_SCHEME from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML -from openedx.features.course_experience.waffle import waffle as course_experience_waffle from xmodule.contentstore.content import StaticContent # lint-amnesty, pylint: disable=wrong-import-order from xmodule.course_module import CourseBlock, DEFAULT_START_DATE, CourseFields # lint-amnesty, pylint: disable=wrong-import-order from xmodule.error_module import ErrorBlock # lint-amnesty, pylint: disable=wrong-import-order @@ -131,6 +130,9 @@ __all__ = ['course_info_handler', 'course_handler', 'course_listing', 'get_course_and_check_access'] WAFFLE_NAMESPACE = 'studio_home' +ENABLE_GLOBAL_STAFF_OPTIMIZATION = WaffleSwitch( # lint-amnesty, pylint: disable=toggle-missing-annotation + f'{WAFFLE_NAMESPACE}.enable_global_staff_optimization', __name__ +) class AccessListFallback(Exception): @@ -518,8 +520,7 @@ def course_listing(request): List all courses and libraries available to the logged in user """ - optimization_enabled = GlobalStaff().has_user(request.user) and \ - LegacyWaffleSwitchNamespace(name=WAFFLE_NAMESPACE).is_enabled('enable_global_staff_optimization') + optimization_enabled = GlobalStaff().has_user(request.user) and ENABLE_GLOBAL_STAFF_OPTIMIZATION.is_enabled() org = request.GET.get('org', '') if optimization_enabled else None courses_iter, in_process_course_actions = get_courses_accessible_to_user(request, org) @@ -1150,8 +1151,7 @@ def settings_handler(request, course_key_string): # lint-amnesty, pylint: disab 'EDITABLE_SHORT_DESCRIPTION', settings.FEATURES.get('EDITABLE_SHORT_DESCRIPTION', True) ) - sidebar_html_enabled = course_experience_waffle().is_enabled(ENABLE_COURSE_ABOUT_SIDEBAR_HTML) - # self_paced_enabled = SelfPacedConfiguration.current().enabled + sidebar_html_enabled = ENABLE_COURSE_ABOUT_SIDEBAR_HTML.is_enabled() verified_mode = CourseMode.verified_mode_for_course(course_key, include_expired=True) upgrade_deadline = (verified_mode and verified_mode.expiration_datetime and diff --git a/cms/djangoapps/contentstore/views/public.py b/cms/djangoapps/contentstore/views/public.py index 2d5e69bc23..f4d1f7c778 100644 --- a/cms/djangoapps/contentstore/views/public.py +++ b/cms/djangoapps/contentstore/views/public.py @@ -2,15 +2,15 @@ Public views """ +from urllib.parse import quote_plus from django.conf import settings +from django.http.response import Http404 from django.shortcuts import redirect -from urllib.parse import quote_plus # lint-amnesty, pylint: disable=wrong-import-order -from waffle.decorators import waffle_switch from common.djangoapps.edxmako.shortcuts import render_to_response -from ..config import waffle +from ..config.waffle import ENABLE_ACCESSIBILITY_POLICY_PAGE __all__ = [ 'register_redirect_to_lms', 'login_redirect_to_lms', 'howitworks', 'accessibility', @@ -69,12 +69,12 @@ def howitworks(request): return render_to_response('howitworks.html', {}) -@waffle_switch(f'{waffle.WAFFLE_NAMESPACE}.{waffle.ENABLE_ACCESSIBILITY_POLICY_PAGE}') def accessibility(request): """ Display the accessibility accommodation form. """ - - return render_to_response('accessibility.html', { - 'language_code': request.LANGUAGE_CODE - }) + if ENABLE_ACCESSIBILITY_POLICY_PAGE.is_enabled(): + return render_to_response('accessibility.html', { + 'language_code': request.LANGUAGE_CODE + }) + raise Http404 diff --git a/cms/djangoapps/contentstore/views/tests/test_videos.py b/cms/djangoapps/contentstore/views/tests/test_videos.py index 91b4d4b305..5b116094df 100644 --- a/cms/djangoapps/contentstore/views/tests/test_videos.py +++ b/cms/djangoapps/contentstore/views/tests/test_videos.py @@ -16,7 +16,6 @@ import ddt import pytz from django.conf import settings from django.test.utils import override_settings -from edx_toggles.toggles import LegacyWaffleSwitch from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch from edxval.api import ( create_or_update_transcript_preferences, @@ -36,7 +35,6 @@ from openedx.core.djangoapps.profile_images.tests.helpers import make_image_file from openedx.core.djangoapps.video_pipeline.config.waffle import ( DEPRECATE_YOUTUBE, ENABLE_DEVSTACK_VIDEO_UPLOADS, - waffle_flags ) from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order @@ -45,15 +43,12 @@ from ..videos import ( ENABLE_VIDEO_UPLOAD_PAGINATION, KEY_EXPIRATION_IN_SECONDS, VIDEO_IMAGE_UPLOAD_ENABLED, - WAFFLE_SWITCHES, StatusDisplayStrings, TranscriptProvider, _get_default_video_image_url, convert_video_status ) -VIDEO_IMAGE_UPLOAD_ENABLED_SWITCH = LegacyWaffleSwitch(WAFFLE_SWITCHES, VIDEO_IMAGE_UPLOAD_ENABLED) # lint-amnesty, pylint: disable=toggle-missing-annotation - class VideoUploadTestBase: """ @@ -546,7 +541,7 @@ class VideosHandlerTestCase(VideoUploadTestMixin, VideoUploadPostTestsMixin, Cou @override_settings(AWS_ACCESS_KEY_ID='test_key_id', AWS_SECRET_ACCESS_KEY='test_secret', AWS_SECURITY_TOKEN='token') @patch('boto.s3.key.Key') @patch('boto.s3.connection.S3Connection') - @override_flag(waffle_flags()[ENABLE_DEVSTACK_VIDEO_UPLOADS].name, active=True) + @override_flag(ENABLE_DEVSTACK_VIDEO_UPLOADS.name, active=True) def test_devstack_upload_connection(self, mock_conn, mock_key): files = [{'file_name': 'first.mp4', 'content_type': 'video/mp4'}] mock_key_instances = [ @@ -651,9 +646,8 @@ class VideosHandlerTestCase(VideoUploadTestMixin, VideoUploadPostTestsMixin, Cou # expected args to be passed to `set_metadata`. expected_args = ('course_video_upload_token', self.test_token) - DEPRECATE_YOUTUBE_FLAG = waffle_flags()[DEPRECATE_YOUTUBE] with patch.object(WaffleFlagCourseOverrideModel, 'override_value', return_value=data['course_override']): - with override_flag(DEPRECATE_YOUTUBE_FLAG.name, active=data['global_waffle']): + with override_flag(DEPRECATE_YOUTUBE.name, active=data['global_waffle']): response = self.client.post( self.url, json.dumps({'files': [file_data]}), @@ -911,7 +905,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase): self.assertIn('error', response) self.assertEqual(response['error'], error_message) - @override_waffle_switch(VIDEO_IMAGE_UPLOAD_ENABLED_SWITCH, False) + @override_waffle_switch(VIDEO_IMAGE_UPLOAD_ENABLED, False) def test_video_image_upload_disabled(self): """ Tests the video image upload when the feature is disabled. @@ -920,7 +914,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase): response = self.client.post(video_image_upload_url, {'file': 'dummy_file'}, format='multipart') self.assertEqual(response.status_code, 404) - @override_waffle_switch(VIDEO_IMAGE_UPLOAD_ENABLED_SWITCH, True) + @override_waffle_switch(VIDEO_IMAGE_UPLOAD_ENABLED, True) def test_video_image(self): """ Test video image is saved. @@ -942,7 +936,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase): self.assertNotEqual(image_url1, image_url2) - @override_waffle_switch(VIDEO_IMAGE_UPLOAD_ENABLED_SWITCH, True) + @override_waffle_switch(VIDEO_IMAGE_UPLOAD_ENABLED, True) def test_video_image_no_file(self): """ Test that an error error message is returned if upload request is incorrect. @@ -951,7 +945,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase): response = self.client.post(video_image_upload_url, {}) self.verify_error_message(response, 'An image file is required.') - @override_waffle_switch(VIDEO_IMAGE_UPLOAD_ENABLED_SWITCH, True) + @override_waffle_switch(VIDEO_IMAGE_UPLOAD_ENABLED, True) def test_no_video_image(self): """ Test image url is set to None if no video image. @@ -1133,7 +1127,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase): ) ) @ddt.unpack - @override_waffle_switch(VIDEO_IMAGE_UPLOAD_ENABLED_SWITCH, True) + @override_waffle_switch(VIDEO_IMAGE_UPLOAD_ENABLED, True) def test_video_image_validation_message(self, image_data, error_message): """ Test video image validation gives proper error message. diff --git a/cms/djangoapps/contentstore/views/videos.py b/cms/djangoapps/contentstore/views/videos.py index 16ab6e4c7c..c39add5894 100644 --- a/cms/djangoapps/contentstore/views/videos.py +++ b/cms/djangoapps/contentstore/views/videos.py @@ -21,7 +21,7 @@ from django.urls import reverse from django.utils.translation import gettext as _ from django.utils.translation import gettext_noop from django.views.decorators.http import require_GET, require_http_methods, require_POST -from edx_toggles.toggles import LegacyWaffleFlagNamespace, LegacyWaffleSwitchNamespace +from edx_toggles.toggles import WaffleSwitch from edxval.api import ( SortDirection, VideoSortField, @@ -49,9 +49,8 @@ from openedx.core.djangoapps.video_config.models import VideoTranscriptEnabledFl from openedx.core.djangoapps.video_pipeline.config.waffle import ( DEPRECATE_YOUTUBE, ENABLE_DEVSTACK_VIDEO_UPLOADS, - waffle_flags ) -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag from openedx.core.lib.api.view_utils import view_auth_classes from xmodule.video_module.transcripts_utils import Transcript # lint-amnesty, pylint: disable=wrong-import-order @@ -72,18 +71,17 @@ LOGGER = logging.getLogger(__name__) # Waffle switches namespace for videos WAFFLE_NAMESPACE = 'videos' -WAFFLE_SWITCHES = LegacyWaffleSwitchNamespace(name=WAFFLE_NAMESPACE) # Waffle switch for enabling/disabling video image upload feature -VIDEO_IMAGE_UPLOAD_ENABLED = 'video_image_upload_enabled' +VIDEO_IMAGE_UPLOAD_ENABLED = WaffleSwitch( # lint-amnesty, pylint: disable=toggle-missing-annotation + f'{WAFFLE_NAMESPACE}.video_image_upload_enabled', __name__ +) # Waffle flag namespace for studio -WAFFLE_STUDIO_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='studio') +WAFFLE_STUDIO_FLAG_NAMESPACE = 'studio' ENABLE_VIDEO_UPLOAD_PAGINATION = CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation - waffle_namespace=WAFFLE_STUDIO_FLAG_NAMESPACE, - flag_name='enable_video_upload_pagination', - module_name=__name__, + f'{WAFFLE_STUDIO_FLAG_NAMESPACE}.enable_video_upload_pagination', __name__ ) # Default expiration, in seconds, of one-time URLs used for uploading videos. KEY_EXPIRATION_IN_SECONDS = 86400 @@ -245,7 +243,7 @@ def video_images_handler(request, course_key_string, edx_video_id=None): """Function to handle image files""" # respond with a 404 if image upload is not enabled. - if not WAFFLE_SWITCHES.is_enabled(VIDEO_IMAGE_UPLOAD_ENABLED): + if not VIDEO_IMAGE_UPLOAD_ENABLED.is_enabled(): return HttpResponseNotFound() if 'file' not in request.FILES: @@ -634,7 +632,7 @@ def videos_index_html(course, pagination_conf=None): 'video_supported_file_formats': list(VIDEO_SUPPORTED_FILE_FORMATS.keys()), 'video_upload_max_file_size': VIDEO_UPLOAD_MAX_FILE_SIZE_GB, 'video_image_settings': { - 'video_image_upload_enabled': WAFFLE_SWITCHES.is_enabled(VIDEO_IMAGE_UPLOAD_ENABLED), + 'video_image_upload_enabled': VIDEO_IMAGE_UPLOAD_ENABLED.is_enabled(), 'max_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MAX_BYTES'], 'min_size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MIN_BYTES'], 'max_width': settings.VIDEO_IMAGE_MAX_WIDTH, @@ -750,12 +748,11 @@ def videos_post(course, request): ('course_key', str(course.id)), ] - deprecate_youtube = waffle_flags()[DEPRECATE_YOUTUBE] course_video_upload_token = course.video_upload_pipeline.get('course_video_upload_token') # Only include `course_video_upload_token` if youtube has not been deprecated # for this course. - if not deprecate_youtube.is_enabled(course.id) and course_video_upload_token: + if not DEPRECATE_YOUTUBE.is_enabled(course.id) and course_video_upload_token: metadata_list.append(('course_video_upload_token', course_video_upload_token)) is_video_transcript_enabled = VideoTranscriptEnabledFlag.feature_enabled(course.id) @@ -791,7 +788,7 @@ def storage_service_bucket(): """ Returns an S3 bucket for video upload. """ - if waffle_flags()[ENABLE_DEVSTACK_VIDEO_UPLOADS].is_enabled(): + if ENABLE_DEVSTACK_VIDEO_UPLOADS.is_enabled(): params = { 'aws_access_key_id': settings.AWS_ACCESS_KEY_ID, 'aws_secret_access_key': settings.AWS_SECRET_ACCESS_KEY, diff --git a/cms/djangoapps/models/settings/waffle.py b/cms/djangoapps/models/settings/waffle.py index 8d9f069802..1df0c471b0 100644 --- a/cms/djangoapps/models/settings/waffle.py +++ b/cms/djangoapps/models/settings/waffle.py @@ -1,7 +1,8 @@ """ Togglable settings for Course Grading behavior """ -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag + WAFFLE_NAMESPACE = 'grades' @@ -9,9 +10,7 @@ WAFFLE_NAMESPACE = 'grades' MATERIAL_RECOMPUTE_ONLY = 'MATERIAL_RECOMPUTE_ONLY' MATERIAL_RECOMPUTE_ONLY_FLAG = CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation - waffle_namespace=WAFFLE_NAMESPACE, - flag_name=MATERIAL_RECOMPUTE_ONLY, - module_name=__name__, + f'{WAFFLE_NAMESPACE}.{MATERIAL_RECOMPUTE_ONLY}', __name__ ) diff --git a/cms/templates/widgets/footer.html b/cms/templates/widgets/footer.html index 36671427e3..b6c7d2f45e 100644 --- a/cms/templates/widgets/footer.html +++ b/cms/templates/widgets/footer.html @@ -6,7 +6,7 @@ from django.urls import reverse from datetime import datetime from django.conf import settings import pytz -from cms.djangoapps.contentstore.config.waffle import waffle, ENABLE_ACCESSIBILITY_POLICY_PAGE +from cms.djangoapps.contentstore.config.waffle import ENABLE_ACCESSIBILITY_POLICY_PAGE from openedx.core.djangolib.markup import HTML, Text %> @@ -30,7 +30,7 @@ from openedx.core.djangolib.markup import HTML, Text ${_("Privacy Policy")} % endif - % if waffle().is_enabled(ENABLE_ACCESSIBILITY_POLICY_PAGE): + % if ENABLE_ACCESSIBILITY_POLICY_PAGE.is_enabled(): @@ -62,4 +62,3 @@ from openedx.core.djangolib.markup import HTML, Text - diff --git a/common/djangoapps/student/views/dashboard.py b/common/djangoapps/student/views/dashboard.py index 1c738704ed..92f0760af0 100644 --- a/common/djangoapps/student/views/dashboard.py +++ b/common/djangoapps/student/views/dashboard.py @@ -16,7 +16,7 @@ from django.utils.translation import gettext as _ from django.views.decorators.csrf import ensure_csrf_cookie from edx_django_utils import monitoring as monitoring_utils from edx_django_utils.plugins import get_plugins_view_context -from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace +from edx_toggles.toggles import WaffleFlag from opaque_keys.edx.keys import CourseKey from pytz import UTC @@ -61,7 +61,7 @@ from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disa log = logging.getLogger("edx.student") -experiments_namespace = LegacyWaffleFlagNamespace(name='student.experiments') +EXPERIMENTS_NAMESPACE = 'student.experiments' def get_org_black_and_whitelist_for_site(): @@ -652,7 +652,7 @@ def student_dashboard(request): # lint-amnesty, pylint: disable=too-many-statem inverted_programs = meter.invert_programs() urls, programs_data = {}, {} - bundles_on_dashboard_flag = LegacyWaffleFlag(experiments_namespace, 'bundles_on_dashboard', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation + bundles_on_dashboard_flag = WaffleFlag(f'{EXPERIMENTS_NAMESPACE}.bundles_on_dashboard', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation # TODO: Delete this code and the relevant HTML code after testing LEARNER-3072 is complete if bundles_on_dashboard_flag.is_enabled() and inverted_programs and list(inverted_programs.items()): diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index 3c15b9c239..d46ca96485 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -22,7 +22,7 @@ from xblock.core import XBlock from xblock.exceptions import NoSuchServiceError from xblock.fields import Boolean, Integer, List, Scope, String -from edx_toggles.toggles import LegacyWaffleFlag, SettingDictToggle +from edx_toggles.toggles import WaffleFlag, SettingDictToggle from xmodule.util.xmodule_django import add_webpack_to_fragment from xmodule.x_module import ( HTMLSnippet, @@ -58,10 +58,8 @@ class_priority = ['video', 'problem'] # `django.utils.translation.ugettext_noop` because Django cannot be imported in this file _ = lambda text: text -TIMED_EXAM_GATING_WAFFLE_FLAG = LegacyWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation - waffle_namespace="xmodule", - flag_name='rev_1377_rollout', - module_name=__name__, +TIMED_EXAM_GATING_WAFFLE_FLAG = WaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation + 'xmodule.rev_1377_rollout', __name__ ) # .. toggle_name: FEATURES['SHOW_PROGRESS_BAR'] diff --git a/common/lib/xmodule/xmodule/video_module/video_module.py b/common/lib/xmodule/xmodule/video_module/video_module.py index f4516a74d8..833c4af8fd 100644 --- a/common/lib/xmodule/xmodule/video_module/video_module.py +++ b/common/lib/xmodule/xmodule/video_module/video_module.py @@ -31,7 +31,7 @@ from xblock.runtime import KvsFieldData from common.djangoapps.xblock_django.constants import ATTR_KEY_REQUEST_COUNTRY_CODE from openedx.core.djangoapps.video_config.models import HLSPlaybackEnabledFlag, CourseYoutubeBlockedFlag -from openedx.core.djangoapps.video_pipeline.config.waffle import DEPRECATE_YOUTUBE, waffle_flags +from openedx.core.djangoapps.video_pipeline.config.waffle import DEPRECATE_YOUTUBE from openedx.core.lib.cache_utils import request_cached from openedx.core.lib.license import LicenseMixin from xmodule.contentstore.content import StaticContent @@ -198,7 +198,7 @@ class VideoBlock( # check if youtube has been deprecated and hls as primary playback # is enabled for this course - return waffle_flags()[DEPRECATE_YOUTUBE].is_enabled(self.location.course_key) + return DEPRECATE_YOUTUBE.is_enabled(self.location.course_key) def youtube_disabled_for_course(self): # lint-amnesty, pylint: disable=missing-function-docstring if not self.location.context_key.is_course: diff --git a/lms/djangoapps/course_api/__init__.py b/lms/djangoapps/course_api/__init__.py index 340f67e2e1..e2e1b30451 100644 --- a/lms/djangoapps/course_api/__init__.py +++ b/lms/djangoapps/course_api/__init__.py @@ -1,9 +1,10 @@ """ Course API """ -from edx_toggles.toggles import LegacyWaffleSwitch, LegacyWaffleSwitchNamespace +from edx_toggles.toggles import WaffleSwitch -WAFFLE_SWITCH_NAMESPACE = LegacyWaffleSwitchNamespace(name='course_list_api_rate_limit') + +WAFFLE_SWITCH_NAMESPACE = 'course_list_api_rate_limit' # .. toggle_name: course_list_api_rate_limit.rate_limit_2 # .. toggle_implementation: WaffleSwitch @@ -13,7 +14,7 @@ WAFFLE_SWITCH_NAMESPACE = LegacyWaffleSwitchNamespace(name='course_list_api_rate # .. toggle_use_cases: circuit_breaker # .. toggle_creation_date: 2018-06-12 # .. toggle_tickets: https://openedx.atlassian.net/browse/LEARNER-5527 -USE_RATE_LIMIT_2_FOR_COURSE_LIST_API = LegacyWaffleSwitch(WAFFLE_SWITCH_NAMESPACE, 'rate_limit_2', __name__) +USE_RATE_LIMIT_2_FOR_COURSE_LIST_API = WaffleSwitch(f'{WAFFLE_SWITCH_NAMESPACE}.rate_limit_2', __name__) # .. toggle_name: course_list_api_rate_limit.rate_limit_10 # .. toggle_implementation: WaffleSwitch # .. toggle_default: False @@ -22,4 +23,4 @@ USE_RATE_LIMIT_2_FOR_COURSE_LIST_API = LegacyWaffleSwitch(WAFFLE_SWITCH_NAMESPAC # .. toggle_use_cases: circuit_breaker # .. toggle_creation_date: 2018-06-12 # .. toggle_tickets: https://openedx.atlassian.net/browse/LEARNER-5527 -USE_RATE_LIMIT_10_FOR_COURSE_LIST_API = LegacyWaffleSwitch(WAFFLE_SWITCH_NAMESPACE, 'rate_limit_10', __name__) +USE_RATE_LIMIT_10_FOR_COURSE_LIST_API = WaffleSwitch(f'{WAFFLE_SWITCH_NAMESPACE}.rate_limit_10', __name__) diff --git a/lms/djangoapps/course_api/blocks/toggles.py b/lms/djangoapps/course_api/blocks/toggles.py index 117c295394..35a38ffeaa 100644 --- a/lms/djangoapps/course_api/blocks/toggles.py +++ b/lms/djangoapps/course_api/blocks/toggles.py @@ -3,9 +3,9 @@ Toggles for Course API. """ -from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace +from edx_toggles.toggles import WaffleFlag -COURSE_BLOCKS_API_NAMESPACE = LegacyWaffleFlagNamespace(name='course_blocks_api') +COURSE_BLOCKS_API_NAMESPACE = 'course_blocks_api' # .. toggle_name: course_blocks_api.hide_access_denials # .. toggle_implementation: WaffleFlag @@ -15,8 +15,6 @@ COURSE_BLOCKS_API_NAMESPACE = LegacyWaffleFlagNamespace(name='course_blocks_api' # .. toggle_creation_date: 2019-09-27 # .. toggle_target_removal_date: None # .. toggle_warnings: This temporary feature toggle does not have a target removal date. -HIDE_ACCESS_DENIALS_FLAG = LegacyWaffleFlag( - waffle_namespace=COURSE_BLOCKS_API_NAMESPACE, - flag_name='hide_access_denials', - module_name=__name__, +HIDE_ACCESS_DENIALS_FLAG = WaffleFlag( + f'{COURSE_BLOCKS_API_NAMESPACE}.hide_access_denials', __name__ ) diff --git a/lms/djangoapps/course_home_api/toggles.py b/lms/djangoapps/course_home_api/toggles.py index bc47acf30b..afeba72954 100644 --- a/lms/djangoapps/course_home_api/toggles.py +++ b/lms/djangoapps/course_home_api/toggles.py @@ -2,14 +2,13 @@ Toggles for course home experience. """ -from edx_toggles.toggles import LegacyWaffleFlagNamespace +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +WAFFLE_FLAG_NAMESPACE = 'course_home' -WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='course_home') - -COURSE_HOME_MICROFRONTEND_PROGRESS_TAB = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'course_home_mfe_progress_tab', # lint-amnesty, pylint: disable=toggle-missing-annotation - __name__) +COURSE_HOME_MICROFRONTEND_PROGRESS_TAB = CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation + f'{WAFFLE_FLAG_NAMESPACE}.course_home_mfe_progress_tab', __name__ +) def course_home_mfe_progress_tab_is_active(course_key): diff --git a/lms/djangoapps/courseware/tests/test_about.py b/lms/djangoapps/courseware/tests/test_about.py index 244a5fd2b3..cbe59e042c 100644 --- a/lms/djangoapps/courseware/tests/test_about.py +++ b/lms/djangoapps/courseware/tests/test_about.py @@ -31,7 +31,6 @@ from common.djangoapps.course_modes.models import CourseMode from openedx.core.djangoapps.models.course_details import CourseDetails from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, course_home_url from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML -from openedx.features.course_experience.waffle import WAFFLE_NAMESPACE as COURSE_EXPERIENCE_WAFFLE_NAMESPACE from common.djangoapps.student.tests.factories import CourseEnrollmentAllowedFactory, UserFactory from common.djangoapps.track.tests import EventTrackingTestCase from common.djangoapps.util.milestones_helpers import get_prerequisite_courses_display, set_prerequisite_courses @@ -397,13 +396,7 @@ class AboutSidebarHTMLTestCase(SharedModuleStoreTestCase): ) @ddt.unpack def test_html_sidebar_enabled(self, itemfactory_display_name, itemfactory_data, waffle_switch_value): - with override_switch( - '{}.{}'.format( - COURSE_EXPERIENCE_WAFFLE_NAMESPACE, - ENABLE_COURSE_ABOUT_SIDEBAR_HTML - ), - active=waffle_switch_value - ): + with override_switch(ENABLE_COURSE_ABOUT_SIDEBAR_HTML.name, active=waffle_switch_value): if itemfactory_display_name: ItemFactory.create( category="about", diff --git a/lms/djangoapps/courseware/tests/test_video_mongo.py b/lms/djangoapps/courseware/tests/test_video_mongo.py index 3b79f238ea..764d60ed4a 100644 --- a/lms/djangoapps/courseware/tests/test_video_mongo.py +++ b/lms/djangoapps/courseware/tests/test_video_mongo.py @@ -36,7 +36,7 @@ from waffle.testutils import override_flag from common.djangoapps.xblock_django.constants import ATTR_KEY_REQUEST_COUNTRY_CODE from lms.djangoapps.courseware.tests.helpers import get_context_dict_from_string -from openedx.core.djangoapps.video_pipeline.config.waffle import DEPRECATE_YOUTUBE, waffle_flags +from openedx.core.djangoapps.video_pipeline.config.waffle import DEPRECATE_YOUTUBE from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel from openedx.core.djangolib.testing.utils import CacheIsolationTestCase from xmodule.contentstore.content import StaticContent # lint-amnesty, pylint: disable=wrong-import-order @@ -1177,9 +1177,8 @@ class TestGetHtmlMethod(BaseTestVideoXBlock): video_xml = ''.format( data['youtube'] ) - DEPRECATE_YOUTUBE_FLAG = waffle_flags()[DEPRECATE_YOUTUBE] with patch.object(WaffleFlagCourseOverrideModel, 'override_value', return_value=data['course_override']): - with override_flag(DEPRECATE_YOUTUBE_FLAG.name, active=data['waffle_enabled']): + with override_flag(DEPRECATE_YOUTUBE.name, active=data['waffle_enabled']): self.initialize_block(data=video_xml, metadata=metadata) context = self.item_descriptor.render(STUDENT_VIEW).content assert '"prioritizeHls": {}'.format(data['result']) in context diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index b84f51b3a4..c9fa87a0e1 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -76,7 +76,6 @@ from lms.djangoapps.courseware.testutils import RenderXBlockTestMixin from lms.djangoapps.courseware.toggles import COURSEWARE_OPTIMIZED_RENDER_XBLOCK from lms.djangoapps.courseware.user_state_client import DjangoXBlockUserStateClient from lms.djangoapps.grades.config.waffle import ASSUME_ZERO_GRADE_IF_ABSENT -from lms.djangoapps.grades.config.waffle import waffle_switch as grades_waffle_switch from lms.djangoapps.instructor.access import allow_access from lms.djangoapps.verify_student.services import IDVerificationService from openedx.core.djangoapps.catalog.tests.factories import CourseFactory as CatalogCourseFactory @@ -1524,7 +1523,7 @@ class ProgressPageTests(ProgressPageBaseTests): def test_progress_queries(self, enable_waffle, initial, subsequent): ContentTypeGatingConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1)) self.setup_course() - with override_waffle_switch(grades_waffle_switch(ASSUME_ZERO_GRADE_IF_ABSENT), active=enable_waffle): + with override_waffle_switch(ASSUME_ZERO_GRADE_IF_ABSENT, active=enable_waffle): with self.assertNumQueries( initial, table_ignorelist=QUERY_COUNT_TABLE_IGNORELIST ), check_mongo_calls(2): diff --git a/lms/djangoapps/courseware/toggles.py b/lms/djangoapps/courseware/toggles.py index 63babaceac..4db48fa9cc 100644 --- a/lms/djangoapps/courseware/toggles.py +++ b/lms/djangoapps/courseware/toggles.py @@ -2,12 +2,12 @@ Toggles for courseware in-course experience. """ -from edx_toggles.toggles import LegacyWaffleFlagNamespace, SettingToggle +from edx_toggles.toggles import SettingToggle, WaffleSwitch -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag # Namespace for courseware waffle flags. -WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='courseware') +WAFFLE_FLAG_NAMESPACE = 'courseware' # Waffle flag to enable the course exit page in the learning MFE. @@ -22,7 +22,7 @@ WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='courseware') # .. toggle_warnings: Also set settings.LEARNING_MICROFRONTEND_URL. # .. toggle_tickets: AA-188 COURSEWARE_MICROFRONTEND_COURSE_EXIT_PAGE = CourseWaffleFlag( - WAFFLE_FLAG_NAMESPACE, 'microfrontend_course_exit_page', __name__ + f'{WAFFLE_FLAG_NAMESPACE}.microfrontend_course_exit_page', __name__ ) # .. toggle_name: courseware.mfe_progress_milestones @@ -36,7 +36,7 @@ COURSEWARE_MICROFRONTEND_COURSE_EXIT_PAGE = CourseWaffleFlag( # .. toggle_warnings: Also set settings.LEARNING_MICROFRONTEND_URL. # .. toggle_tickets: AA-371 COURSEWARE_MICROFRONTEND_PROGRESS_MILESTONES = CourseWaffleFlag( - WAFFLE_FLAG_NAMESPACE, 'mfe_progress_milestones', __name__ + f'{WAFFLE_FLAG_NAMESPACE}.mfe_progress_milestones', __name__ ) # .. toggle_name: courseware.mfe_progress_milestones_streak_celebration @@ -52,7 +52,7 @@ COURSEWARE_MICROFRONTEND_PROGRESS_MILESTONES = CourseWaffleFlag( # COURSEWARE_MICROFRONTEND_PROGRESS_MILESTONES. # .. toggle_tickets: AA-304 COURSEWARE_MICROFRONTEND_PROGRESS_MILESTONES_STREAK_CELEBRATION = CourseWaffleFlag( - WAFFLE_FLAG_NAMESPACE, 'mfe_progress_milestones_streak_celebration', __name__ + f'{WAFFLE_FLAG_NAMESPACE}.mfe_progress_milestones_streak_celebration', __name__ ) # .. toggle_name: courseware.mfe_progress_milestones_streak_discount_enabled @@ -65,8 +65,7 @@ COURSEWARE_MICROFRONTEND_PROGRESS_MILESTONES_STREAK_CELEBRATION = CourseWaffleFl # .. toggle_target_removal_date: None # .. toggle_tickets: https://openedx.atlassian.net/browse/AA-950 COURSEWARE_MFE_MILESTONES_STREAK_DISCOUNT = CourseWaffleFlag( - WAFFLE_FLAG_NAMESPACE, 'streak_discount_enabled', - __name__, + f'{WAFFLE_FLAG_NAMESPACE}.streak_discount_enabled', __name__ ) @@ -81,7 +80,7 @@ COURSEWARE_MFE_MILESTONES_STREAK_DISCOUNT = CourseWaffleFlag( # .. toggle_creation_date: 2021-02-09 # .. toggle_target_removal_date: 2021-05-01 COURSEWARE_OPTIMIZED_RENDER_XBLOCK = CourseWaffleFlag( - WAFFLE_FLAG_NAMESPACE, 'optimized_render_xblock', __name__ + f'{WAFFLE_FLAG_NAMESPACE}.optimized_render_xblock', __name__ ) # .. toggle_name: COURSES_INVITE_ONLY @@ -97,12 +96,17 @@ COURSEWARE_OPTIMIZED_RENDER_XBLOCK = CourseWaffleFlag( # .. toggle_status: unsupported COURSES_INVITE_ONLY = SettingToggle('COURSES_INVITE_ONLY', default=False) +ENABLE_OPTIMIZELY_IN_COURSEWARE = WaffleSwitch( # lint-amnesty, pylint: disable=toggle-missing-annotation + 'RET.enable_optimizely_in_courseware', __name__ +) + def courseware_mfe_is_active() -> bool: """ Should we serve the Learning MFE as the canonical courseware experience? """ from lms.djangoapps.courseware.access_utils import in_preview_mode # avoid a circular import + # We only use legacy views for the Studio "preview mode" feature these days, while everyone else gets the MFE return not in_preview_mode() diff --git a/lms/djangoapps/courseware/views/index.py b/lms/djangoapps/courseware/views/index.py index b789a86598..d2d72eae94 100644 --- a/lms/djangoapps/courseware/views/index.py +++ b/lms/djangoapps/courseware/views/index.py @@ -6,8 +6,8 @@ View for Courseware Index import logging - import urllib + from django.conf import settings from django.contrib.auth.views import redirect_to_login from django.db import transaction @@ -21,12 +21,16 @@ from django.views.decorators.cache import cache_control from django.views.decorators.csrf import ensure_csrf_cookie from django.views.generic import View from edx_django_utils.monitoring import set_custom_attributes_for_course_key -from edx_toggles.toggles import LegacyWaffleSwitchNamespace from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey, UsageKey from web_fragments.fragment import Fragment +from xmodule.course_module import COURSE_VISIBILITY_PUBLIC +from xmodule.modulestore.django import modulestore +from xmodule.x_module import PUBLIC_VIEW, STUDENT_VIEW from common.djangoapps.edxmako.shortcuts import render_to_response, render_to_string +from common.djangoapps.student.models import CourseEnrollment +from common.djangoapps.util.views import ensure_valid_course_key from lms.djangoapps.courseware.exceptions import CourseAccessRedirect, Redirect from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context from lms.djangoapps.gating.api import get_entrance_exam_score, get_entrance_exam_usage_key @@ -42,14 +46,9 @@ from openedx.features.course_experience import ( DISABLE_COURSE_OUTLINE_PAGE_FLAG, default_course_url ) -from openedx.features.course_experience.views.course_sock import CourseSockFragmentView 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 common.djangoapps.student.models import CourseEnrollment -from common.djangoapps.util.views import ensure_valid_course_key -from xmodule.course_module import COURSE_VISIBILITY_PUBLIC # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.x_module import PUBLIC_VIEW, STUDENT_VIEW # lint-amnesty, pylint: disable=wrong-import-order from ..access import has_access from ..access_utils import check_public_access @@ -64,7 +63,7 @@ from ..masquerade import check_content_start_date_for_masquerade_user, setup_mas from ..model_data import FieldDataCache from ..module_render import get_module_for_descriptor, toc_for_course from ..permissions import MASQUERADE_AS_STUDENT -from ..toggles import courseware_mfe_is_active +from ..toggles import ENABLE_OPTIMIZELY_IN_COURSEWARE, courseware_mfe_is_active from .views import CourseTabView log = logging.getLogger("edx.courseware.views.index") @@ -425,7 +424,7 @@ class CoursewareIndex(View): 'xqa_server': settings.FEATURES.get('XQA_SERVER', "http://your_xqa_server.com"), 'bookmarks_api_url': reverse('bookmarks'), 'language_preference': self._get_language_preference(), - 'disable_optimizely': not LegacyWaffleSwitchNamespace('RET').is_enabled('enable_optimizely_in_courseware'), + 'disable_optimizely': not ENABLE_OPTIMIZELY_IN_COURSEWARE.is_enabled(), 'section_title': None, 'sequence_title': None, 'disable_accordion': not DISABLE_COURSE_OUTLINE_PAGE_FLAG.is_enabled(self.course.id), diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 32a5bade9e..27dadbf05f 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -126,7 +126,6 @@ from openedx.features.course_experience.url_helpers import ( from openedx.features.course_experience.utils import dates_banner_should_display from openedx.features.course_experience.views.course_dates import CourseDatesFragmentView from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML -from openedx.features.course_experience.waffle import waffle as course_experience_waffle from openedx.features.enterprise_support.api import data_sharing_consent_required from ..entrance_exams import user_can_skip_entrance_exam @@ -967,7 +966,7 @@ def course_about(request, course_id): # Overview overview = CourseOverview.get_from_id(course.id) - sidebar_html_enabled = course_experience_waffle().is_enabled(ENABLE_COURSE_ABOUT_SIDEBAR_HTML) + sidebar_html_enabled = ENABLE_COURSE_ABOUT_SIDEBAR_HTML.is_enabled() allow_anonymous = check_public_access(course, [COURSE_VISIBILITY_PUBLIC, COURSE_VISIBILITY_PUBLIC_OUTLINE]) diff --git a/lms/djangoapps/discussion/toggles.py b/lms/djangoapps/discussion/toggles.py index b22151cd15..aaf1934fdd 100644 --- a/lms/djangoapps/discussion/toggles.py +++ b/lms/djangoapps/discussion/toggles.py @@ -1,7 +1,7 @@ """ Discussions feature toggles """ -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag WAFFLE_FLAG_NAMESPACE = "discussions" @@ -12,7 +12,7 @@ WAFFLE_FLAG_NAMESPACE = "discussions" # .. toggle_use_cases: temporary, open_edx # .. toggle_creation_date: 2021-11-05 # .. toggle_target_removal_date: 2022-03-05 -ENABLE_DISCUSSIONS_MFE = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'enable_discussions_mfe', __name__) +ENABLE_DISCUSSIONS_MFE = CourseWaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.enable_discussions_mfe', __name__) # .. toggle_name: discussions.enable_discussions_mfe_for_everyone # .. toggle_implementation: CourseWaffleFlag @@ -22,7 +22,7 @@ ENABLE_DISCUSSIONS_MFE = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'enable_discuss # .. toggle_creation_date: 2021-04-21 # .. toggle_target_removal_date: 2022-03-05 ENABLE_DISCUSSIONS_MFE_FOR_EVERYONE = CourseWaffleFlag( - WAFFLE_FLAG_NAMESPACE, 'enable_discussions_mfe_for_everyone', __name__ + f'{WAFFLE_FLAG_NAMESPACE}.enable_discussions_mfe_for_everyone', __name__ ) # .. toggle_name: discussions.enable_new_structure_discussions @@ -32,7 +32,9 @@ ENABLE_DISCUSSIONS_MFE_FOR_EVERYONE = CourseWaffleFlag( # .. toggle_use_cases: temporary, open_edx # .. toggle_creation_date: 2022-02-22 # .. toggle_target_removal_date: 2022-09-22 -ENABLE_NEW_STRUCTURE_DISCUSSIONS = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'enable_new_structure_discussions', __name__) +ENABLE_NEW_STRUCTURE_DISCUSSIONS = CourseWaffleFlag( + f'{WAFFLE_FLAG_NAMESPACE}.enable_new_structure_discussions', __name__ +) # .. toggle_name: discussions.enable_learners_tab_in_discussions_mfe # .. toggle_implementation: CourseWaffleFlag @@ -42,7 +44,9 @@ ENABLE_NEW_STRUCTURE_DISCUSSIONS = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'enab # .. toggle_creation_date: 2022-02-21 # .. toggle_target_removal_date: 2022-05-21 # lint-amnesty, pylint: disable=line-too-long -ENABLE_LEARNERS_TAB_IN_DISCUSSIONS_MFE = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'enable_learners_tab_in_discussions_mfe', __name__) +ENABLE_LEARNERS_TAB_IN_DISCUSSIONS_MFE = CourseWaffleFlag( + f'{WAFFLE_FLAG_NAMESPACE}.enable_learners_tab_in_discussions_mfe', __name__ +) # .. toggle_name: discussions.enable_moderation_reason_codes # .. toggle_implementation: CourseWaffleFlag @@ -52,9 +56,7 @@ ENABLE_LEARNERS_TAB_IN_DISCUSSIONS_MFE = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, # .. toggle_creation_date: 2022-02-22 # .. toggle_target_removal_date: 2022-09-22 ENABLE_DISCUSSION_MODERATION_REASON_CODES = CourseWaffleFlag( - WAFFLE_FLAG_NAMESPACE, - 'enable_moderation_reason_codes', - __name__, + f'{WAFFLE_FLAG_NAMESPACE}.enable_moderation_reason_codes', __name__ ) # .. toggle_name: discussions.enable_reported_content_email_notifications @@ -65,7 +67,5 @@ ENABLE_DISCUSSION_MODERATION_REASON_CODES = CourseWaffleFlag( # .. toggle_creation_date: 2022-03-08 # .. toggle_target_removal_date: 2022-12-31 ENABLE_REPORTED_CONTENT_EMAIL_NOTIFICATIONS = CourseWaffleFlag( - WAFFLE_FLAG_NAMESPACE, - 'enable_reported_content_email_notifications', - __name__, + f'{WAFFLE_FLAG_NAMESPACE}.enable_reported_content_email_notifications', __name__ ) diff --git a/lms/djangoapps/experiments/flags.py b/lms/djangoapps/experiments/flags.py index 323100f849..65d17289ea 100644 --- a/lms/djangoapps/experiments/flags.py +++ b/lms/djangoapps/experiments/flags.py @@ -12,7 +12,7 @@ from edx_django_utils.cache import RequestCache from common.djangoapps.track import segment from lms.djangoapps.experiments.stable_bucketing import stable_bucketing_hash_group -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag log = logging.getLogger(__name__) @@ -64,7 +64,6 @@ class ExperimentWaffleFlag(CourseWaffleFlag): def __init__( self, - waffle_namespace, flag_name, module_name, num_buckets=2, @@ -72,11 +71,11 @@ class ExperimentWaffleFlag(CourseWaffleFlag): use_course_aware_bucketing=True, **kwargs ): - super().__init__(waffle_namespace, flag_name, module_name, **kwargs) + super().__init__(flag_name, module_name, **kwargs) self.num_buckets = num_buckets self.experiment_id = experiment_id self.bucket_flags = [ - CourseWaffleFlag(waffle_namespace, f'{flag_name}.{bucket}', module_name) # lint-amnesty, pylint: disable=toggle-missing-annotation + CourseWaffleFlag(f'{flag_name}.{bucket}', module_name) # lint-amnesty, pylint: disable=toggle-missing-annotation for bucket in range(num_buckets) ] self.use_course_aware_bucketing = use_course_aware_bucketing diff --git a/lms/djangoapps/experiments/tests/test_flags.py b/lms/djangoapps/experiments/tests/test_flags.py index da70f4bb31..f1537b2ec1 100644 --- a/lms/djangoapps/experiments/tests/test_flags.py +++ b/lms/djangoapps/experiments/tests/test_flags.py @@ -18,7 +18,7 @@ from lms.djangoapps.experiments.factories import ExperimentKeyValueFactory from lms.djangoapps.experiments.flags import ExperimentWaffleFlag from lms.djangoapps.experiments.testutils import override_experiment_waffle_flag from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order @@ -38,7 +38,7 @@ class ExperimentWaffleFlagTests(SharedModuleStoreTestCase): self.addCleanup(set_current_request, None) set_current_request(self.request) - self.flag = ExperimentWaffleFlag('experiments', 'test', __name__, num_buckets=2, experiment_id=0) # lint-amnesty, pylint: disable=toggle-missing-annotation + self.flag = ExperimentWaffleFlag('experiments.test', __name__, num_buckets=2, experiment_id=0) # lint-amnesty, pylint: disable=toggle-missing-annotation self.key = CourseKey.from_string('a/b/c') bucket_patch = patch('lms.djangoapps.experiments.flags.stable_bucketing_hash_group', return_value=1) @@ -105,7 +105,7 @@ class ExperimentWaffleFlagTests(SharedModuleStoreTestCase): ) @ddt.unpack def test_forcing_bucket(self, active, expected_bucket): - bucket_flag = CourseWaffleFlag('experiments', 'test.0', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation + bucket_flag = CourseWaffleFlag('experiments.test.0', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation with override_waffle_flag(bucket_flag, active=active): assert self.get_bucket() == expected_bucket @@ -163,7 +163,7 @@ class ExperimentWaffleFlagTests(SharedModuleStoreTestCase): assert 'experiments' == self.flag._app_label assert 'test' == self.flag._experiment_name - flag = ExperimentWaffleFlag("namespace", "flag.name", __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation + flag = ExperimentWaffleFlag("namespace.flag.name", __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation assert 'namespace' == flag._app_label assert 'flag.name' == flag._experiment_name @@ -174,14 +174,14 @@ class ExperimentWaffleFlagCourseAwarenessTest(SharedModuleStoreTestCase): ExperimentWaffleFlag class. """ course_aware_flag = ExperimentWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation - 'exp', 'aware', __name__, num_buckets=20, use_course_aware_bucketing=True, + 'exp.aware', __name__, num_buckets=20, use_course_aware_bucketing=True, ) - course_aware_subflag = CourseWaffleFlag('exp', 'aware.1', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation + course_aware_subflag = CourseWaffleFlag('exp.aware.1', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation course_unaware_flag = ExperimentWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation - 'exp', 'unaware', __name__, num_buckets=20, use_course_aware_bucketing=False, + 'exp.unaware', __name__, num_buckets=20, use_course_aware_bucketing=False, ) - course_unaware_subflag = CourseWaffleFlag('exp', 'unaware.1', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation + course_unaware_subflag = CourseWaffleFlag('exp.unaware.1', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation course_key_1 = CourseKey.from_string("x/y/1") course_key_2 = CourseKey.from_string("x/y/22") diff --git a/lms/djangoapps/experiments/views_custom.py b/lms/djangoapps/experiments/views_custom.py index 0d4c436887..def5621df8 100644 --- a/lms/djangoapps/experiments/views_custom.py +++ b/lms/djangoapps/experiments/views_custom.py @@ -6,7 +6,7 @@ from django.http import HttpResponseBadRequest from django.utils.decorators import method_decorator from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser -from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace +from edx_toggles.toggles import WaffleFlag from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey from rest_framework.response import Response @@ -33,11 +33,7 @@ from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin # .. toggle_target_removal_date: None # .. toggle_tickets: REV-934 # .. toggle_warnings: This temporary feature toggle does not have a target removal date. -MOBILE_UPSELL_FLAG = LegacyWaffleFlag( - waffle_namespace=LegacyWaffleFlagNamespace(name='experiments'), - flag_name='mobile_upsell_rev934', - module_name=__name__, -) +MOBILE_UPSELL_FLAG = WaffleFlag('experiments.mobile_upsell_rev934', __name__) MOBILE_UPSELL_EXPERIMENT = 'mobile_upsell_experiment' diff --git a/lms/djangoapps/grades/api.py b/lms/djangoapps/grades/api.py index 9f14e33945..fb06e28013 100644 --- a/lms/djangoapps/grades/api.py +++ b/lms/djangoapps/grades/api.py @@ -134,10 +134,12 @@ def undo_override_subsection_grade(user_id, course_key_or_id, usage_key_or_id, f def should_override_grade_on_rejected_exam(course_key_or_id): - """Convenience function to return the state of the CourseWaffleFlag REJECTED_EXAM_OVERRIDES_GRADE""" - from .config.waffle import waffle_flags, REJECTED_EXAM_OVERRIDES_GRADE + """ + Convenience function to return the state of the CourseWaffleFlag REJECTED_EXAM_OVERRIDES_GRADE + """ + from .config.waffle import REJECTED_EXAM_OVERRIDES_GRADE course_key = _get_key(course_key_or_id, CourseKey) - return waffle_flags()[REJECTED_EXAM_OVERRIDES_GRADE].is_enabled(course_key) + return REJECTED_EXAM_OVERRIDES_GRADE.is_enabled(course_key) def _create_subsection_grade(user_id, course_key, usage_key): diff --git a/lms/djangoapps/grades/config/__init__.py b/lms/djangoapps/grades/config/__init__.py index 829277576a..7f976601dc 100644 --- a/lms/djangoapps/grades/config/__init__.py +++ b/lms/djangoapps/grades/config/__init__.py @@ -7,7 +7,6 @@ from django.conf import settings from lms.djangoapps.grades.config.models import PersistentGradesEnabledFlag from lms.djangoapps.grades.config.waffle import ASSUME_ZERO_GRADE_IF_ABSENT -from lms.djangoapps.grades.config.waffle import waffle as waffle_func def assume_zero_if_absent(course_key): @@ -17,7 +16,7 @@ def assume_zero_if_absent(course_key): return ( should_persist_grades(course_key) and ( settings.FEATURES.get('ASSUME_ZERO_GRADE_IF_ABSENT_FOR_ALL_TESTS') or - waffle_func().is_enabled(ASSUME_ZERO_GRADE_IF_ABSENT) + ASSUME_ZERO_GRADE_IF_ABSENT.is_enabled() ) ) diff --git a/lms/djangoapps/grades/config/waffle.py b/lms/djangoapps/grades/config/waffle.py index 1ca42ff7b8..ddd3432432 100644 --- a/lms/djangoapps/grades/config/waffle.py +++ b/lms/djangoapps/grades/config/waffle.py @@ -4,12 +4,13 @@ waffle switches for the Grades app. """ -from edx_toggles.toggles import LegacyWaffleFlagNamespace, LegacyWaffleSwitch, LegacyWaffleSwitchNamespace +from edx_toggles.toggles import WaffleSwitch -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag # Namespace WAFFLE_NAMESPACE = 'grades' +LOG_PREFIX = 'Grades: ' # Switches @@ -23,8 +24,7 @@ WAFFLE_NAMESPACE = 'grades' # .. toggle_creation_date: 2017-04-11 # .. toggle_tickets: https://github.com/edx/edx-platform/pull/14771 # .. toggle_warnings: This requires the PersistentGradesEnabledFlag to be enabled. -# TODO: Replace with WaffleSwitch(). See waffle_switch(name) docstring. -ASSUME_ZERO_GRADE_IF_ABSENT = 'assume_zero_grade_if_absent' +ASSUME_ZERO_GRADE_IF_ABSENT = WaffleSwitch(f'{WAFFLE_NAMESPACE}.assume_zero_grade_if_absent', __name__) # .. toggle_name: grades.disable_regrade_on_policy_change # .. toggle_implementation: WaffleSwitch # .. toggle_default: False @@ -32,8 +32,7 @@ ASSUME_ZERO_GRADE_IF_ABSENT = 'assume_zero_grade_if_absent' # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2017-08-03 # .. toggle_tickets: https://github.com/edx/edx-platform/pull/15733 -# TODO: Replace with WaffleSwitch(). See waffle_switch(name) docstring. -DISABLE_REGRADE_ON_POLICY_CHANGE = 'disable_regrade_on_policy_change' +DISABLE_REGRADE_ON_POLICY_CHANGE = WaffleSwitch(f'{WAFFLE_NAMESPACE}.disable_regrade_on_policy_change', __name__) # Course Flags @@ -45,8 +44,10 @@ DISABLE_REGRADE_ON_POLICY_CHANGE = 'disable_regrade_on_policy_change' # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2019-05-29 # .. toggle_tickets: https://github.com/edx/edx-platform/pull/20719 -# TODO: Replace with CourseWaffleFlag() from below. See waffle_flag(name) docstring. -REJECTED_EXAM_OVERRIDES_GRADE = 'rejected_exam_overrides_grade' +# TODO: After removing this flag, add a migration to remove waffle flag in a follow-up deployment. +REJECTED_EXAM_OVERRIDES_GRADE = CourseWaffleFlag( + f'{WAFFLE_NAMESPACE}.rejected_exam_overrides_grade', __name__, LOG_PREFIX +) # .. toggle_name: grades.rejected_exam_overrides_grade # .. toggle_implementation: CourseWaffleFlag # .. toggle_default: False @@ -55,8 +56,10 @@ REJECTED_EXAM_OVERRIDES_GRADE = 'rejected_exam_overrides_grade' # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2018-10-01 # .. toggle_tickets: https://github.com/edx/edx-platform/pull/19026 -# TODO: Replace with CourseWaffleFlag() from below. See waffle_flag(name) docstring. -ENFORCE_FREEZE_GRADE_AFTER_COURSE_END = 'enforce_freeze_grade_after_course_end' +# TODO: After removing this flag, add a migration to remove waffle flag in a follow-up deployment. +ENFORCE_FREEZE_GRADE_AFTER_COURSE_END = CourseWaffleFlag( + f'{WAFFLE_NAMESPACE}.enforce_freeze_grade_after_course_end', __name__, LOG_PREFIX +) # .. toggle_name: grades.writable_gradebook # .. toggle_implementation: CourseWaffleFlag @@ -67,8 +70,8 @@ ENFORCE_FREEZE_GRADE_AFTER_COURSE_END = 'enforce_freeze_grade_after_course_end' # .. toggle_creation_date: 2018-10-03 # .. toggle_tickets: https://github.com/edx/edx-platform/pull/19054 # .. toggle_warnings: Enabling this requires that the `WRITABLE_GRADEBOOK_URL` setting be properly defined. -# TODO: Replace with CourseWaffleFlag() from below. See waffle_flag(name) docstring. -WRITABLE_GRADEBOOK = 'writable_gradebook' +# TODO: After removing this flag, add a migration to remove waffle flag in a follow-up deployment. +WRITABLE_GRADEBOOK = CourseWaffleFlag(f'{WAFFLE_NAMESPACE}.writable_gradebook', __name__, LOG_PREFIX) # .. toggle_name: grades.bulk_management # .. toggle_implementation: CourseWaffleFlag @@ -78,80 +81,18 @@ WRITABLE_GRADEBOOK = 'writable_gradebook' # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2019-08-20 # .. toggle_tickets: https://github.com/edx/edx-platform/pull/21389 -# TODO: Replace with CourseWaffleFlag() from below. See waffle_flag(name) docstring. -BULK_MANAGEMENT = 'bulk_management' - - -def waffle(): - """ - Deprecated: Returns the namespaced, cached, audited Waffle class for Grades. - - Note: Replace uses of this function with direct references to each switch. - See waffle_switch(name) docstring for details. - """ - return LegacyWaffleSwitchNamespace(name=WAFFLE_NAMESPACE, log_prefix='Grades: ') - - -def waffle_switch(name): - """ - Deprecated: Return the corresponding namespaced waffle switch. - - IMPORTANT: Do NOT copy this pattern and do NOT use this to reference new switches. - Instead, replace the string constants above with the actual switch instances. - For example:: - - ASSUME_ZERO_GRADE_IF_ABSENT = WaffleSwitch(f'{WAFFLE_NAMESPACE}.assume_zero_grade_if_absent') - """ - return LegacyWaffleSwitch(waffle(), name, module_name=__name__) - - -def waffle_flags(): - """ - Deprecated: Returns the namespaced, cached, audited Waffle flags dictionary for Grades. - - IMPORTANT: Do NOT copy this pattern and do NOT use this to reference new flags. - Instead, replace the string constants above with the flag declarations below, and use - them directly. - """ - namespace = LegacyWaffleFlagNamespace(name=WAFFLE_NAMESPACE, log_prefix='Grades: ') - return { - # By default, enable rejected exam grade overrides. Can be disabled on a course-by-course basis. - # TODO: After removing this flag, add a migration to remove waffle flag in a follow-up deployment. - REJECTED_EXAM_OVERRIDES_GRADE: CourseWaffleFlag( - namespace, - REJECTED_EXAM_OVERRIDES_GRADE, - __name__, - ), - # TODO: After removing this flag, add a migration to remove waffle flag in a follow-up deployment. - ENFORCE_FREEZE_GRADE_AFTER_COURSE_END: CourseWaffleFlag( - namespace, - ENFORCE_FREEZE_GRADE_AFTER_COURSE_END, - __name__, - ), - # Have this course override flag so we can selectively turn off the gradebook for courses. - # TODO: After removing this flag, add a migration to remove waffle flag in a follow-up deployment. - WRITABLE_GRADEBOOK: CourseWaffleFlag( - namespace, - WRITABLE_GRADEBOOK, - __name__, - ), - BULK_MANAGEMENT: CourseWaffleFlag( - namespace, - BULK_MANAGEMENT, - __name__, - ), - } +BULK_MANAGEMENT = CourseWaffleFlag(f'{WAFFLE_NAMESPACE}.bulk_management', __name__, LOG_PREFIX) def is_writable_gradebook_enabled(course_key): """ Returns whether the writable gradebook app is enabled for the given course. """ - return waffle_flags()[WRITABLE_GRADEBOOK].is_enabled(course_key) + return WRITABLE_GRADEBOOK.is_enabled(course_key) def gradebook_bulk_management_enabled(course_key): """ Returns whether bulk management features should be specially enabled for a given course. """ - return waffle_flags()[BULK_MANAGEMENT].is_enabled(course_key) + return BULK_MANAGEMENT.is_enabled(course_key) diff --git a/lms/djangoapps/grades/grade_utils.py b/lms/djangoapps/grades/grade_utils.py index cd432c9e2d..0344cf6c20 100644 --- a/lms/djangoapps/grades/grade_utils.py +++ b/lms/djangoapps/grades/grade_utils.py @@ -10,14 +10,16 @@ from django.utils import timezone from openedx.core.djangoapps.content.course_overviews.models import CourseOverview -from .config.waffle import ENFORCE_FREEZE_GRADE_AFTER_COURSE_END, waffle_flags +from .config.waffle import ENFORCE_FREEZE_GRADE_AFTER_COURSE_END log = logging.getLogger(__name__) def are_grades_frozen(course_key): - """ Returns whether grades are frozen for the given course. """ - if waffle_flags()[ENFORCE_FREEZE_GRADE_AFTER_COURSE_END].is_enabled(course_key): + """ + Returns whether grades are frozen for the given course. + """ + if ENFORCE_FREEZE_GRADE_AFTER_COURSE_END.is_enabled(course_key): course = CourseOverview.get_from_id(course_key) if course.end: freeze_grade_date = course.end + timedelta(30) diff --git a/lms/djangoapps/grades/migrations/0018_add_waffle_flag_defaults.py b/lms/djangoapps/grades/migrations/0018_add_waffle_flag_defaults.py index c64149216b..3903fc970b 100644 --- a/lms/djangoapps/grades/migrations/0018_add_waffle_flag_defaults.py +++ b/lms/djangoapps/grades/migrations/0018_add_waffle_flag_defaults.py @@ -4,7 +4,6 @@ from lms.djangoapps.grades.config.waffle import ( ENFORCE_FREEZE_GRADE_AFTER_COURSE_END, REJECTED_EXAM_OVERRIDES_GRADE, WRITABLE_GRADEBOOK, - waffle_flags, ) @@ -12,13 +11,13 @@ def create_flag(apps, schema_editor): Flag = apps.get_model('waffle', 'Flag') # Replacement for flag_undefined_default=True on flag definition Flag.objects.get_or_create( - name=waffle_flags()[REJECTED_EXAM_OVERRIDES_GRADE].name, defaults={'everyone': True} + name=REJECTED_EXAM_OVERRIDES_GRADE.name, defaults={'everyone': True} ) Flag.objects.get_or_create( - name=waffle_flags()[ENFORCE_FREEZE_GRADE_AFTER_COURSE_END].name, defaults={'everyone': True} + name=ENFORCE_FREEZE_GRADE_AFTER_COURSE_END.name, defaults={'everyone': True} ) Flag.objects.get_or_create( - name=waffle_flags()[WRITABLE_GRADEBOOK].name, defaults={'everyone': True} + name=WRITABLE_GRADEBOOK.name, defaults={'everyone': True} ) diff --git a/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py b/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py index 7b8e631527..0ff96467e7 100644 --- a/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py +++ b/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py @@ -34,7 +34,7 @@ from common.djangoapps.student.tests.factories import InstructorFactory from common.djangoapps.student.tests.factories import StaffFactory from lms.djangoapps.certificates.data import CertificateStatuses from lms.djangoapps.certificates.models import GeneratedCertificate -from lms.djangoapps.grades.config.waffle import BULK_MANAGEMENT, WRITABLE_GRADEBOOK, waffle_flags +from lms.djangoapps.grades.config.waffle import BULK_MANAGEMENT, WRITABLE_GRADEBOOK from lms.djangoapps.grades.constants import GradeOverrideFeatureEnum from lms.djangoapps.grades.course_data import CourseData from lms.djangoapps.grades.course_grade import CourseGrade @@ -270,7 +270,7 @@ class CourseGradingViewTest(SharedModuleStoreTestCase, APITestCase): assert resp.status_code == status.HTTP_200_OK assert resp.data['can_see_bulk_management'] is True - @override_waffle_flag(waffle_flags()[BULK_MANAGEMENT], active=True) + @override_waffle_flag(BULK_MANAGEMENT, active=True) def test_can_see_bulk_management_force_enabled(self): # Given a course without (or with) a master's track where bulk management is enabled with the config flag # When getting course grading view @@ -290,7 +290,7 @@ class GradebookViewTestBase(GradeViewTestMixin, APITestCase): def setUpClass(cls): super().setUpClass() cls.namespaced_url = 'grades_api:v1:course_gradebook' - cls.waffle_flag = waffle_flags()[WRITABLE_GRADEBOOK] + cls.waffle_flag = WRITABLE_GRADEBOOK cls.course = CourseFactory.create(display_name='test-course', run='run-1') cls.course_key = cls.course.id diff --git a/lms/djangoapps/grades/tasks.py b/lms/djangoapps/grades/tasks.py index 41c5457e49..bf0b8993f1 100644 --- a/lms/djangoapps/grades/tasks.py +++ b/lms/djangoapps/grades/tasks.py @@ -28,7 +28,7 @@ from openedx.core.djangoapps.content.course_overviews.models import \ CourseOverview # lint-amnesty, pylint: disable=unused-import from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order -from .config.waffle import DISABLE_REGRADE_ON_POLICY_CHANGE, waffle +from .config.waffle import DISABLE_REGRADE_ON_POLICY_CHANGE from .constants import ScoreDatabaseTableEnum from .course_grade_factory import CourseGradeFactory from .exceptions import DatabaseNotReadyError @@ -58,7 +58,7 @@ def compute_all_grades_for_course(**kwargs): Kicks off a series of compute_grades_for_course_v2 tasks to cover all of the students in the course. """ - if waffle().is_enabled(DISABLE_REGRADE_ON_POLICY_CHANGE): + if DISABLE_REGRADE_ON_POLICY_CHANGE.is_enabled(): log.debug('Grades: ignoring policy change regrade due to waffle switch') else: course_key = CourseKey.from_string(kwargs.pop('course_key')) diff --git a/lms/djangoapps/grades/tests/test_course_grade.py b/lms/djangoapps/grades/tests/test_course_grade.py index c59f3ac73b..77ea291957 100644 --- a/lms/djangoapps/grades/tests/test_course_grade.py +++ b/lms/djangoapps/grades/tests/test_course_grade.py @@ -12,7 +12,7 @@ from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import UserFactory from openedx.core.djangolib.testing.utils import get_mock_request -from ..config.waffle import ASSUME_ZERO_GRADE_IF_ABSENT, waffle_switch +from ..config.waffle import ASSUME_ZERO_GRADE_IF_ABSENT from ..course_data import CourseData from ..course_grade import ZeroCourseGrade from ..course_grade_factory import CourseGradeFactory @@ -33,7 +33,7 @@ class ZeroGradeTest(GradeTestBase): """ Creates a ZeroCourseGrade and ensures it's empty. """ - with override_waffle_switch(waffle_switch(ASSUME_ZERO_GRADE_IF_ABSENT), active=assume_zero_enabled): + with override_waffle_switch(ASSUME_ZERO_GRADE_IF_ABSENT, active=assume_zero_enabled): course_data = CourseData(self.request.user, structure=self.course_structure) chapter_grades = ZeroCourseGrade(self.request.user, course_data).chapter_grades for chapter in chapter_grades: @@ -48,7 +48,7 @@ class ZeroGradeTest(GradeTestBase): """ Creates a zero course grade and ensures that null scores aren't included in the section problem scores. """ - with override_waffle_switch(waffle_switch(ASSUME_ZERO_GRADE_IF_ABSENT), active=assume_zero_enabled): + with override_waffle_switch(ASSUME_ZERO_GRADE_IF_ABSENT, active=assume_zero_enabled): with patch('lms.djangoapps.grades.subsection_grade.get_score', return_value=None): course_data = CourseData(self.request.user, structure=self.course_structure) chapter_grades = ZeroCourseGrade(self.request.user, course_data).chapter_grades diff --git a/lms/djangoapps/grades/tests/test_course_grade_factory.py b/lms/djangoapps/grades/tests/test_course_grade_factory.py index 880b31f0d2..df9acb4905 100644 --- a/lms/djangoapps/grades/tests/test_course_grade_factory.py +++ b/lms/djangoapps/grades/tests/test_course_grade_factory.py @@ -18,7 +18,7 @@ from openedx.core.djangoapps.signals.signals import COURSE_GRADE_NOW_PASSED from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order -from ..config.waffle import ASSUME_ZERO_GRADE_IF_ABSENT, waffle_switch +from ..config.waffle import ASSUME_ZERO_GRADE_IF_ABSENT from ..course_grade import CourseGrade, ZeroCourseGrade from ..course_grade_factory import CourseGradeFactory from ..subsection_grade import ReadSubsectionGrade, ZeroSubsectionGrade @@ -162,7 +162,7 @@ class TestCourseGradeFactory(GradeTestBase): @ddt.data(*itertools.product((True, False), (True, False))) @ddt.unpack def test_read_zero(self, assume_zero_enabled, create_if_needed): - with override_waffle_switch(waffle_switch(ASSUME_ZERO_GRADE_IF_ABSENT), active=assume_zero_enabled): + with override_waffle_switch(ASSUME_ZERO_GRADE_IF_ABSENT, active=assume_zero_enabled): grade_factory = CourseGradeFactory() course_grade = grade_factory.read(self.request.user, self.course, create_if_needed=create_if_needed) if create_if_needed or assume_zero_enabled: diff --git a/lms/djangoapps/grades/tests/test_services.py b/lms/djangoapps/grades/tests/test_services.py index e77af77e28..459cf6d08c 100644 --- a/lms/djangoapps/grades/tests/test_services.py +++ b/lms/djangoapps/grades/tests/test_services.py @@ -17,7 +17,6 @@ from lms.djangoapps.grades.services import GradesService from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory # lint-amnesty, pylint: disable=wrong-import-order -from ..config.waffle import REJECTED_EXAM_OVERRIDES_GRADE from ..constants import ScoreDatabaseTableEnum @@ -68,11 +67,10 @@ class GradesServiceTests(ModuleStoreTestCase): self.mock_create_id.return_value = 1 self.type_patcher = patch('lms.djangoapps.grades.api.set_event_transaction_type') self.mock_set_type = self.type_patcher.start() - self.flag_patcher = patch('lms.djangoapps.grades.config.waffle.waffle_flags') - self.mock_waffle_flags = self.flag_patcher.start() - self.mock_waffle_flags.return_value = { - REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(True) - } + self.waffle_flag_patch = patch( + 'lms.djangoapps.grades.config.waffle.REJECTED_EXAM_OVERRIDES_GRADE', MockWaffleFlag(True) + ) + self.waffle_flag_patch.start() def tearDown(self): super().tearDown() @@ -80,7 +78,7 @@ class GradesServiceTests(ModuleStoreTestCase): self.signal_patcher.stop() self.id_patcher.stop() self.type_patcher.stop() - self.flag_patcher.stop() + self.waffle_flag_patch.stop() def subsection_grade_to_dict(self, grade): return { @@ -304,7 +302,5 @@ class GradesServiceTests(ModuleStoreTestCase): def test_should_override_grade_on_rejected_exam(self): assert self.service.should_override_grade_on_rejected_exam('course-v1:edX+DemoX+Demo_Course') - self.mock_waffle_flags.return_value = { - REJECTED_EXAM_OVERRIDES_GRADE: MockWaffleFlag(False) - } - assert not self.service.should_override_grade_on_rejected_exam('course-v1:edX+DemoX+Demo_Course') + with patch('lms.djangoapps.grades.config.waffle.REJECTED_EXAM_OVERRIDES_GRADE', MockWaffleFlag(False)): + assert not self.service.should_override_grade_on_rejected_exam('course-v1:edX+DemoX+Demo_Course') diff --git a/lms/djangoapps/grades/tests/test_tasks.py b/lms/djangoapps/grades/tests/test_tasks.py index 637e162ccc..b76d549b33 100644 --- a/lms/djangoapps/grades/tests/test_tasks.py +++ b/lms/djangoapps/grades/tests/test_tasks.py @@ -26,7 +26,7 @@ from common.djangoapps.track.event_transaction_utils import create_new_event_tra from common.djangoapps.util.date_utils import to_timestamp from lms.djangoapps.grades import tasks from lms.djangoapps.grades.config.models import PersistentGradesEnabledFlag -from lms.djangoapps.grades.config.waffle import ENFORCE_FREEZE_GRADE_AFTER_COURSE_END, waffle_flags +from lms.djangoapps.grades.config.waffle import ENFORCE_FREEZE_GRADE_AFTER_COURSE_END from lms.djangoapps.grades.constants import ScoreDatabaseTableEnum from lms.djangoapps.grades.models import PersistentCourseGrade, PersistentSubsectionGrade from lms.djangoapps.grades.signals.signals import PROBLEM_WEIGHTED_SCORE_CHANGED @@ -490,7 +490,7 @@ class FreezeGradingAfterCourseEndTest(HasCourseWithProblemsMixin, ModuleStoreTes super().setUp() self.users = [UserFactory.create() for _ in range(12)] self.user = self.users[0] - self.freeze_grade_flag = waffle_flags()[ENFORCE_FREEZE_GRADE_AFTER_COURSE_END] + self.freeze_grade_flag = ENFORCE_FREEZE_GRADE_AFTER_COURSE_END def _assert_log(self, mock_log, method_name): assert mock_log.info.called diff --git a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py index 4f0e49152e..da495802e6 100644 --- a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py +++ b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py @@ -21,7 +21,6 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, chec from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.edxmako.shortcuts import render_to_response from common.djangoapps.student.models import CourseEnrollment -from common.djangoapps.student.roles import CourseFinanceAdminRole # lint-amnesty, pylint: disable=unused-import from common.djangoapps.student.tests.factories import AdminFactory, CourseAccessRoleFactory, CourseEnrollmentFactory from common.djangoapps.student.tests.factories import StaffFactory from common.djangoapps.student.tests.factories import UserFactory @@ -30,7 +29,7 @@ from lms.djangoapps.courseware.courses import get_studio_url from lms.djangoapps.courseware.tabs import get_course_tab_list from lms.djangoapps.courseware.tests.factories import StudentModuleFactory from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase -from lms.djangoapps.grades.config.waffle import WRITABLE_GRADEBOOK, waffle_flags +from lms.djangoapps.grades.config.waffle import WRITABLE_GRADEBOOK from lms.djangoapps.instructor.toggles import DATA_DOWNLOAD_V2 from lms.djangoapps.instructor.views.gradebook_api import calculate_page_info from openedx.core.djangoapps.course_groups.cohorts import set_course_cohorted @@ -335,8 +334,7 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssT Test that, when the writable gradebook feature is enabled and deployed in another domain, a staff member can see it. """ - waffle_flag = waffle_flags()[WRITABLE_GRADEBOOK] - with override_waffle_flag(waffle_flag, active=True): + with override_waffle_flag(WRITABLE_GRADEBOOK, active=True): response = self.client.get(self.url) expected_gradebook_url = f'http://gradebook.local.edx.org/{self.course.id}' @@ -357,8 +355,7 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssT Test that, when the writable gradebook feature is enabled and deployed in a subdirectory, a staff member can see it. """ - waffle_flag = waffle_flags()[WRITABLE_GRADEBOOK] - with override_waffle_flag(waffle_flag, active=True): + with override_waffle_flag(WRITABLE_GRADEBOOK, active=True): response = self.client.get(self.url) expected_gradebook_url = f'{settings.WRITABLE_GRADEBOOK_URL}/{self.course.id}' @@ -393,8 +390,7 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssT a message that the feature is only available for courses with small numbers of learners. """ - waffle_flag = waffle_flags()[WRITABLE_GRADEBOOK] - with override_waffle_flag(waffle_flag, active=True): + with override_waffle_flag(WRITABLE_GRADEBOOK, active=True): response = self.client.get(self.url) assert TestInstructorDashboard.GRADEBOOK_LEARNER_COUNT_MESSAGE not in response.content.decode('utf-8') self.assertContains(response, 'View Gradebook') diff --git a/lms/djangoapps/instructor/toggles.py b/lms/djangoapps/instructor/toggles.py index 8f360ff504..5149e8765c 100644 --- a/lms/djangoapps/instructor/toggles.py +++ b/lms/djangoapps/instructor/toggles.py @@ -2,11 +2,11 @@ Waffle flags for instructor dashboard. """ -from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace +from edx_toggles.toggles import WaffleFlag + -WAFFLE_NAMESPACE = 'instructor' # Namespace for instructor waffle flags. -WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name=WAFFLE_NAMESPACE) +WAFFLE_FLAG_NAMESPACE = 'instructor' # Waffle flag enable new data download UI on specific course. # .. toggle_name: instructor.enable_data_download_v2 @@ -16,11 +16,7 @@ WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name=WAFFLE_NAMESPACE) # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2020-07-8 # .. toggle_tickets: PROD-1309 -DATA_DOWNLOAD_V2 = LegacyWaffleFlag( - waffle_namespace=WAFFLE_FLAG_NAMESPACE, - flag_name='enable_data_download_v2', - module_name=__name__, -) +DATA_DOWNLOAD_V2 = WaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.enable_data_download_v2', __name__) # .. toggle_name: verify_student.optimised_is_small_course # .. toggle_implementation: WaffleFlag @@ -31,11 +27,7 @@ DATA_DOWNLOAD_V2 = LegacyWaffleFlag( # .. toggle_warnings: Description mentions staged rollout, but the use case is not set as temporary. # This may actually be a temporary toggle. # .. toggle_tickets: PROD-1740 -OPTIMISED_IS_SMALL_COURSE = LegacyWaffleFlag( - waffle_namespace=WAFFLE_FLAG_NAMESPACE, - flag_name='optimised_is_small_course', - module_name=__name__, -) +OPTIMISED_IS_SMALL_COURSE = WaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.optimised_is_small_course', __name__) def data_download_v2_is_enabled(): diff --git a/lms/djangoapps/instructor_task/config/waffle.py b/lms/djangoapps/instructor_task/config/waffle.py index bd569d6c03..ee2319293b 100644 --- a/lms/djangoapps/instructor_task/config/waffle.py +++ b/lms/djangoapps/instructor_task/config/waffle.py @@ -3,53 +3,33 @@ This module contains various configuration settings via waffle switches for the instructor_task app. """ -from edx_toggles.toggles import LegacyWaffleFlagNamespace, LegacyWaffleSwitchNamespace +from edx_toggles.toggles import WaffleSwitch + +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag WAFFLE_NAMESPACE = 'instructor_task' -# TODO: Remove and replace with direct references to each flag. -INSTRUCTOR_TASK_WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name=WAFFLE_NAMESPACE) -# TODO: Remove and replace with direct references to each switch. -WAFFLE_SWITCHES = LegacyWaffleSwitchNamespace(name=WAFFLE_NAMESPACE) # Waffle switches -# TODO: Replace with WaffleSwitch(). See WAFFLE_SWITCHES comment. -OPTIMIZE_GET_LEARNERS_FOR_COURSE = 'optimize_get_learners_for_course' +OPTIMIZE_GET_LEARNERS_FOR_COURSE = WaffleSwitch( # lint-amnesty, pylint: disable=toggle-missing-annotation + f'{WAFFLE_NAMESPACE}.optimize_get_learners_for_course', __name__ +) # Course override flags -# TODO: Replace with WaffleFlag(). See waffle_flags() docstring. -GENERATE_PROBLEM_GRADE_REPORT_VERIFIED_ONLY = 'generate_problem_grade_report_verified_only' -# TODO: Replace with WaffleFlag(). See waffle_flags() docstring. -GENERATE_COURSE_GRADE_REPORT_VERIFIED_ONLY = 'generate_course_grade_report_verified_only' +GENERATE_PROBLEM_GRADE_REPORT_VERIFIED_ONLY = CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation + f'{WAFFLE_NAMESPACE}.generate_problem_grade_report_verified_only', __name__ +) - -def waffle_flags(): - """ - Returns the namespaced, cached, audited Waffle flags dictionary for Grades. - - IMPORTANT: Do NOT copy this dict pattern and do NOT add new flags to this dict. - Instead, replace the string constants above with the actual flag instances. - """ - return { - GENERATE_PROBLEM_GRADE_REPORT_VERIFIED_ONLY: CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation - waffle_namespace=INSTRUCTOR_TASK_WAFFLE_FLAG_NAMESPACE, - flag_name=GENERATE_PROBLEM_GRADE_REPORT_VERIFIED_ONLY, - module_name=__name__, - ), - GENERATE_COURSE_GRADE_REPORT_VERIFIED_ONLY: CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation - waffle_namespace=INSTRUCTOR_TASK_WAFFLE_FLAG_NAMESPACE, - flag_name=GENERATE_COURSE_GRADE_REPORT_VERIFIED_ONLY, - module_name=__name__, - ), - } +GENERATE_COURSE_GRADE_REPORT_VERIFIED_ONLY = CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation + f'{WAFFLE_NAMESPACE}.generate_course_grade_report_verified_only', __name__ +) def optimize_get_learners_switch_enabled(): """ Returns True if optimize get learner switch is enabled, otherwise False. """ - return WAFFLE_SWITCHES.is_enabled(OPTIMIZE_GET_LEARNERS_FOR_COURSE) + return OPTIMIZE_GET_LEARNERS_FOR_COURSE.is_enabled() def problem_grade_report_verified_only(course_id): @@ -58,7 +38,7 @@ def problem_grade_report_verified_only(course_id): return rows for verified students in the given course, False otherwise. """ - return waffle_flags()[GENERATE_PROBLEM_GRADE_REPORT_VERIFIED_ONLY].is_enabled(course_id) + return GENERATE_PROBLEM_GRADE_REPORT_VERIFIED_ONLY.is_enabled(course_id) def course_grade_report_verified_only(course_id): @@ -67,4 +47,4 @@ def course_grade_report_verified_only(course_id): return rows for verified students in the given course, False otherwise. """ - return waffle_flags()[GENERATE_COURSE_GRADE_REPORT_VERIFIED_ONLY].is_enabled(course_id) + return GENERATE_COURSE_GRADE_REPORT_VERIFIED_ONLY.is_enabled(course_id) diff --git a/lms/djangoapps/ora_staff_grader/views.py b/lms/djangoapps/ora_staff_grader/views.py index cf4d08fbc7..7dd6f72900 100644 --- a/lms/djangoapps/ora_staff_grader/views.py +++ b/lms/djangoapps/ora_staff_grader/views.py @@ -57,7 +57,7 @@ from lms.djangoapps.ora_staff_grader.utils import require_params from openedx.core.djangoapps.content.course_overviews.api import ( get_course_overview_or_none, ) -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser log = logging.getLogger(__name__) @@ -98,10 +98,12 @@ class InitializeView(StaffGraderBaseView): def _is_staff_grader_enabled(self, course_key): """ Helper to evaluate if the staff grader flag / overrides are enabled """ # This toggle is documented on the edx-ora2 repo in openassessment/xblock/config_mixin.py + # Note: Do not copy this practice of directly using a toggle from a library. + # Instead, see docs for exposing a wrapper api: + # https://edx.readthedocs.io/projects/edx-toggles/en/latest/how_to/implement_the_right_toggle_type.html#using-other-toggles pylint: disable=line-too-long # pylint: disable=toggle-missing-annotation enhanced_staff_grader_flag = CourseWaffleFlag( - WAFFLE_NAMESPACE, - ENHANCED_STAFF_GRADER, + f"{WAFFLE_NAMESPACE}.{ENHANCED_STAFF_GRADER}", module_name='openassessment.xblock.config_mixin' ) return enhanced_staff_grader_flag.is_enabled(course_key) diff --git a/lms/djangoapps/teams/toggles.py b/lms/djangoapps/teams/toggles.py index f9a100e15b..9f3a30f085 100644 --- a/lms/djangoapps/teams/toggles.py +++ b/lms/djangoapps/teams/toggles.py @@ -3,7 +3,7 @@ Togglable settings for Teams behavior """ from edx_toggles.toggles import SettingDictToggle -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag # Course Waffle inherited from edx/edx-ora2 WAFFLE_NAMESPACE = "openresponseassessment" @@ -29,7 +29,7 @@ def are_team_submissions_enabled(course_key): """ Checks to see if the CourseWaffleFlag or Django setting for team submissions is enabled """ - if CourseWaffleFlag(WAFFLE_NAMESPACE, TEAM_SUBMISSIONS_FLAG, __name__).is_enabled( + if CourseWaffleFlag(f'{WAFFLE_NAMESPACE}.{TEAM_SUBMISSIONS_FLAG}', __name__).is_enabled( course_key ): return True diff --git a/lms/djangoapps/verify_student/toggles.py b/lms/djangoapps/verify_student/toggles.py index 9439262c1e..1632d26a97 100644 --- a/lms/djangoapps/verify_student/toggles.py +++ b/lms/djangoapps/verify_student/toggles.py @@ -2,10 +2,10 @@ Toggles for verify_student app """ -from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace +from edx_toggles.toggles import WaffleFlag # Namespace for verify_students waffle flags. -WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='verify_student') +WAFFLE_FLAG_NAMESPACE = 'verify_student' # Waffle flag to use new email templates for sending ID verification emails. # .. toggle_name: verify_student.use_new_email_templates @@ -18,11 +18,7 @@ WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='verify_student') # .. toggle_target_removal_date: None # .. toggle_warnings: This temporary feature toggle does not have a target removal date. # .. toggle_tickets: PROD-1639 -USE_NEW_EMAIL_TEMPLATES = LegacyWaffleFlag( - waffle_namespace=WAFFLE_FLAG_NAMESPACE, - flag_name='use_new_email_templates', - module_name=__name__, -) +USE_NEW_EMAIL_TEMPLATES = WaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.use_new_email_templates', __name__) def use_new_templates_for_id_verification_emails(): diff --git a/openedx/core/djangoapps/course_apps/toggles.py b/openedx/core/djangoapps/course_apps/toggles.py index daa5fe3f8e..34f40b4d06 100644 --- a/openedx/core/djangoapps/course_apps/toggles.py +++ b/openedx/core/djangoapps/course_apps/toggles.py @@ -1,12 +1,10 @@ """ Toggles for course apps. """ -from edx_toggles.toggles import LegacyWaffleSwitchNamespace - -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag #: Namespace for use by course apps for creating availability toggles -COURSE_APPS_WAFFLE_NAMESPACE = LegacyWaffleSwitchNamespace("course_apps") +COURSE_APPS_WAFFLE_NAMESPACE = 'course_apps' # .. toggle_name: course_apps.proctoring_settings_modal_view # .. toggle_use_cases: temporary @@ -18,7 +16,7 @@ COURSE_APPS_WAFFLE_NAMESPACE = LegacyWaffleSwitchNamespace("course_apps") # .. toggle_creation_date: 2021-08-17 # .. toggle_target_removal_date: None PROCTORING_SETTINGS_MODAL_VIEW = CourseWaffleFlag( - COURSE_APPS_WAFFLE_NAMESPACE, 'proctoring_settings_modal_view', module_name=__name__, + f'{COURSE_APPS_WAFFLE_NAMESPACE}.proctoring_settings_modal_view', __name__ ) diff --git a/openedx/core/djangoapps/course_live/config/waffle.py b/openedx/core/djangoapps/course_live/config/waffle.py index d2c05196f3..7cdb99f515 100644 --- a/openedx/core/djangoapps/course_live/config/waffle.py +++ b/openedx/core/djangoapps/course_live/config/waffle.py @@ -3,11 +3,9 @@ This module contains various configuration settings via waffle switches for the live app. """ -from edx_toggles.toggles import LegacyWaffleFlagNamespace +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag - -WAFFLE_NAMESPACE = LegacyWaffleFlagNamespace(name='course_live') +WAFFLE_NAMESPACE = 'course_live' # .. toggle_name: course_live.enable_course_live # .. toggle_implementation: CourseWaffleFlag @@ -18,8 +16,4 @@ WAFFLE_NAMESPACE = LegacyWaffleFlagNamespace(name='course_live') # .. toggle_target_removal_date: 2022-06-02 # .. toggle_warnings: When the flag is ON, the course live app will be visible in the course authoring mfe # .. toggle_tickets: TNL-9603 -ENABLE_COURSE_LIVE = CourseWaffleFlag( - waffle_namespace=WAFFLE_NAMESPACE, - flag_name='enable_course_live', - module_name=__name__, -) +ENABLE_COURSE_LIVE = CourseWaffleFlag(f'{WAFFLE_NAMESPACE}.enable_course_live', __name__) diff --git a/openedx/core/djangoapps/discussions/config/waffle.py b/openedx/core/djangoapps/discussions/config/waffle.py index b642baabca..47e9c56f41 100644 --- a/openedx/core/djangoapps/discussions/config/waffle.py +++ b/openedx/core/djangoapps/discussions/config/waffle.py @@ -3,12 +3,10 @@ This module contains various configuration settings via waffle switches for the discussions app. """ -from edx_toggles.toggles import LegacyWaffleFlagNamespace - -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag -WAFFLE_NAMESPACE = LegacyWaffleFlagNamespace(name='discussions') +WAFFLE_NAMESPACE = 'discussions' # .. toggle_name: discussions.override_discussion_legacy_settings # .. toggle_implementation: CourseWaffleFlag @@ -20,9 +18,7 @@ WAFFLE_NAMESPACE = LegacyWaffleFlagNamespace(name='discussions') # .. toggle_warnings: When the flag is ON, the discussion settings will be available on legacy experience. # .. toggle_tickets: TNL-8389 OVERRIDE_DISCUSSION_LEGACY_SETTINGS_FLAG = CourseWaffleFlag( - waffle_namespace=WAFFLE_NAMESPACE, - flag_name='override_discussion_legacy_settings', - module_name=__name__, + f'{WAFFLE_NAMESPACE}.override_discussion_legacy_settings', __name__ ) @@ -36,7 +32,5 @@ OVERRIDE_DISCUSSION_LEGACY_SETTINGS_FLAG = CourseWaffleFlag( # .. toggle_warnings: When the flag is ON, the new experience for Pages and Resources will be enabled. # .. toggle_tickets: TNL-7791 ENABLE_PAGES_AND_RESOURCES_MICROFRONTEND = CourseWaffleFlag( - waffle_namespace=WAFFLE_NAMESPACE, - flag_name='pages_and_resources_mfe', - module_name=__name__, + f'{WAFFLE_NAMESPACE}.pages_and_resources_mfe', __name__ ) diff --git a/openedx/core/djangoapps/programs/__init__.py b/openedx/core/djangoapps/programs/__init__.py index 04def48a7a..8d12a150ee 100644 --- a/openedx/core/djangoapps/programs/__init__.py +++ b/openedx/core/djangoapps/programs/__init__.py @@ -8,15 +8,14 @@ if and only if the service is deployed in the Open edX installation. To ensure maximum separation of concerns, and a minimum of interdependencies, this package should be kept small, thin, and stateless. """ +from edx_toggles.toggles import WaffleSwitch + default_app_config = 'openedx.core.djangoapps.programs.apps.ProgramsConfig' -from edx_toggles.toggles import LegacyWaffleSwitch, LegacyWaffleSwitchNamespace # lint-amnesty, pylint: disable=wrong-import-position - -PROGRAMS_WAFFLE_SWITCH_NAMESPACE = LegacyWaffleSwitchNamespace(name='programs') +PROGRAMS_WAFFLE_SWITCH_NAMESPACE = 'programs' # This is meant to be enabled until https://openedx.atlassian.net/browse/LEARNER-5573 needs to be resolved -ALWAYS_CALCULATE_PROGRAM_PRICE_AS_ANONYMOUS_USER = LegacyWaffleSwitch( # lint-amnesty, pylint: disable=toggle-missing-annotation - PROGRAMS_WAFFLE_SWITCH_NAMESPACE, - 'always_calculate_program_price_as_anonymous_user', +ALWAYS_CALCULATE_PROGRAM_PRICE_AS_ANONYMOUS_USER = WaffleSwitch( # lint-amnesty, pylint: disable=toggle-missing-annotation + f'{PROGRAMS_WAFFLE_SWITCH_NAMESPACE}.always_calculate_program_price_as_anonymous_user', __name__ ) diff --git a/openedx/core/djangoapps/schedules/config.py b/openedx/core/djangoapps/schedules/config.py index 6d03f79191..802559ebcf 100644 --- a/openedx/core/djangoapps/schedules/config.py +++ b/openedx/core/djangoapps/schedules/config.py @@ -3,13 +3,12 @@ Contains configuration for schedules app """ from crum import get_current_request -from edx_toggles.toggles import LegacyWaffleSwitch, LegacyWaffleFlagNamespace, LegacyWaffleSwitchNamespace, WaffleFlag +from edx_toggles.toggles import WaffleFlag, WaffleSwitch from lms.djangoapps.experiments.flags import ExperimentWaffleFlag from lms.djangoapps.experiments.models import ExperimentData -WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='schedules') -WAFFLE_SWITCH_NAMESPACE = LegacyWaffleSwitchNamespace(name='schedules') +WAFFLE_NAMESPACE = 'schedules' # .. toggle_name: schedules.enable_debugging # .. toggle_implementation: WaffleFlag @@ -17,12 +16,10 @@ WAFFLE_SWITCH_NAMESPACE = LegacyWaffleSwitchNamespace(name='schedules') # .. toggle_description: Enable debug level of logging for schedules messages. # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2017-09-17 -DEBUG_MESSAGE_WAFFLE_FLAG = WaffleFlag('schedules.enable_debugging', __name__) +DEBUG_MESSAGE_WAFFLE_FLAG = WaffleFlag(f'{WAFFLE_NAMESPACE}.enable_debugging', __name__) -COURSE_UPDATE_SHOW_UNSUBSCRIBE_WAFFLE_SWITCH = LegacyWaffleSwitch( # lint-amnesty, pylint: disable=toggle-missing-annotation - WAFFLE_SWITCH_NAMESPACE, - 'course_update_show_unsubscribe', - __name__ +COURSE_UPDATE_SHOW_UNSUBSCRIBE_WAFFLE_SWITCH = WaffleSwitch( # lint-amnesty, pylint: disable=toggle-missing-annotation + f'{WAFFLE_NAMESPACE}.course_update_show_unsubscribe', __name__ ) # This experiment waffle is supporting an A/B test we are running on sending course updates from an external service, @@ -31,9 +28,11 @@ COURSE_UPDATE_SHOW_UNSUBSCRIBE_WAFFLE_SWITCH = LegacyWaffleSwitch( # lint-amnes # methods below. We save this flag decision at enrollment time and don't change it even if the flag changes. So you # can't just directly look at flag result. _EXTERNAL_COURSE_UPDATES_EXPERIMENT_ID = 18 -_EXTERNAL_COURSE_UPDATES_FLAG = ExperimentWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'external_updates', __name__, # lint-amnesty, pylint: disable=toggle-missing-annotation - experiment_id=_EXTERNAL_COURSE_UPDATES_EXPERIMENT_ID, - use_course_aware_bucketing=False) +_EXTERNAL_COURSE_UPDATES_FLAG = ExperimentWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation + f'{WAFFLE_NAMESPACE}.external_updates', __name__, + experiment_id=_EXTERNAL_COURSE_UPDATES_EXPERIMENT_ID, + use_course_aware_bucketing=False +) def set_up_external_updates_for_enrollment(user, course_key): diff --git a/openedx/core/djangoapps/user_api/accounts/toggles.py b/openedx/core/djangoapps/user_api/accounts/toggles.py index f8221a02ab..d21eeb96aa 100644 --- a/openedx/core/djangoapps/user_api/accounts/toggles.py +++ b/openedx/core/djangoapps/user_api/accounts/toggles.py @@ -2,7 +2,8 @@ Toggles for accounts related code. """ -from edx_toggles.toggles import LegacyWaffleFlag +from edx_toggles.toggles import WaffleFlag + from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers # .. toggle_name: order_history.redirect_to_microfrontend @@ -15,7 +16,7 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_ # .. toggle_warnings: Also set settings.ORDER_HISTORY_MICROFRONTEND_URL and site's # ENABLE_ORDER_HISTORY_MICROFRONTEND. # .. toggle_tickets: DEPR-17 -REDIRECT_TO_ORDER_HISTORY_MICROFRONTEND = LegacyWaffleFlag('order_history', 'redirect_to_microfrontend', __name__) +REDIRECT_TO_ORDER_HISTORY_MICROFRONTEND = WaffleFlag('order_history.redirect_to_microfrontend', __name__) def should_redirect_to_order_history_microfrontend(): @@ -35,7 +36,7 @@ def should_redirect_to_order_history_microfrontend(): # .. toggle_target_removal_date: 2021-12-31 # .. toggle_warnings: Also set settings.ACCOUNT_MICROFRONTEND_URL. # .. toggle_tickets: DEPR-17 -REDIRECT_TO_ACCOUNT_MICROFRONTEND = LegacyWaffleFlag('account', 'redirect_to_microfrontend', __name__) +REDIRECT_TO_ACCOUNT_MICROFRONTEND = WaffleFlag('account.redirect_to_microfrontend', __name__) def should_redirect_to_account_microfrontend(): diff --git a/openedx/core/djangoapps/user_authn/config/waffle.py b/openedx/core/djangoapps/user_authn/config/waffle.py index dc409d4eaa..1183d94319 100644 --- a/openedx/core/djangoapps/user_authn/config/waffle.py +++ b/openedx/core/djangoapps/user_authn/config/waffle.py @@ -3,10 +3,9 @@ Waffle flags and switches for user authn. """ -from edx_toggles.toggles import LegacyWaffleSwitch, LegacyWaffleSwitchNamespace +from edx_toggles.toggles import WaffleSwitch _WAFFLE_NAMESPACE = 'user_authn' -_WAFFLE_SWITCH_NAMESPACE = LegacyWaffleSwitchNamespace(name=_WAFFLE_NAMESPACE, log_prefix='UserAuthN: ') # .. toggle_name: user_authn.enable_login_using_thirdparty_auth_only # .. toggle_implementation: WaffleSwitch @@ -18,10 +17,8 @@ _WAFFLE_SWITCH_NAMESPACE = LegacyWaffleSwitchNamespace(name=_WAFFLE_NAMESPACE, l # .. toggle_target_removal_date: 2020-01-31 # .. toggle_warnings: Requires THIRD_PARTY_AUTH_ONLY_DOMAIN to also be set. # .. toggle_tickets: ENT-2461 -ENABLE_LOGIN_USING_THIRDPARTY_AUTH_ONLY = LegacyWaffleSwitch( - _WAFFLE_SWITCH_NAMESPACE, - 'enable_login_using_thirdparty_auth_only', - __name__ +ENABLE_LOGIN_USING_THIRDPARTY_AUTH_ONLY = WaffleSwitch( + f'{_WAFFLE_NAMESPACE}.enable_login_using_thirdparty_auth_only', __name__ ) # .. toggle_name: user_authn.enable_pwned_password_api @@ -32,8 +29,6 @@ ENABLE_LOGIN_USING_THIRDPARTY_AUTH_ONLY = LegacyWaffleSwitch( # .. toggle_creation_date: 2021-09-22 # .. toggle_target_removal_date: 2021-12-31 # .. toggle_tickets: VAN-664 -ENABLE_PWNED_PASSWORD_API = LegacyWaffleSwitch( - _WAFFLE_SWITCH_NAMESPACE, - 'enable_pwned_password_api', - __name__ +ENABLE_PWNED_PASSWORD_API = WaffleSwitch( + f'{_WAFFLE_NAMESPACE}.enable_pwned_password_api', __name__ ) diff --git a/openedx/core/djangoapps/user_authn/views/register.py b/openedx/core/djangoapps/user_authn/views/register.py index 7ba3a8920e..6310ce2c02 100644 --- a/openedx/core/djangoapps/user_authn/views/register.py +++ b/openedx/core/djangoapps/user_authn/views/register.py @@ -22,7 +22,7 @@ from django.utils.translation import gettext as _ from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie from django.views.decorators.debug import sensitive_post_parameters from edx_django_utils.monitoring import set_custom_attribute -from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace +from edx_toggles.toggles import WaffleFlag from openedx_events.learning.data import UserData, UserPersonalData from openedx_events.learning.signals import STUDENT_REGISTRATION_COMPLETED from openedx_filters.learning.filters import StudentRegistrationRequested @@ -115,11 +115,7 @@ REGISTER_USER = Signal() # .. toggle_creation_date: 2020-04-30 # .. toggle_target_removal_date: 2020-06-01 # .. toggle_warnings: This temporary feature toggle does not have a target removal date. -REGISTRATION_FAILURE_LOGGING_FLAG = LegacyWaffleFlag( - waffle_namespace=LegacyWaffleFlagNamespace(name='registration'), - flag_name='enable_failure_logging', - module_name=__name__, -) +REGISTRATION_FAILURE_LOGGING_FLAG = WaffleFlag('registration.enable_failure_logging', __name__) REAL_IP_KEY = 'openedx.core.djangoapps.util.ratelimit.real_ip' diff --git a/openedx/core/djangoapps/video_pipeline/config/waffle.py b/openedx/core/djangoapps/video_pipeline/config/waffle.py index 34b734cf1d..58a2ddb24c 100644 --- a/openedx/core/djangoapps/video_pipeline/config/waffle.py +++ b/openedx/core/djangoapps/video_pipeline/config/waffle.py @@ -3,11 +3,13 @@ This module contains configuration settings via waffle flags for the Video Pipeline app. """ -from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from edx_toggles.toggles import WaffleFlag + +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag # Videos Namespace WAFFLE_NAMESPACE = 'videos' +LOG_PREFIX = 'Videos: ' # .. toggle_name: videos.deprecate_youtube # .. toggle_implementation: CourseWaffleFlag @@ -17,8 +19,8 @@ WAFFLE_NAMESPACE = 'videos' # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2018-08-03 # .. toggle_tickets: https://github.com/edx/edx-platform/pull/18765 -# TODO: Replace with CourseWaffleFlag() from waffle_flags(). -DEPRECATE_YOUTUBE = 'deprecate_youtube' +DEPRECATE_YOUTUBE = CourseWaffleFlag(f'{WAFFLE_NAMESPACE}.deprecate_youtube', __name__, LOG_PREFIX) + # .. toggle_name: videos.enable_devstack_video_uploads # .. toggle_implementation: WaffleFlag # .. toggle_default: False @@ -30,34 +32,8 @@ DEPRECATE_YOUTUBE = 'deprecate_youtube' # .. toggle_warnings: Enabling this feature requires that the ROLE_ARN, MFA_SERIAL_NUMBER, MFA_TOKEN settings are # properly defined. # .. toggle_tickets: https://github.com/edx/edx-platform/pull/23375 -# TODO: Replace with WaffleFlag() from waffle_flags(). -ENABLE_DEVSTACK_VIDEO_UPLOADS = 'enable_devstack_video_uploads' -# TODO: Replace with CourseWaffleFlag() from waffle_flags(). -ENABLE_VEM_PIPELINE = 'enable_vem_pipeline' +ENABLE_DEVSTACK_VIDEO_UPLOADS = WaffleFlag(f'{WAFFLE_NAMESPACE}.enable_devstack_video_uploads', __name__, LOG_PREFIX) - -def waffle_flags(): - """ - Returns the namespaced, cached, audited Waffle flags dictionary for Videos. - - IMPORTANT: Do NOT copy this dict pattern and do NOT add new flags to this dict. - Instead, replace the string constants above with the actual flag instances. - """ - namespace = LegacyWaffleFlagNamespace(name=WAFFLE_NAMESPACE, log_prefix='Videos: ') - return { - DEPRECATE_YOUTUBE: CourseWaffleFlag( - waffle_namespace=namespace, - flag_name=DEPRECATE_YOUTUBE, - module_name=__name__, - ), - ENABLE_DEVSTACK_VIDEO_UPLOADS: LegacyWaffleFlag( - waffle_namespace=namespace, - flag_name=ENABLE_DEVSTACK_VIDEO_UPLOADS, - module_name=__name__, - ), - ENABLE_VEM_PIPELINE: CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation - waffle_namespace=namespace, - flag_name=ENABLE_VEM_PIPELINE, - module_name=__name__, - ) - } +ENABLE_VEM_PIPELINE = CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation + f'{WAFFLE_NAMESPACE}.enable_vem_pipeline', __name__, LOG_PREFIX +) diff --git a/openedx/core/djangoapps/waffle_utils/__future__.py b/openedx/core/djangoapps/waffle_utils/__future__.py new file mode 100644 index 0000000000..5d44636b26 --- /dev/null +++ b/openedx/core/djangoapps/waffle_utils/__future__.py @@ -0,0 +1,116 @@ +""" +Temporary module to switch from the LegacyWaffle* classes. +""" +import logging + +from edx_toggles.toggles import WaffleFlag +from opaque_keys.edx.keys import CourseKey + +log = logging.getLogger(__name__) + + +class FutureCourseWaffleFlag(WaffleFlag): + """ + Represents a single waffle flag that can be forced on/off for a course. This class should be used instead of + WaffleFlag when in the context of a course. This class will also respect any org-level overrides, though + course-level overrides will take precedence. + + Uses a cached waffle namespace. + + Usage: + + SOME_COURSE_FLAG = CourseWaffleFlag('my_namespace.some_course_feature', __name__, log_prefix='') + + And then we can check this flag in code with:: + + SOME_COURSE_FLAG.is_enabled(course_key) + + To configure a course-level override, go to Django Admin "waffle_utils" -> "Waffle flag course overrides". + + Waffle flag: Set this to the flag name (e.g. my_namespace.some_course_feature). + Course id: Set this to the course id (e.g. course-v1:edx+100+Demo) + Override choice: (Force on/Force off). "Force on" will enable the waffle flag for all users in a course, + overriding any behavior configured on the waffle flag itself. "Force off" will disable the waffle flag + for all users in a course, overriding any behavior configured on the waffle flag itself. Requires + "Enabled" (see below) to apply. + Enabled: Must be marked as "enabled" in order for the override to be applied. These settings can't be + deleted, so instead, you need to add another disabled override entry to disable the override. + + To configure an org-level override, go to Django Admin "waffle_utils" -> "Waffle flag org overrides". + + Waffle flag: Set this to the flag name (e.g. my_namespace.some_course_feature). + Org name: Set this to the organization name (e.g. edx) + Override choice: (Force on/Force off). "Force on" will enable the waffle flag for all users in an org's courses, + overriding any behavior configured on the waffle flag itself. "Force off" will disable the waffle flag + for all users in a org's courses, overriding any behavior configured on the waffle flag itself. Requires + "Enabled" (see below) to apply. + Enabled: Must be marked as "enabled" in order for the override to be applied. These settings can't be + deleted, so instead, you need to add another disabled override entry to disable the override. + + """ + + def _get_course_override_value(self, course_key): + """ + Returns True/False if the flag was forced on or off for the provided course. Returns None if the flag was not + overridden. + + Note: Has side effect of caching the override value. + + Arguments: + course_key (CourseKey): The course to check for override before checking waffle. + """ + # Import is placed here to avoid model import at project startup. + from .models import WaffleFlagCourseOverrideModel, WaffleFlagOrgOverrideModel + + course_cache_key = f"{self.name}.cwaffle.{str(course_key)}" + course_override = self.cached_flags().get(course_cache_key) + + if course_override is None: + course_override = WaffleFlagCourseOverrideModel.override_value( + self.name, course_key + ) + self.cached_flags()[course_cache_key] = course_override + + if course_override == WaffleFlagCourseOverrideModel.ALL_CHOICES.on: + return True + if course_override == WaffleFlagCourseOverrideModel.ALL_CHOICES.off: + return False + + # Since no course-specific override was found, fall back to checking at the org-level. + if course_key: + org = course_key.org + org_cache_key = f"{self.name}.owaffle.{org}" + org_override = self.cached_flags().get(org_cache_key) + + if org_override is None: + org_override = WaffleFlagOrgOverrideModel.override_value( + self.name, org + ) + self.cached_flags()[org_cache_key] = org_override + + if org_override == WaffleFlagOrgOverrideModel.ALL_CHOICES.on: + return True + if org_override == WaffleFlagOrgOverrideModel.ALL_CHOICES.off: + return False + + return None + + def is_enabled(self, course_key=None): # pylint: disable=arguments-differ + """ + Returns whether or not the flag is enabled within the context of a given course. + + Arguments: + course_key (Optional[CourseKey]): The course to check for override before + checking waffle. If omitted, check whether the flag is enabled + outside the context of any course. + """ + if course_key: + assert isinstance( + course_key, CourseKey + ), "Provided course_key '{}' is not instance of CourseKey.".format( + course_key + ) + is_enabled_for_course = self._get_course_override_value(course_key) + if is_enabled_for_course is not None: + return is_enabled_for_course + return super().is_enabled() diff --git a/openedx/core/djangoapps/waffle_utils/__init__.py b/openedx/core/djangoapps/waffle_utils/__init__.py index f0df220933..d964e6d5b3 100644 --- a/openedx/core/djangoapps/waffle_utils/__init__.py +++ b/openedx/core/djangoapps/waffle_utils/__init__.py @@ -3,123 +3,30 @@ Extra utilities for waffle: most classes are defined in edx_toggles.toggles (htt we keep here some extra classes for usage within edx-platform. These classes cover course override use cases. """ import logging -import warnings -from contextlib import contextmanager from edx_django_utils.monitoring import set_custom_attribute -from edx_toggles.toggles import ( - LegacyWaffleFlag, - LegacyWaffleFlagNamespace, - LegacyWaffleSwitch, - LegacyWaffleSwitchNamespace, -) -from opaque_keys.edx.keys import CourseKey +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag log = logging.getLogger(__name__) -class CourseWaffleFlag(LegacyWaffleFlag): +class CourseWaffleFlag(FutureCourseWaffleFlag): """ - Represents a single waffle flag that can be forced on/off for a course. This class should be used instead of - WaffleFlag when in the context of a course. This class will also respect any org-level overrides, though - course-level overrides will take precedence. - - Uses a cached waffle namespace. - - Usage: - - SOME_COURSE_FLAG = CourseWaffleFlag('my_namespace', 'some_course_feature', __name__) - - And then we can check this flag in code with:: - - SOME_COURSE_FLAG.is_enabled(course_key) - - To configure a course-level override, go to Django Admin "waffle_utils" -> "Waffle flag course overrides". - - Waffle flag: Set this to the flag name (e.g. my_namespace.some_course_feature). - Course id: Set this to the course id (e.g. course-v1:edx+100+Demo) - Override choice: (Force on/Force off). "Force on" will enable the waffle flag for all users in a course, - overriding any behavior configured on the waffle flag itself. "Force off" will disable the waffle flag - for all users in a course, overriding any behavior configured on the waffle flag itself. Requires - "Enabled" (see below) to apply. - Enabled: Must be marked as "enabled" in order for the override to be applied. These settings can't be - deleted, so instead, you need to add another disabled override entry to disable the override. - - To configure an org-level override, go to Django Admin "waffle_utils" -> "Waffle flag org overrides". - - Waffle flag: Set this to the flag name (e.g. my_namespace.some_course_feature). - Org name: Set this to the organization name (e.g. edx) - Override choice: (Force on/Force off). "Force on" will enable the waffle flag for all users in an org's courses, - overriding any behavior configured on the waffle flag itself. "Force off" will disable the waffle flag - for all users in a org's courses, overriding any behavior configured on the waffle flag itself. Requires - "Enabled" (see below) to apply. - Enabled: Must be marked as "enabled" in order for the override to be applied. These settings can't be - deleted, so instead, you need to add another disabled override entry to disable the override. + Represents a single waffle flag that can be forced on/off for a course. + Deprecated: use the FutureCourseWaffleFlag instead. """ + def __init__(self, waffle_namespace, flag_name, module_name=None): + log_prefix = "" + if not isinstance(waffle_namespace, str): + log_prefix = waffle_namespace.log_prefix or log_prefix + waffle_namespace = waffle_namespace.name - def _get_course_override_value(self, course_key): - """ - Returns True/False if the flag was forced on or off for the provided course. Returns None if the flag was not - overridden. - - Note: Has side effect of caching the override value. - - Arguments: - course_key (CourseKey): The course to check for override before checking waffle. - """ - # Import is placed here to avoid model import at project startup. - from .models import WaffleFlagCourseOverrideModel, WaffleFlagOrgOverrideModel - - course_cache_key = f"{self.name}.cwaffle.{str(course_key)}" - course_override = self.cached_flags().get(course_cache_key) - - if course_override is None: - course_override = WaffleFlagCourseOverrideModel.override_value( - self.name, course_key - ) - self.cached_flags()[course_cache_key] = course_override - - if course_override == WaffleFlagCourseOverrideModel.ALL_CHOICES.on: - return True - if course_override == WaffleFlagCourseOverrideModel.ALL_CHOICES.off: - return False - - # Since no course-specific override was found, fall back to checking at the org-level. - if course_key: - org = course_key.org - org_cache_key = f"{self.name}.owaffle.{org}" - org_override = self.cached_flags().get(org_cache_key) - - if org_override is None: - org_override = WaffleFlagOrgOverrideModel.override_value( - self.name, org - ) - self.cached_flags()[org_cache_key] = org_override - - if org_override == WaffleFlagOrgOverrideModel.ALL_CHOICES.on: - return True - if org_override == WaffleFlagOrgOverrideModel.ALL_CHOICES.off: - return False - - return None - - def is_enabled(self, course_key=None): # pylint: disable=arguments-differ - """ - Returns whether or not the flag is enabled within the context of a given course. - - Arguments: - course_key (Optional[CourseKey]): The course to check for override before - checking waffle. If omitted, check whether the flag is enabled - outside the context of any course. - """ - if course_key: - assert isinstance( - course_key, CourseKey - ), "Provided course_key '{}' is not instance of CourseKey.".format( - course_key - ) - is_enabled_for_course = self._get_course_override_value(course_key) - if is_enabled_for_course is not None: - return is_enabled_for_course - return super().is_enabled() + # Non-namespaced flag_name attribute preserved for backward compatibility + self._flag_name = flag_name + name = f"{waffle_namespace}.{flag_name}" + super().__init__(name, module_name=module_name, log_prefix=log_prefix) + set_custom_attribute( + "deprecated_legacy_waffle_class", + f"{self.__class__.__module__}.{self.__class__.__name__}[{self.name}]" + ) diff --git a/openedx/core/djangoapps/waffle_utils/tests/test_init.py b/openedx/core/djangoapps/waffle_utils/tests/test_init.py index 546ec2b7b5..8db41727d4 100644 --- a/openedx/core/djangoapps/waffle_utils/tests/test_init.py +++ b/openedx/core/djangoapps/waffle_utils/tests/test_init.py @@ -4,6 +4,7 @@ Tests for waffle utils features. # pylint: disable=toggle-missing-annotation from unittest.mock import patch + import crum import ddt from django.test.client import RequestFactory @@ -11,11 +12,11 @@ from edx_django_utils.cache import RequestCache from opaque_keys.edx.keys import CourseKey from waffle.testutils import override_flag +from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag as LegacyCourseWaffleFlag +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag +from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel, WaffleFlagOrgOverrideModel from openedx.core.djangolib.testing.utils import CacheIsolationTestCase -from .. import CourseWaffleFlag -from ..models import WaffleFlagCourseOverrideModel, WaffleFlagOrgOverrideModel - @ddt.ddt class TestCourseWaffleFlag(CacheIsolationTestCase): @@ -33,7 +34,7 @@ class TestCourseWaffleFlag(CacheIsolationTestCase): TEST_COURSE_KEY = CourseKey.from_string(f"{TEST_ORG}/DemoX/Demo_Course") TEST_COURSE_2_KEY = CourseKey.from_string(f"{TEST_ORG}/DemoX/Demo_Course_2") TEST_COURSE_3_KEY = CourseKey.from_string("CollegeX/DemoX/Demo_Course") - TEST_COURSE_FLAG = CourseWaffleFlag(NAMESPACE_NAME, FLAG_NAME, __name__) + TEST_COURSE_FLAG = CourseWaffleFlag(NAMESPACED_FLAG_NAME, __name__) def setUp(self): super().setUp() @@ -75,6 +76,44 @@ class TestCourseWaffleFlag(CacheIsolationTestCase): # course which should get the default value of False. assert self.TEST_COURSE_FLAG.is_enabled(self.TEST_COURSE_2_KEY) is False + @ddt.data( + (False, WaffleFlagCourseOverrideModel.ALL_CHOICES.on, True), + (True, WaffleFlagCourseOverrideModel.ALL_CHOICES.off, False), + (True, WaffleFlagCourseOverrideModel.ALL_CHOICES.unset, True), + (False, WaffleFlagCourseOverrideModel.ALL_CHOICES.unset, False), + ) + @ddt.unpack + def test_legacy_course_waffle_flag(self, waffle_enabled, course_override, result): + """ + Tests various combinations of a legacy flag being set in waffle and overridden for a course. + """ + test_legacy_course_flag = LegacyCourseWaffleFlag( + self.NAMESPACE_NAME, + self.FLAG_NAME, + __name__, + ) + with patch.object(WaffleFlagCourseOverrideModel, 'override_value', return_value=course_override): + with override_flag(self.NAMESPACED_FLAG_NAME, active=waffle_enabled): + # check twice to test that the result is properly cached + assert test_legacy_course_flag.is_enabled(self.TEST_COURSE_KEY) == result + assert test_legacy_course_flag.is_enabled(self.TEST_COURSE_KEY) == result + # result is cached, so override check should happen only once + # pylint: disable=no-member + WaffleFlagCourseOverrideModel.override_value.assert_called_once_with( + self.NAMESPACED_FLAG_NAME, + self.TEST_COURSE_KEY + ) + + # check flag for a second course + if course_override == WaffleFlagCourseOverrideModel.ALL_CHOICES.unset: + # When course override wasn't set for the first course, the second course will get the same + # cached value from waffle. + assert test_legacy_course_flag.is_enabled(self.TEST_COURSE_2_KEY) == waffle_enabled + else: + # When course override was set for the first course, it should not apply to the second + # course which should get the default value of False. + assert test_legacy_course_flag.is_enabled(self.TEST_COURSE_2_KEY) is False + @ddt.data( (False, WaffleFlagOrgOverrideModel.ALL_CHOICES.unset, False), (True, WaffleFlagOrgOverrideModel.ALL_CHOICES.unset, True), @@ -196,11 +235,7 @@ class TestCourseWaffleFlag(CacheIsolationTestCase): """ Test flag with undefined waffle flag. """ - test_course_flag = CourseWaffleFlag( - self.NAMESPACE_NAME, - self.FLAG_NAME, - __name__, - ) + test_course_flag = CourseWaffleFlag(self.NAMESPACED_FLAG_NAME, __name__) with patch.object( WaffleFlagCourseOverrideModel, @@ -222,11 +257,7 @@ class TestCourseWaffleFlag(CacheIsolationTestCase): Test the flag behavior when outside a request context and waffle data undefined. """ crum.set_current_request(None) - test_course_flag = CourseWaffleFlag( - self.NAMESPACE_NAME, - self.FLAG_NAME, - __name__, - ) + test_course_flag = CourseWaffleFlag(self.NAMESPACED_FLAG_NAME, __name__) assert test_course_flag.is_enabled(self.TEST_COURSE_KEY) is False def test_without_request_and_everyone_active_waffle(self): @@ -234,10 +265,7 @@ class TestCourseWaffleFlag(CacheIsolationTestCase): Test the flag behavior when outside a request context and waffle active for everyone. """ crum.set_current_request(None) - test_course_flag = CourseWaffleFlag( - self.NAMESPACE_NAME, - self.FLAG_NAME, - __name__, - ) + + test_course_flag = CourseWaffleFlag(self.NAMESPACED_FLAG_NAME, __name__) with override_flag(self.NAMESPACED_FLAG_NAME, active=True): assert test_course_flag.is_enabled(self.TEST_COURSE_KEY) is True diff --git a/openedx/features/course_experience/__init__.py b/openedx/features/course_experience/__init__.py index 0c9eca091d..83bb4d06f1 100644 --- a/openedx/features/course_experience/__init__.py +++ b/openedx/features/course_experience/__init__.py @@ -4,28 +4,28 @@ Unified course experience settings and helper methods. from django.urls import reverse from django.utils.translation import gettext as _ -from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from edx_toggles.toggles import WaffleFlag +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag # Namespace for course experience waffle flags. -WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='course_experience') +WAFFLE_FLAG_NAMESPACE = 'course_experience' # Waffle flag to disable the separate course outline page and full width content. DISABLE_COURSE_OUTLINE_PAGE_FLAG = CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation - WAFFLE_FLAG_NAMESPACE, 'disable_course_outline_page', __name__ + f'{WAFFLE_FLAG_NAMESPACE}.disable_course_outline_page', __name__ ) # Waffle flag to enable a single unified "Course" tab. DISABLE_UNIFIED_COURSE_TAB_FLAG = CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation - WAFFLE_FLAG_NAMESPACE, 'disable_unified_course_tab', __name__ + f'{WAFFLE_FLAG_NAMESPACE}.disable_unified_course_tab', __name__ ) # Waffle flag to enable the sock on the footer of the home and courseware pages. -DISPLAY_COURSE_SOCK_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'display_course_sock', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation +DISPLAY_COURSE_SOCK_FLAG = CourseWaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.display_course_sock', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation # Waffle flag to let learners access a course before its start date. -COURSE_PRE_START_ACCESS_FLAG = LegacyWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'pre_start_access', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation +COURSE_PRE_START_ACCESS_FLAG = WaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.pre_start_access', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation # .. toggle_name: course_experience.enable_course_goals # .. toggle_implementation: CourseWaffleFlag @@ -35,14 +35,12 @@ COURSE_PRE_START_ACCESS_FLAG = LegacyWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'pre_star # .. toggle_creation_date: 2017-09-11 # .. toggle_target_removal_date: None # .. toggle_warnings: This temporary feature toggle does not have a target removal date. -ENABLE_COURSE_GOALS = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'enable_course_goals', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation +ENABLE_COURSE_GOALS = CourseWaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.enable_course_goals', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation # Waffle flag to enable anonymous access to a course -SEO_WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='seo') +SEO_WAFFLE_FLAG_NAMESPACE = 'seo' COURSE_ENABLE_UNENROLLED_ACCESS_FLAG = CourseWaffleFlag( # lint-amnesty, pylint: disable=toggle-missing-annotation - SEO_WAFFLE_FLAG_NAMESPACE, - 'enable_anonymous_courseware_access', - __name__, + f'{SEO_WAFFLE_FLAG_NAMESPACE}.enable_anonymous_courseware_access', __name__ ) # .. toggle_name: course_experience.relative_dates @@ -55,7 +53,7 @@ COURSE_ENABLE_UNENROLLED_ACCESS_FLAG = CourseWaffleFlag( # lint-amnesty, pylint # .. toggle_warnings: To set a relative due date for self-paced courses, the weeks_to_complete field for a course run # needs to be set. Currently it can be set through the publisher app. # .. toggle_tickets: https://openedx.atlassian.net/browse/AA-27 -RELATIVE_DATES_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'relative_dates', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation +RELATIVE_DATES_FLAG = CourseWaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.relative_dates', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation # .. toggle_name: course_experience.calendar_sync # .. toggle_implementation: CourseWaffleFlag @@ -69,7 +67,7 @@ RELATIVE_DATES_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'relative_dates', # .. toggle_creation_date: 2021-01-26 # .. toggle_target_removal_date: 2021-04-26 # .. toggle_tickets: https://openedx.atlassian.net/browse/AA-36 -CALENDAR_SYNC_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'calendar_sync', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation +CALENDAR_SYNC_FLAG = CourseWaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.calendar_sync', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation def course_home_page_title(_course): diff --git a/openedx/features/course_experience/waffle.py b/openedx/features/course_experience/waffle.py index 8fc7e14a63..26423f882a 100644 --- a/openedx/features/course_experience/waffle.py +++ b/openedx/features/course_experience/waffle.py @@ -3,7 +3,7 @@ Miscellaneous waffle switches that both LMS and Studio need to access """ -from edx_toggles.toggles import LegacyWaffleSwitchNamespace +from edx_toggles.toggles import WaffleSwitch # Namespace WAFFLE_NAMESPACE = 'course_experience' @@ -15,18 +15,4 @@ WAFFLE_NAMESPACE = 'course_experience' # .. toggle_description: Used to determine whether to show custom HTML in the sidebar on the internal course about page. # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2018-01-26 -# TODO: Replace with WaffleSwitch(). See waffle() docstring. -ENABLE_COURSE_ABOUT_SIDEBAR_HTML = 'enable_about_sidebar_html' - - -def waffle(): - """ - Deprecated: Returns the namespaced, cached, audited shared Waffle Switch class. - - IMPORTANT: Do NOT copy this pattern and do NOT use this to reference new switches. - Instead, replace the string constant above with the actual switch instance. - For example:: - - ENABLE_COURSE_ABOUT_SIDEBAR_HTML = WaffleSwitch(f'{WAFFLE_NAMESPACE}.enable_about_sidebar_html') - """ - return LegacyWaffleSwitchNamespace(name=WAFFLE_NAMESPACE, log_prefix='Course Experience: ') +ENABLE_COURSE_ABOUT_SIDEBAR_HTML = WaffleSwitch(f'{WAFFLE_NAMESPACE}.enable_about_sidebar_html', __name__) diff --git a/openedx/features/discounts/applicability.py b/openedx/features/discounts/applicability.py index 2dc1469bd8..f4fd4733cb 100644 --- a/openedx/features/discounts/applicability.py +++ b/openedx/features/discounts/applicability.py @@ -15,7 +15,7 @@ import pytz from crum import get_current_request, impersonate from django.utils import timezone from django.utils.dateparse import parse_datetime -from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace +from edx_toggles.toggles import WaffleFlag from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.entitlements.models import CourseEntitlement @@ -37,11 +37,7 @@ from common.djangoapps.track import segment # .. toggle_target_removal_date: None # .. toggle_tickets: REVEM-282 # .. toggle_warnings: This temporary feature toggle does not have a target removal date. -DISCOUNT_APPLICABILITY_FLAG = LegacyWaffleFlag( - waffle_namespace=LegacyWaffleFlagNamespace(name='discounts'), - flag_name='enable_discounting', - module_name=__name__, -) +DISCOUNT_APPLICABILITY_FLAG = WaffleFlag('discounts.enable_discounting', __name__) DISCOUNT_APPLICABILITY_HOLDBACK = 'first_purchase_discount_holdback' REV1008_EXPERIMENT_ID = 16 diff --git a/openedx/features/effort_estimation/toggles.py b/openedx/features/effort_estimation/toggles.py index be1d67acd5..d9d5e72d10 100644 --- a/openedx/features/effort_estimation/toggles.py +++ b/openedx/features/effort_estimation/toggles.py @@ -2,12 +2,10 @@ Feature toggles used for effort estimation. """ -from edx_toggles.toggles import LegacyWaffleFlagNamespace - -from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag +from openedx.core.djangoapps.waffle_utils.__future__ import FutureCourseWaffleFlag as CourseWaffleFlag -WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='effort_estimation') +WAFFLE_FLAG_NAMESPACE = 'effort_estimation' # .. toggle_name: effort_estimation.disabled # .. toggle_implementation: CourseWaffleFlag @@ -16,4 +14,4 @@ WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='effort_estimation') # estimates), you can turn them off case by case here. # .. toggle_use_cases: opt_out # .. toggle_creation_date: 2021-07-27 -EFFORT_ESTIMATION_DISABLED_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'disabled', __name__) +EFFORT_ESTIMATION_DISABLED_FLAG = CourseWaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.disabled', __name__) diff --git a/openedx/features/enterprise_support/utils.py b/openedx/features/enterprise_support/utils.py index 018ee7967f..4604b5c4fb 100644 --- a/openedx/features/enterprise_support/utils.py +++ b/openedx/features/enterprise_support/utils.py @@ -13,7 +13,7 @@ from django.core.cache import cache from django.urls import NoReverseMatch, reverse from django.utils.translation import gettext as _ from edx_django_utils.cache import TieredCache, get_cache_key -from edx_toggles.toggles import LegacyWaffleFlag +from edx_toggles.toggles import WaffleFlag from enterprise.api.v1.serializers import EnterpriseCustomerBrandingConfigurationSerializer from enterprise.models import EnterpriseCustomer, EnterpriseCustomerUser from social_django.models import UserSocialAuth @@ -25,7 +25,7 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_ from openedx.core.djangoapps.user_authn.cookies import standard_cookie_settings from openedx.core.djangolib.markup import HTML, Text -ENTERPRISE_HEADER_LINKS = LegacyWaffleFlag('enterprise', 'enterprise_header_links', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation +ENTERPRISE_HEADER_LINKS = WaffleFlag('enterprise.enterprise_header_links', __name__) # lint-amnesty, pylint: disable=toggle-missing-annotation def get_data_consent_share_cache_key(user_id, course_id, enterprise_customer_uuid=None): diff --git a/openedx/features/learner_profile/toggles.py b/openedx/features/learner_profile/toggles.py index 23d4afe128..6b29f922ac 100644 --- a/openedx/features/learner_profile/toggles.py +++ b/openedx/features/learner_profile/toggles.py @@ -3,11 +3,11 @@ Toggles for Learner Profile page. """ -from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace +from edx_toggles.toggles import WaffleFlag from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers # Namespace for learner profile waffle flags. -WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='learner_profile') +WAFFLE_FLAG_NAMESPACE = 'learner_profile' # Waffle flag to redirect to another learner profile experience. # .. toggle_name: learner_profile.redirect_to_microfrontend @@ -19,7 +19,7 @@ WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='learner_profile') # .. toggle_target_removal_date: 2020-12-31 # .. toggle_warnings: Also set settings.PROFILE_MICROFRONTEND_URL and site's ENABLE_PROFILE_MICROFRONTEND. # .. toggle_tickets: DEPR-17 -REDIRECT_TO_PROFILE_MICROFRONTEND = LegacyWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'redirect_to_microfrontend', __name__) +REDIRECT_TO_PROFILE_MICROFRONTEND = WaffleFlag(f'{WAFFLE_FLAG_NAMESPACE}.redirect_to_microfrontend', __name__) def should_redirect_to_profile_microfrontend():