diff --git a/openedx/core/djangoapps/waffle_utils/__init__.py b/openedx/core/djangoapps/waffle_utils/__init__.py index 0bb3872f8f178b4050bad460c2320716b466fbf9..2b97a3de926a4134dc22fc93893808999cc9ed24 100644 --- a/openedx/core/djangoapps/waffle_utils/__init__.py +++ b/openedx/core/djangoapps/waffle_utils/__init__.py @@ -8,25 +8,25 @@ Usage: For Waffle Flags, first set up the namespace, and then create flags using the namespace. For example:: - WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='course_experience') + WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='my_namespace') # Use CourseWaffleFlag when you are in the context of a course. - UNIFIED_COURSE_TAB_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'unified_course_tab') + SOME_COURSE_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'some_course_feature') # Use WaffleFlag when outside the context of a course. - HIDE_SEARCH_FLAG = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'hide_search') + SOME_FLAG = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'some_feature') You can check these flags in code using the following:: - HIDE_SEARCH_FLAG.is_enabled() - UNIFIED_COURSE_TAB_FLAG.is_enabled(course_key) + SOME_FLAG.is_enabled() + SOME_COURSE_FLAG.is_enabled(course_key) To test these WaffleFlags, see testutils.py. In the above examples, you will use Django Admin "waffle" section to configure -for a flag named: course_experience.unified_course_tab +for a flag named: my_namespace.some_course_feature You could also use the Django Admin "waffle_utils" section to configure a course -override for this same flag (e.g. course_experience.unified_course_tab). +override for this same flag (e.g. my_namespace.some_course_feature). For Waffle Switches, first set up the namespace, and then create the flag name. For example:: diff --git a/openedx/core/djangoapps/waffle_utils/testutils.py b/openedx/core/djangoapps/waffle_utils/testutils.py index 0f52fd6992f7574e74cb3be6ffbcb5b09415116b..af7b6a614ba05ed6d49be75253a7bade5ce865f7 100644 --- a/openedx/core/djangoapps/waffle_utils/testutils.py +++ b/openedx/core/djangoapps/waffle_utils/testutils.py @@ -19,7 +19,7 @@ class override_waffle_flag(override_flag): It accepts two parameters, the flag itself and its intended state. Example usage:: - with override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True): + with override_waffle_flag(SOME_COURSE_FLAG, active=True): ... If the flag already exists, its value will be changed inside the context @@ -29,7 +29,7 @@ class override_waffle_flag(override_flag): It can also act as a decorator:: - @override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True) + @override_waffle_flag(SOME_COURSE_FLAG, active=True) def test_happy_mode_enabled(): ... """ diff --git a/openedx/features/course_experience/__init__.py b/openedx/features/course_experience/__init__.py index a038208310be60ed3a9e281c753dd7194766e71f..5bbe6004e8681f11b6931d71e363238fe4db4308 100644 --- a/openedx/features/course_experience/__init__.py +++ b/openedx/features/course_experience/__init__.py @@ -1,9 +1,11 @@ """ Unified course experience settings and helper methods. """ - - +import crum +from django.conf import settings from django.utils.translation import ugettext as _ +from edx_django_utils.monitoring import set_custom_metric +from waffle import flag_is_active from lms.djangoapps.experiments.flags import ExperimentWaffleFlag from openedx.core.djangoapps.util.user_messages import UserMessageCollection @@ -12,11 +14,95 @@ from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlag, W # Namespace for course experience waffle flags. WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='course_experience') +# .. toggle_name: USE_DEFAULT_TRUE_NAMESPACE +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: When True, uses the new default_true namespace to help deprecate flag_undefined_default. +# .. toggle_category: course_experience +# .. toggle_use_cases: monitored_rollout +# .. toggle_creation_date: 2020-06-30 +# .. toggle_expiration_date: 2020-07 +# .. toggle_warnings: n/a +# .. toggle_tickets: n/a +# .. toggle_status: supported +_USE_DEFAULT_TRUE_NAMESPACE = 'USE_DEFAULT_TRUE_NAMESPACE' + + +class DefaultTrueWaffleFlagNamespace(WaffleFlagNamespace): + """ + This is a temporary class to help deprecate/remove ``flag_undefined_default``. + + TODO: TNL-7061: Perform the actual clean-up required to remove these flags + and refactor/fix any tests that shouldn't be removed. + + """ + def _is_flag_active(self, flag_name): + """ + Returns and caches whether the provided flag is active. + + If the flag value is already cached in the request, it is returned. + If the flag doesn't exist, always returns default of True. + + Note: This is a similified version of the method it overrides, that + hard codes the default to True, and skips the call back used for + course overrides: + https://github.com/edx/edx-platform/blob/df9be8c678f8266e2e5710513c74deca14c4527c/openedx/core/djangoapps/waffle_utils/__init__.py#L229-L305 + + """ + # Import is placed here to avoid model import at project startup. + from waffle.models import Flag + + # validate arguments + namespaced_flag_name = self._namespaced_name(flag_name) + value = self._cached_flags.get(namespaced_flag_name) + if value is None: + + # determine if the flag is undefined in waffle + try: + Flag.objects.get(name=namespaced_flag_name) + except Flag.DoesNotExist: + # default to True if not defined + value = True + + if value is None: + request = crum.get_current_request() + if request: + value = flag_is_active(request, namespaced_flag_name) + else: + set_custom_metric('warn_flag_no_request', True) + # Return the default value if not in a request context. + # Same as the original implementation + self._set_waffle_flag_metric(namespaced_flag_name, value) + return True + + self._cached_flags[namespaced_flag_name] = value + + self._set_waffle_flag_metric(namespaced_flag_name, value) + return value + + def is_flag_active(self, flag_name, check_before_waffle_callback=None, flag_undefined_default=None): + """ + Overrides is_flag_active if setting USE_DEFAULT_TRUE_NAMESPACE is True. + """ + use_default_true_namespace = getattr(settings, _USE_DEFAULT_TRUE_NAMESPACE, False) + set_custom_metric('temp_use_default_true_namespace', use_default_true_namespace) + if use_default_true_namespace: + return self._is_flag_active(flag_name) + else: + return super().is_flag_active(flag_name, check_before_waffle_callback, flag_undefined_default=True) + + +DEFAULT_TRUE_WAFFLE_FLAG_NAMESPACE = DefaultTrueWaffleFlagNamespace(name='course_experience') + # Waffle flag to enable the separate course outline page and full width content. -COURSE_OUTLINE_PAGE_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'course_outline_page', flag_undefined_default=True) +# NOTE: The special namespace makes the default True and skips checking course overrides. +# TODO: TNL-7061: Perform the actual clean-up required to remove this flag. +COURSE_OUTLINE_PAGE_FLAG = CourseWaffleFlag(DEFAULT_TRUE_WAFFLE_FLAG_NAMESPACE, 'course_outline_page') # Waffle flag to enable a single unified "Course" tab. -UNIFIED_COURSE_TAB_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'unified_course_tab', flag_undefined_default=True) +# NOTE: The special namespace makes the default True and skips checking course overrides. +# TODO: TNL-7061: Perform the actual clean-up required to remove this flag. +UNIFIED_COURSE_TAB_FLAG = CourseWaffleFlag(DEFAULT_TRUE_WAFFLE_FLAG_NAMESPACE, 'unified_course_tab') # 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')