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.
This commit is contained in:
Eugene Dyudyunov
2022-05-05 19:03:10 +03:00
committed by GitHub
parent f1bd793cc5
commit 8bd43207ca
60 changed files with 471 additions and 644 deletions

View File

@@ -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__)

View File

@@ -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')

View File

@@ -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,
)

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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,

View File

@@ -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__
)

View File

@@ -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
<a href="${marketing_link('PRIVACY')}">${_("Privacy Policy")}</a>
</li>
% endif
% if waffle().is_enabled(ENABLE_ACCESSIBILITY_POLICY_PAGE):
% if ENABLE_ACCESSIBILITY_POLICY_PAGE.is_enabled():
<li class="nav-item nav-peripheral-aar">
<a href="${reverse('accessibility')}">${_("Accessibility Accommodation Request")}</a>
</li>
@@ -62,4 +62,3 @@ from openedx.core.djangolib.markup import HTML, Text
</div>
</footer>
</div>

View File

@@ -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()):

View File

@@ -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']

View File

@@ -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:

View File

@@ -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__)

View File

@@ -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__
)

View File

@@ -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):

View File

@@ -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",

View File

@@ -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 = '<video display_name="Video" edx_video_id="12345-67890" youtube_id_1_0="{}">[]</video>'.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

View File

@@ -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):

View File

@@ -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()

View File

@@ -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),

View File

@@ -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])

View File

@@ -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__
)

View File

@@ -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

View File

@@ -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")

View File

@@ -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'

View File

@@ -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):

View File

@@ -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()
)
)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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}
)

View File

@@ -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

View File

@@ -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'))

View File

@@ -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

View File

@@ -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:

View File

@@ -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')

View File

@@ -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

View File

@@ -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')

View File

@@ -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():

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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():

View File

@@ -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__
)

View File

@@ -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__)

View File

@@ -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__
)

View File

@@ -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__
)

View File

@@ -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):

View File

@@ -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():

View File

@@ -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__
)

View File

@@ -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'

View File

@@ -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
)

View File

@@ -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()

View File

@@ -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}]"
)

View File

@@ -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

View File

@@ -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):

View File

@@ -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__)

View File

@@ -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

View File

@@ -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__)

View File

@@ -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):

View File

@@ -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():