Merge pull request #26325 from regisb/regisb/migrate-toggle-state-report
[BD-21] Migrate toggle state report to edx-toggles
This commit is contained in:
@@ -8,6 +8,7 @@ from collections import namedtuple
|
||||
from copy import deepcopy
|
||||
|
||||
import ddt
|
||||
from edx_toggles.toggles.testutils import override_waffle_switch
|
||||
import httpretty
|
||||
import mock
|
||||
import six
|
||||
@@ -18,7 +19,6 @@ from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from pytz import utc
|
||||
from testfixtures import LogCapture
|
||||
from waffle.testutils import override_switch
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
|
||||
@@ -1473,7 +1473,7 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase):
|
||||
assert data['discount_data'] == mock_discount_data
|
||||
|
||||
@httpretty.activate
|
||||
@override_switch(ALWAYS_CALCULATE_PROGRAM_PRICE_AS_ANONYMOUS_USER.name, active=True)
|
||||
@override_waffle_switch(ALWAYS_CALCULATE_PROGRAM_PRICE_AS_ANONYMOUS_USER, True)
|
||||
def test_fetching_program_price_when_forced_as_anonymous_user(self):
|
||||
"""
|
||||
When all users are forced as anonymous, all requests to calculate the program
|
||||
|
||||
@@ -13,7 +13,6 @@ from waffle.testutils import override_flag
|
||||
|
||||
from .. import (
|
||||
CourseWaffleFlag,
|
||||
WaffleFlagNamespace,
|
||||
WaffleSwitchNamespace,
|
||||
)
|
||||
from ..models import WaffleFlagCourseOverrideModel
|
||||
@@ -33,8 +32,7 @@ class TestCourseWaffleFlag(TestCase):
|
||||
|
||||
TEST_COURSE_KEY = CourseKey.from_string("edX/DemoX/Demo_Course")
|
||||
TEST_COURSE_2_KEY = CourseKey.from_string("edX/DemoX/Demo_Course_2")
|
||||
TEST_NAMESPACE = WaffleFlagNamespace(NAMESPACE_NAME)
|
||||
TEST_COURSE_FLAG = CourseWaffleFlag(TEST_NAMESPACE, FLAG_NAME, __name__)
|
||||
TEST_COURSE_FLAG = CourseWaffleFlag(NAMESPACE_NAME, FLAG_NAME, __name__)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
@@ -83,7 +81,7 @@ class TestCourseWaffleFlag(TestCase):
|
||||
Test flag with undefined waffle flag.
|
||||
"""
|
||||
test_course_flag = CourseWaffleFlag(
|
||||
self.TEST_NAMESPACE,
|
||||
self.NAMESPACE_NAME,
|
||||
self.FLAG_NAME,
|
||||
__name__,
|
||||
)
|
||||
@@ -109,7 +107,7 @@ class TestCourseWaffleFlag(TestCase):
|
||||
"""
|
||||
crum.set_current_request(None)
|
||||
test_course_flag = CourseWaffleFlag(
|
||||
self.TEST_NAMESPACE,
|
||||
self.NAMESPACE_NAME,
|
||||
self.FLAG_NAME,
|
||||
__name__,
|
||||
)
|
||||
@@ -121,7 +119,7 @@ class TestCourseWaffleFlag(TestCase):
|
||||
"""
|
||||
crum.set_current_request(None)
|
||||
test_course_flag = CourseWaffleFlag(
|
||||
self.TEST_NAMESPACE,
|
||||
self.NAMESPACE_NAME,
|
||||
self.FLAG_NAME,
|
||||
__name__,
|
||||
)
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
"""
|
||||
Tests for waffle utils views.
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from edx_toggles.toggles import SettingDictToggle, SettingToggle
|
||||
from edx_toggles.toggles.testutils import override_waffle_flag
|
||||
from rest_framework.test import APIRequestFactory
|
||||
from waffle.testutils import override_switch
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
|
||||
@@ -19,118 +14,36 @@ TEST_WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace("test")
|
||||
TEST_WAFFLE_FLAG = WaffleFlag(TEST_WAFFLE_FLAG_NAMESPACE, "flag", __name__)
|
||||
|
||||
|
||||
# TODO: Missing coverage for:
|
||||
# - computed_status
|
||||
class ToggleStateViewTests(TestCase): # lint-amnesty, pylint: disable=missing-class-docstring
|
||||
"""
|
||||
Tests for the toggle state report view.
|
||||
"""
|
||||
|
||||
def test_success_for_staff(self):
|
||||
response = self._get_toggle_state_response()
|
||||
response = get_toggle_state_response()
|
||||
assert response.status_code == 200
|
||||
assert response.data
|
||||
|
||||
def test_failure_for_non_staff(self):
|
||||
response = self._get_toggle_state_response(is_staff=False)
|
||||
response = get_toggle_state_response(is_staff=False)
|
||||
assert response.status_code == 403
|
||||
|
||||
@override_waffle_flag(TEST_WAFFLE_FLAG, True)
|
||||
def test_response_with_waffle_flag(self):
|
||||
response = self._get_toggle_state_response()
|
||||
assert 'waffle_flags' in response.data
|
||||
assert response.data['waffle_flags']
|
||||
waffle_names = [waffle["name"] for waffle in response.data['waffle_flags']]
|
||||
assert 'test.flag' in waffle_names
|
||||
|
||||
@override_switch('test.switch', True)
|
||||
def test_response_with_waffle_switch(self):
|
||||
response = self._get_toggle_state_response()
|
||||
assert 'waffle_switches' in response.data
|
||||
assert response.data['waffle_switches']
|
||||
waffle_names = [waffle["name"] for waffle in response.data['waffle_switches']]
|
||||
assert 'test.switch' in waffle_names
|
||||
|
||||
def test_response_with_setting_toggle(self):
|
||||
_toggle = SettingToggle("MYSETTING", default=False, module_name="module1")
|
||||
with override_settings(MYSETTING=True):
|
||||
response = self._get_toggle_state_response()
|
||||
|
||||
assert {'name': 'MYSETTING', 'is_active': True, 'module': 'module1', 'class': 'SettingToggle'}\
|
||||
in response.data['django_settings']
|
||||
|
||||
def test_response_with_existing_setting_dict_toggle(self):
|
||||
response = self._get_toggle_state_response()
|
||||
assert {'name': "FEATURES['MILESTONES_APP']", 'is_active': True,
|
||||
'module': 'common.djangoapps.util.milestones_helpers',
|
||||
'class': 'SettingDictToggle'} in response.data['django_settings']
|
||||
|
||||
def test_response_with_new_setting_dict_toggle(self):
|
||||
_toggle = SettingDictToggle(
|
||||
"CUSTOM_FEATURES", "MYSETTING", default=False, module_name="module1"
|
||||
)
|
||||
with override_settings(CUSTOM_FEATURES={"MYSETTING": True}):
|
||||
response = self._get_toggle_state_response()
|
||||
|
||||
setting_dict = {toggle["name"]: toggle for toggle in response.data["django_settings"]}
|
||||
|
||||
assert {'name': "CUSTOM_FEATURES['MYSETTING']", 'is_active': True, 'module': 'module1',
|
||||
'class': 'SettingDictToggle'} == setting_dict["CUSTOM_FEATURES['MYSETTING']"]
|
||||
|
||||
def test_setting_overridden_by_setting_toggle(self):
|
||||
_toggle2 = SettingToggle(
|
||||
"MYSETTING2", module_name="module1"
|
||||
)
|
||||
_toggle3 = SettingDictToggle(
|
||||
"MYDICT", "MYSETTING3", module_name="module1"
|
||||
)
|
||||
with override_settings(MYSETTING1=True, MYSETTING2=False, MYDICT={"MYSETTING3": False}):
|
||||
# Need to pre-load settings, otherwise they are not picked up by the view
|
||||
assert settings.MYSETTING1
|
||||
response = self._get_toggle_state_response()
|
||||
|
||||
setting_dict = {toggle["name"]: toggle for toggle in response.data["django_settings"]}
|
||||
|
||||
# Check that Django settings for which a SettingToggle exists have both the correct is_active and class values
|
||||
assert setting_dict['MYSETTING1']['is_active']
|
||||
assert 'class' not in setting_dict['MYSETTING1']
|
||||
assert not setting_dict['MYSETTING2']['is_active']
|
||||
assert 'SettingToggle' == setting_dict['MYSETTING2']['class']
|
||||
assert not setting_dict["MYDICT['MYSETTING3']"]['is_active']
|
||||
assert 'SettingDictToggle' == setting_dict["MYDICT['MYSETTING3']"]['class']
|
||||
|
||||
def test_no_duplicate_setting_toggle(self):
|
||||
_toggle1 = SettingToggle(
|
||||
"MYSETTING1", module_name="module1"
|
||||
)
|
||||
_toggle2 = SettingDictToggle(
|
||||
"MYDICT", "MYSETTING2", module_name="module1"
|
||||
)
|
||||
with override_settings(MYSETTING1=True, MYDICT={"MYSETTING2": False}):
|
||||
response = self._get_toggle_state_response()
|
||||
|
||||
# Check there are no duplicate setting/toggle
|
||||
response_toggles_1 = [toggle for toggle in response.data["django_settings"] if toggle["name"] == "MYSETTING1"]
|
||||
response_toggles_2 = [
|
||||
toggle for toggle in response.data["django_settings"] if toggle["name"] == "MYDICT['MYSETTING2']"
|
||||
]
|
||||
assert 1 == len(response_toggles_1)
|
||||
assert 1 == len(response_toggles_2)
|
||||
|
||||
def test_code_owners_without_module_information(self):
|
||||
# Create a waffle flag without any associated module_name
|
||||
waffle_flag = WaffleFlag(TEST_WAFFLE_FLAG_NAMESPACE, "flag2", module_name=None)
|
||||
response = self._get_toggle_state_response(is_staff=True)
|
||||
|
||||
result = [
|
||||
flag for flag in response.data["waffle_flags"] if flag["name"] == waffle_flag.name
|
||||
][0]
|
||||
assert 'code_owner' not in result
|
||||
response = get_toggle_state_response()
|
||||
assert {
|
||||
"name": "FEATURES['MILESTONES_APP']",
|
||||
"is_active": True,
|
||||
"module": "common.djangoapps.util.milestones_helpers",
|
||||
"class": "SettingDictToggle",
|
||||
} in response.data["django_settings"]
|
||||
|
||||
def test_course_overrides(self):
|
||||
models.WaffleFlagCourseOverrideModel.objects.create(waffle_flag="my.flag", enabled=True)
|
||||
course_overrides = {}
|
||||
|
||||
# pylint: disable=protected-access
|
||||
toggle_state_views._add_waffle_flag_course_override_state(course_overrides)
|
||||
toggle_state_views._add_waffle_flag_computed_status(course_overrides)
|
||||
report = toggle_state_views.CourseOverrideToggleStateReport()
|
||||
report.add_waffle_flag_instances(course_overrides)
|
||||
report.add_waffle_flag_computed_status(course_overrides)
|
||||
|
||||
assert 'my.flag' in course_overrides
|
||||
assert 'course_overrides' in course_overrides['my.flag']
|
||||
@@ -139,11 +52,39 @@ class ToggleStateViewTests(TestCase): # lint-amnesty, pylint: disable=missing-c
|
||||
assert 'on' == course_overrides['my.flag']['course_overrides'][0]['force']
|
||||
assert 'both' == course_overrides['my.flag']['computed_status']
|
||||
|
||||
def _get_toggle_state_response(self, is_staff=True): # lint-amnesty, pylint: disable=missing-function-docstring
|
||||
request = APIRequestFactory().get('/api/toggles/state/')
|
||||
user = UserFactory()
|
||||
user.is_staff = is_staff
|
||||
request.user = user
|
||||
view = toggle_state_views.ToggleStateView.as_view()
|
||||
response = view(request)
|
||||
return response
|
||||
def test_computed_status(self):
|
||||
models.WaffleFlagCourseOverrideModel.objects.create(
|
||||
waffle_flag="my.overriddenflag1", enabled=True, course_id="org/course/id"
|
||||
)
|
||||
models.WaffleFlagCourseOverrideModel.objects.create(
|
||||
waffle_flag="my.overriddenflag2", enabled=True
|
||||
)
|
||||
models.WaffleFlagCourseOverrideModel.objects.create(
|
||||
waffle_flag="my.disabledflag1", enabled=False, course_id="org/course/id"
|
||||
)
|
||||
|
||||
course_overrides = {}
|
||||
report = toggle_state_views.CourseOverrideToggleStateReport()
|
||||
report.add_waffle_flag_instances(course_overrides)
|
||||
report.add_waffle_flag_computed_status(course_overrides)
|
||||
|
||||
assert "both" == course_overrides["my.overriddenflag1"]["computed_status"]
|
||||
assert "org/course/id" == course_overrides["my.overriddenflag1"]["course_overrides"][0]["course_id"]
|
||||
assert "on" == course_overrides["my.overriddenflag1"]["course_overrides"][0]["force"]
|
||||
|
||||
assert "both" == course_overrides["my.overriddenflag2"]["computed_status"]
|
||||
assert "None" == course_overrides["my.overriddenflag2"]["course_overrides"][0]["course_id"]
|
||||
assert "on" == course_overrides["my.overriddenflag2"]["course_overrides"][0]["force"]
|
||||
|
||||
assert "my.disabledflag1" not in course_overrides
|
||||
|
||||
|
||||
def get_toggle_state_response(is_staff=True):
|
||||
"""
|
||||
Query the toggle state API endpoint.
|
||||
"""
|
||||
request = APIRequestFactory().get('/api/toggles/state/')
|
||||
request.user = UserFactory(is_staff=is_staff)
|
||||
view = toggle_state_views.ToggleStateView.as_view()
|
||||
response = view(request)
|
||||
return response
|
||||
|
||||
@@ -3,277 +3,121 @@ Views that we will use to view toggle state in edx-platform.
|
||||
"""
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
from edx_django_utils.monitoring import get_code_owner_from_module
|
||||
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
|
||||
from edx_rest_framework_extensions.permissions import IsStaff
|
||||
from edx_toggles.toggles import SettingDictToggle, SettingToggle
|
||||
from edx_toggles.toggles.state import ToggleStateReport, get_or_create_toggle_response
|
||||
from rest_framework import permissions, views
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.response import Response
|
||||
from waffle.models import Flag, Switch
|
||||
|
||||
from . import WaffleFlag, WaffleSwitch
|
||||
from .models import WaffleFlagCourseOverrideModel
|
||||
|
||||
|
||||
class CourseOverrideToggleStateReport(ToggleStateReport):
|
||||
"""
|
||||
Override some of the methods from ToggleStateReport to expose toggles from WaffleFlagCourseOverrideModel objects.
|
||||
"""
|
||||
|
||||
def add_waffle_flag_instances(self, flags_dict):
|
||||
"""
|
||||
Append objects from WaffleFlagCourseOverrideModel.
|
||||
"""
|
||||
super().add_waffle_flag_state(flags_dict)
|
||||
_add_waffle_flag_course_override_state(flags_dict)
|
||||
|
||||
def get_waffle_flag_computed_status(self, flag):
|
||||
"""
|
||||
Produce correct "computed_status" values for WaffleFlagCourseOverrideModel instances.
|
||||
"""
|
||||
computed_status = super().get_waffle_flag_computed_status(flag)
|
||||
# check course overrides only if computed_status is not already 'both'
|
||||
if computed_status != "both" and "course_overrides" in flag:
|
||||
has_force_on = any(
|
||||
override["force"] == "on" for override in flag["course_overrides"]
|
||||
)
|
||||
has_force_off = any(
|
||||
override["force"] == "off" for override in flag["course_overrides"]
|
||||
)
|
||||
if has_force_on and has_force_off:
|
||||
computed_status = "both"
|
||||
elif has_force_on:
|
||||
computed_status = "on" if computed_status == "on" else "both"
|
||||
elif has_force_off:
|
||||
computed_status = "off" if computed_status == "off" else "both"
|
||||
return computed_status
|
||||
|
||||
|
||||
class ToggleStateView(views.APIView):
|
||||
"""
|
||||
An endpoint for displaying the state of toggles in edx-platform.
|
||||
"""
|
||||
authentication_classes = (JwtAuthentication, SessionAuthentication,)
|
||||
permission_classes = (permissions.IsAuthenticated, IsStaff,)
|
||||
|
||||
def get(self, request): # lint-amnesty, pylint: disable=missing-function-docstring
|
||||
response = OrderedDict()
|
||||
response['waffle_flags'] = _get_all_waffle_flags()
|
||||
response['waffle_switches'] = _get_all_waffle_switches()
|
||||
response['django_settings'] = _get_settings_state()
|
||||
return Response(response)
|
||||
authentication_classes = (
|
||||
JwtAuthentication,
|
||||
SessionAuthentication,
|
||||
)
|
||||
permission_classes = (IsStaff,)
|
||||
|
||||
|
||||
def _get_all_waffle_switches():
|
||||
"""
|
||||
Gets all waffle switches and their state.
|
||||
"""
|
||||
switches_dict = {}
|
||||
_add_waffle_switch_instances(switches_dict)
|
||||
_add_waffle_switch_state(switches_dict)
|
||||
_add_waffle_switch_computed_status(switches_dict)
|
||||
switch_list = list(switches_dict.values())
|
||||
switch_list.sort(key=lambda toggle: toggle['name'])
|
||||
return switch_list
|
||||
|
||||
|
||||
def _add_waffle_switch_instances(switches_dict):
|
||||
"""
|
||||
Add details from waffle switch instances, like code_owner.
|
||||
"""
|
||||
waffle_switch_instances = WaffleSwitch.get_instances()
|
||||
for switch_instance in waffle_switch_instances:
|
||||
switch = _get_or_create_toggle_response(switches_dict, switch_instance.name)
|
||||
_add_toggle_instance_details(switch, switch_instance)
|
||||
|
||||
|
||||
def _add_waffle_switch_state(switches_dict):
|
||||
"""
|
||||
Add waffle switch state from the waffle Switch model.
|
||||
"""
|
||||
waffle_switches = Switch.objects.all()
|
||||
for switch_data in waffle_switches:
|
||||
switch = _get_or_create_toggle_response(switches_dict, switch_data.name)
|
||||
switch['is_active'] = 'true' if switch_data.active else 'false'
|
||||
if switch_data.note:
|
||||
switch['note'] = switch_data.note
|
||||
switch['created'] = str(switch_data.created)
|
||||
switch['modified'] = str(switch_data.modified)
|
||||
|
||||
|
||||
def _add_waffle_switch_computed_status(switch_dict):
|
||||
"""
|
||||
Add computed status to each waffle switch.
|
||||
"""
|
||||
for switch in switch_dict.values():
|
||||
computed_status = 'off'
|
||||
if 'is_active' in switch:
|
||||
if switch['is_active'] == 'true':
|
||||
computed_status = 'on'
|
||||
else:
|
||||
computed_status = 'off'
|
||||
switch['computed_status'] = computed_status
|
||||
|
||||
|
||||
def _get_all_waffle_flags():
|
||||
"""
|
||||
Gets all waffle flags and their state.
|
||||
"""
|
||||
flags_dict = {}
|
||||
_add_waffle_flag_instances(flags_dict)
|
||||
_add_waffle_flag_state(flags_dict)
|
||||
_add_waffle_flag_course_override_state(flags_dict)
|
||||
_add_waffle_flag_computed_status(flags_dict)
|
||||
flag_list = list(flags_dict.values())
|
||||
flag_list.sort(key=lambda toggle: toggle['name'])
|
||||
return flag_list
|
||||
|
||||
|
||||
def _add_waffle_flag_instances(flags_dict):
|
||||
"""
|
||||
Add details from waffle flag instances, like code_owner.
|
||||
"""
|
||||
waffle_flag_instances = WaffleFlag.get_instances()
|
||||
for flag_instance in waffle_flag_instances:
|
||||
flag = _get_or_create_toggle_response(flags_dict, flag_instance.name)
|
||||
_add_toggle_instance_details(flag, flag_instance)
|
||||
|
||||
|
||||
def _add_waffle_flag_state(flags_dict):
|
||||
"""
|
||||
Add waffle flag state from the waffle Flag model.
|
||||
"""
|
||||
waffle_flags = Flag.objects.all()
|
||||
for flag_data in waffle_flags:
|
||||
flag = _get_or_create_toggle_response(flags_dict, flag_data.name)
|
||||
if flag_data.everyone is True:
|
||||
everyone = 'yes'
|
||||
elif flag_data.everyone is False:
|
||||
everyone = 'no'
|
||||
else:
|
||||
everyone = 'unknown'
|
||||
flag['everyone'] = everyone
|
||||
if flag_data.note:
|
||||
flag['note'] = flag_data.note
|
||||
flag['created'] = str(flag_data.created)
|
||||
flag['modified'] = str(flag_data.modified)
|
||||
def get(self, request):
|
||||
"""
|
||||
Expose toggle state report dict as a view.
|
||||
"""
|
||||
report = CourseOverrideToggleStateReport().as_dict()
|
||||
_add_waffle_flag_course_override_state(report["waffle_flags"])
|
||||
return Response(report)
|
||||
|
||||
|
||||
def _add_waffle_flag_course_override_state(flags_dict):
|
||||
"""
|
||||
Add waffle flag course override state from the WaffleFlagCourseOverrideModel model.
|
||||
"""
|
||||
|
||||
flag_course_overrides = _get_flag_course_overrides()
|
||||
for flag_name, course_overrides_dict in flag_course_overrides.items():
|
||||
course_overrides = [
|
||||
course_override
|
||||
for course_override in course_overrides_dict.values()
|
||||
if not course_override.get("disabled")
|
||||
]
|
||||
if course_overrides:
|
||||
flag = get_or_create_toggle_response(flags_dict, flag_name)
|
||||
flag["course_overrides"] = course_overrides
|
||||
|
||||
|
||||
def _get_flag_course_overrides():
|
||||
"""
|
||||
Return flag objects from WaffleFlagCourseOverrideModel instances.
|
||||
"""
|
||||
# This dict is keyed by flag name, and contains dicts keyed by course_id, the contains
|
||||
# the final dict of metadata for a single course override that will be returned.
|
||||
flag_course_overrides = OrderedDict()
|
||||
# Note: We can't just get enabled records, because if a historical record is enabled but
|
||||
# the current record is disabled, we would not know this. We get all records, and mark
|
||||
# some overrides as disabled, and then later filter the disabled records.
|
||||
course_overrides_data = WaffleFlagCourseOverrideModel.objects.all()
|
||||
course_overrides_data = course_overrides_data.order_by('waffle_flag', 'course_id', '-change_date')
|
||||
# the current record is disabled, we would not know this. We get all records, and mark
|
||||
# some overrides as disabled, and then later filter the disabled records.
|
||||
course_overrides_data = WaffleFlagCourseOverrideModel.objects.all().order_by(
|
||||
"waffle_flag", "course_id", "-change_date"
|
||||
)
|
||||
for course_override_data in course_overrides_data:
|
||||
if course_override_data.enabled:
|
||||
course_override_fields = {
|
||||
"force": course_override_data.override_choice,
|
||||
"modified": str(course_override_data.change_date),
|
||||
}
|
||||
else:
|
||||
# The current record may be disabled, but later history might be enabled.
|
||||
# We'll filter these disabled records below.
|
||||
course_override_fields = {"disabled": True}
|
||||
course_override_created_at = str(course_override_data.change_date)
|
||||
|
||||
flag_name = course_override_data.waffle_flag
|
||||
course_id = str(course_override_data.course_id)
|
||||
if flag_name not in flag_course_overrides:
|
||||
flag_course_overrides[flag_name] = OrderedDict()
|
||||
course_overrides = flag_course_overrides[flag_name]
|
||||
if course_id not in course_overrides:
|
||||
course_overrides[course_id] = OrderedDict()
|
||||
course_override = course_overrides[course_id]
|
||||
course_override = flag_course_overrides.setdefault(
|
||||
flag_name, OrderedDict()
|
||||
).setdefault(course_id, OrderedDict())
|
||||
# data is reverse ordered by date, so the first record is the current record
|
||||
if 'course_id' not in course_override:
|
||||
course_override['course_id'] = course_id
|
||||
if not course_override_data.enabled:
|
||||
# The current record may be disabled, but later history might be enabled.
|
||||
# We'll filter these disabled records below.
|
||||
course_override['disabled'] = True
|
||||
else:
|
||||
course_override['force'] = course_override_data.override_choice
|
||||
course_override['modified'] = str(course_override_data.change_date)
|
||||
if "course_id" not in course_override:
|
||||
course_override["course_id"] = course_id
|
||||
course_override.update(course_override_fields)
|
||||
# data is reverse ordered by date, so the last record is the oldest record
|
||||
course_override['created'] = str(course_override_data.change_date)
|
||||
|
||||
for flag_name, course_overrides_dict in flag_course_overrides.items():
|
||||
course_overrides = [
|
||||
course_override for course_override in course_overrides_dict.values()
|
||||
if 'disabled' not in course_override
|
||||
]
|
||||
if course_overrides:
|
||||
flag = _get_or_create_toggle_response(flags_dict, flag_name)
|
||||
flag['course_overrides'] = course_overrides
|
||||
|
||||
|
||||
def _add_waffle_flag_computed_status(flags_dict):
|
||||
"""
|
||||
Add computed status to each waffle flag.
|
||||
"""
|
||||
for flag in flags_dict.values():
|
||||
computed_status = 'off'
|
||||
if 'everyone' in flag:
|
||||
if flag['everyone'] == 'yes':
|
||||
computed_status = 'on'
|
||||
elif flag['everyone'] == 'unknown':
|
||||
computed_status = 'both'
|
||||
# check course overrides only if computed_status is not already 'both'
|
||||
if computed_status != 'both' and 'course_overrides' in flag:
|
||||
has_force_on = any(override['force'] == 'on' for override in flag['course_overrides'])
|
||||
has_force_off = any(override['force'] == 'off' for override in flag['course_overrides'])
|
||||
if has_force_on and has_force_off:
|
||||
computed_status = 'both'
|
||||
elif has_force_on:
|
||||
computed_status = 'on' if computed_status == 'on' else 'both'
|
||||
elif has_force_off:
|
||||
computed_status = 'off' if computed_status == 'off' else 'both'
|
||||
flag['computed_status'] = computed_status
|
||||
|
||||
|
||||
def _get_settings_state():
|
||||
"""
|
||||
Return a list of setting-based toggles: Django settings, SettingToggle and SettingDictToggle instances.
|
||||
SettingToggle and SettingDictToggle override the settings with identical names (if any).
|
||||
"""
|
||||
settings_dict = {}
|
||||
_add_settings(settings_dict)
|
||||
_add_setting_toggles(settings_dict)
|
||||
_add_setting_dict_toggles(settings_dict)
|
||||
return sorted(settings_dict.values(), key=(lambda toggle: toggle['name']))
|
||||
|
||||
|
||||
def _add_settings(settings_dict):
|
||||
"""
|
||||
Fill the `settings_dict`: will only include values that are set to true or false.
|
||||
"""
|
||||
for setting_name, setting_value in vars(settings).items():
|
||||
if isinstance(setting_value, dict):
|
||||
for dict_name, dict_value in setting_value.items():
|
||||
if isinstance(dict_value, bool):
|
||||
name = setting_dict_name(setting_name, dict_name)
|
||||
toggle_response = _get_or_create_toggle_response(settings_dict, name)
|
||||
toggle_response['is_active'] = dict_value
|
||||
elif isinstance(setting_value, bool):
|
||||
toggle_response = _get_or_create_toggle_response(settings_dict, setting_name)
|
||||
toggle_response['is_active'] = setting_value
|
||||
|
||||
|
||||
def _add_setting_toggles(settings_dict):
|
||||
"""
|
||||
Fill the `settings_dict` with values from the list of SettingToggle instances.
|
||||
"""
|
||||
for toggle in SettingToggle.get_instances():
|
||||
toggle_response = _get_or_create_toggle_response(settings_dict, toggle.name)
|
||||
toggle_response["is_active"] = toggle.is_enabled()
|
||||
_add_toggle_instance_details(toggle_response, toggle)
|
||||
|
||||
|
||||
def _add_toggle_instance_details(toggle, toggle_instance):
|
||||
"""
|
||||
Add details (class, module, code_owner) from a specific toggle instance.
|
||||
"""
|
||||
toggle['class'] = toggle_instance.__class__.__name__
|
||||
toggle['module'] = toggle_instance.module_name
|
||||
if toggle_instance.module_name:
|
||||
code_owner = get_code_owner_from_module(toggle_instance.module_name)
|
||||
if code_owner:
|
||||
toggle['code_owner'] = code_owner
|
||||
|
||||
|
||||
def _add_setting_dict_toggles(settings_dict):
|
||||
"""
|
||||
Fill the `settings_dict` with values from the list of SettingDictToggle instances.
|
||||
"""
|
||||
for toggle in SettingDictToggle.get_instances():
|
||||
name = setting_dict_name(toggle.name, toggle.key)
|
||||
toggle_response = _get_or_create_toggle_response(settings_dict, name)
|
||||
toggle_response["is_active"] = toggle.is_enabled()
|
||||
_add_toggle_instance_details(toggle_response, toggle)
|
||||
|
||||
|
||||
def _get_or_create_toggle_response(toggles_dict, toggle_name):
|
||||
"""
|
||||
Gets or creates a toggle response dict and adds it to the toggles_dict.
|
||||
|
||||
Returns:
|
||||
Either the pre-existing toggle response, or a new toggle dict with its name set.
|
||||
|
||||
"""
|
||||
if toggle_name in toggles_dict:
|
||||
return toggles_dict[toggle_name]
|
||||
toggle = OrderedDict()
|
||||
toggle['name'] = toggle_name
|
||||
toggles_dict[toggle_name] = toggle
|
||||
return toggle
|
||||
|
||||
|
||||
def setting_dict_name(dict_name, key):
|
||||
"""
|
||||
Return the name associated to a `dict_name[key]` setting.
|
||||
"""
|
||||
return "{dict_name}['{key}']".format(dict_name=dict_name, key=key)
|
||||
course_override["created"] = course_override_created_at
|
||||
return flag_course_overrides
|
||||
|
||||
Reference in New Issue
Block a user