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:
@@ -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__)
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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__
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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()):
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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__
|
||||
)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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__
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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__
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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__
|
||||
)
|
||||
|
||||
@@ -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__
|
||||
)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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__
|
||||
)
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
116
openedx/core/djangoapps/waffle_utils/__future__.py
Normal file
116
openedx/core/djangoapps/waffle_utils/__future__.py
Normal 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()
|
||||
@@ -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}]"
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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():
|
||||
|
||||
Reference in New Issue
Block a user