diff --git a/openedx/core/djangoapps/waffle_utils/tests/test_init.py b/openedx/core/djangoapps/waffle_utils/tests/test_init.py index 3a85d9676ee03f5ea93fc8d22e7d0861ad3f0f68..436279a2270c4791a52790955372c4908e63aca4 100644 --- a/openedx/core/djangoapps/waffle_utils/tests/test_init.py +++ b/openedx/core/djangoapps/waffle_utils/tests/test_init.py @@ -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__, ) diff --git a/openedx/core/djangoapps/waffle_utils/tests/test_views.py b/openedx/core/djangoapps/waffle_utils/tests/test_views.py index 726a509a396f261ef5ce83498716a5c4d663218e..0f8152a858c5a166da6221154c79c2491d5ea637 100644 --- a/openedx/core/djangoapps/waffle_utils/tests/test_views.py +++ b/openedx/core/djangoapps/waffle_utils/tests/test_views.py @@ -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 diff --git a/openedx/core/djangoapps/waffle_utils/views.py b/openedx/core/djangoapps/waffle_utils/views.py index cb6a5f9e96d38d3336249769cfeca56c9902cdcd..0967c1d65676e3e28c3598948885821c0cc9ad45 100644 --- a/openedx/core/djangoapps/waffle_utils/views.py +++ b/openedx/core/djangoapps/waffle_utils/views.py @@ -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 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) - - -def _get_all_waffle_switches(): +class CourseOverrideToggleStateReport(ToggleStateReport): """ - Gets all waffle switches and their state. + Override some of the methods from ToggleStateReport to expose toggles from WaffleFlagCourseOverrideModel objects. """ - 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_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 _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 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 -def _add_waffle_switch_state(switches_dict): +class ToggleStateView(views.APIView): """ - Add waffle switch state from the waffle Switch model. + An endpoint for displaying the state of toggles in edx-platform. """ - 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) + authentication_classes = ( + JwtAuthentication, + SessionAuthentication, + ) + permission_classes = (IsStaff,) -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(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 _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): +def _add_waffle_flag_course_override_state(flags_dict): """ - Add details from waffle flag instances, like code_owner. + Add waffle flag course override state from the WaffleFlagCourseOverrideModel model. """ - 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) + 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 _add_waffle_flag_course_override_state(flags_dict): +def _get_flag_course_overrides(): """ - Add waffle flag course override state from the WaffleFlagCourseOverrideModel model. + 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 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index bc99d764f87c5018e35c6a34947f784eb95dbd31..1080fb24cc5366b4ec4cc3ef649c6dc2d4837ef9 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -113,7 +113,7 @@ edx-search==3.0.0 # via -r requirements/edx/base.in edx-sga==0.16.0 # via -r requirements/edx/base.in edx-submissions==3.2.4 # via -r requirements/edx/base.in, ora2 edx-tincan-py35==1.0.0 # via edx-enterprise -edx-toggles==4.0.0 # via -r requirements/edx/base.in, edx-ace, edx-completion, edx-event-routing-backends, edxval, ora2 +edx-toggles==4.1.0 # via -r requirements/edx/base.in, edx-ace, edx-completion, edx-event-routing-backends, edxval, ora2 edx-user-state-client==1.3.0 # via -r requirements/edx/base.in edx-when==2.0.0 # via -r requirements/edx/base.in, edx-proctoring edxval==2.0.1 # via -r requirements/edx/base.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index eb5abdcf9e58a71c72a2881fe20ad3ad07e30d7b..c6861df21e82766b30cc41d2df8f0ea689316921 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -126,7 +126,7 @@ edx-sga==0.16.0 # via -r requirements/edx/testing.txt edx-sphinx-theme==2.0.0 # via -r requirements/edx/development.in edx-submissions==3.2.4 # via -r requirements/edx/testing.txt, ora2 edx-tincan-py35==1.0.0 # via -r requirements/edx/testing.txt, edx-enterprise -edx-toggles==4.0.0 # via -r requirements/edx/testing.txt, edx-ace, edx-completion, edx-event-routing-backends, edxval, ora2 +edx-toggles==4.1.0 # via -r requirements/edx/testing.txt, edx-ace, edx-completion, edx-event-routing-backends, edxval, ora2 edx-user-state-client==1.3.0 # via -r requirements/edx/testing.txt edx-when==2.0.0 # via -r requirements/edx/testing.txt, edx-proctoring edxval==2.0.1 # via -r requirements/edx/testing.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 7ee7c888d947be0bff753a6f17654640e077f5c0..bd2c6715f1d9f48f8428a9a758fc57f711553456 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -122,7 +122,7 @@ edx-search==3.0.0 # via -r requirements/edx/base.txt edx-sga==0.16.0 # via -r requirements/edx/base.txt edx-submissions==3.2.4 # via -r requirements/edx/base.txt, ora2 edx-tincan-py35==1.0.0 # via -r requirements/edx/base.txt, edx-enterprise -edx-toggles==4.0.0 # via -r requirements/edx/base.txt, edx-ace, edx-completion, edx-event-routing-backends, edxval, ora2 +edx-toggles==4.1.0 # via -r requirements/edx/base.txt, edx-ace, edx-completion, edx-event-routing-backends, edxval, ora2 edx-user-state-client==1.3.0 # via -r requirements/edx/base.txt edx-when==2.0.0 # via -r requirements/edx/base.txt, edx-proctoring edxval==2.0.1 # via -r requirements/edx/base.txt