diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c610e15..f98bab2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,13 @@ Change Log Unreleased ********** +3.3.0 – 2023-08-16 +********************************************** + +Features +========= +* Add xpert summaries configuration by default for units + 3.2.0 – 2023-07-26 ********************************************** diff --git a/ai_aside/__init__.py b/ai_aside/__init__.py index ad142c4..d2e1878 100644 --- a/ai_aside/__init__.py +++ b/ai_aside/__init__.py @@ -2,6 +2,6 @@ A plugin containing xblocks and apps supporting GPT and other LLM use on edX. """ -__version__ = '3.2.0' +__version__ = '3.3.0' default_app_config = "ai_aside.apps.AiAsideConfig" diff --git a/ai_aside/config_api/api.py b/ai_aside/config_api/api.py index a131231..dda9fe7 100644 --- a/ai_aside/config_api/api.py +++ b/ai_aside/config_api/api.py @@ -1,7 +1,7 @@ """ Implements an API for updating unit and course settings. """ -from ai_aside.config_api.internal import NotFoundError, _get_course, _get_unit +from ai_aside.config_api.internal import NotFoundError, _get_course, _get_course_units, _get_unit from ai_aside.models import AIAsideCourseEnabled, AIAsideUnitEnabled from ai_aside.waffle import summaries_configuration_enabled @@ -49,6 +49,7 @@ def delete_course_settings(course_key): Raises NotFoundError if the settings are not found. """ + reset_course_unit_settings(course_key) record = _get_course(course_key) record.delete() @@ -69,6 +70,13 @@ def get_unit_settings(course_key, unit_key): return fields +def reset_course_unit_settings(course_key): + """ + Deletes the unit settings of a course. + """ + return _get_course_units(course_key).delete() + + def set_unit_settings(course_key, unit_key, settings): """ Sets the settings of a course's unit. @@ -112,13 +120,24 @@ def is_summary_config_enabled(course_key): return summaries_configuration_enabled(course_key) +def is_course_settings_present(course_key): + """ + Exist a course for the given key? + """ + try: + course = _get_course(course_key) + return course is not None + except NotFoundError: + return False + + def is_summary_enabled(course_key, unit_key=None): """ Gets the enabled state of a course's unit. It considers both the state of a unit's override and a course defaults. """ - # If the feature flag is disabled, always returns false. + # If the feature flag is disabled, always returns False. if not summaries_configuration_enabled(course_key): return False diff --git a/ai_aside/config_api/internal.py b/ai_aside/config_api/internal.py index fec2690..0560dad 100644 --- a/ai_aside/config_api/internal.py +++ b/ai_aside/config_api/internal.py @@ -31,3 +31,10 @@ def _get_unit(course_key, unit_key): raise NotFoundError from exc return record + + +def _get_course_units(course_key): + "Private method that gets a unit based on a course_key" + return AIAsideUnitEnabled.objects.filter( + course_key=course_key, + ) diff --git a/ai_aside/config_api/views.py b/ai_aside/config_api/views.py index 6dcfdc0..126ddbb 100644 --- a/ai_aside/config_api/views.py +++ b/ai_aside/config_api/views.py @@ -28,6 +28,7 @@ get_course_settings, get_unit_settings, is_summary_config_enabled, + reset_course_unit_settings, set_course_settings, set_unit_settings, ) @@ -80,12 +81,19 @@ def get(self, request, course_id=None): return APIResponse(success=True, data=settings) def post(self, request, course_id=None): - """Sets the enabled state for a course""" + """Update the course and reset if its necessary""" + # enabled: Updates the course enabled default state enabled = request.data.get('enabled') + # reset: If it is present, it will delete all unit settings, resetting them back to the default + reset = request.data.get('reset') + try: - set_course_settings(CourseKey.from_string(course_id), {'enabled': enabled}) + course_key = CourseKey.from_string(course_id) + set_course_settings(course_key, {'enabled': enabled}) + if reset: + reset_course_unit_settings(course_key) except InvalidKeyError: data = {'message': 'Invalid Key'} return APIResponse(http_status=status.HTTP_400_BAD_REQUEST, data=data) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/api/test_api.py b/tests/api/test_api.py index 91b122b..e174ffd 100644 --- a/tests/api/test_api.py +++ b/tests/api/test_api.py @@ -12,7 +12,9 @@ delete_unit_settings, get_course_settings, get_unit_settings, + is_course_settings_present, is_summary_enabled, + reset_course_unit_settings, set_course_settings, set_unit_settings, ) @@ -86,22 +88,47 @@ def test_get_course_settings_not_found(self): def test_course_delete(self): course_key = course_keys[0] + unit_key = unit_keys[0] AIAsideCourseEnabled.objects.create( course_key=course_key, enabled=True ) + AIAsideUnitEnabled.objects.create( + course_key=course_key, + unit_key=unit_key, + enabled=True + ) + delete_course_settings(course_key) - res = AIAsideCourseEnabled.objects.filter(course_key=course_key) + courses = AIAsideCourseEnabled.objects.filter(course_key=course_key) + units = AIAsideUnitEnabled.objects.filter(course_key=course_key) - self.assertEqual(res.count(), 0) + self.assertEqual(courses.count(), 0) + self.assertEqual(units.count(), 0) def test_course_delete_not_found(self): with self.assertRaises(NotFoundError): delete_course_settings(course_keys[1]) + def test_course_delete_not_found_reset_all_units(self): + course_key = course_keys[1] + unit_key = unit_keys[1] + + AIAsideUnitEnabled.objects.create( + course_key=course_key, + unit_key=unit_key, + ) + + units = AIAsideUnitEnabled.objects.filter(course_key=course_key) + + with self.assertRaises(NotFoundError): + self.assertEqual(units.count(), 1) + delete_course_settings(course_keys[1]) + self.assertEqual(units.count(), 0) + def test_set_unit_settings(self): course_key = course_keys[0] unit_key = unit_keys[0] @@ -285,3 +312,36 @@ def test_is_summary_enabled_disabled_feature_flag(self): self.assertFalse(is_summary_enabled(course_key_true, unit_key_non_existent)) self.assertFalse(is_summary_enabled(course_key_false, unit_key_non_existent)) self.assertFalse(is_summary_enabled(course_key_non_existent, unit_key_non_existent)) + + def test_reset_course_unit_settings(self): + course_key = course_keys[0] + + AIAsideUnitEnabled.objects.create( + course_key=course_key, + unit_key=unit_keys[0], + enabled=True + ) + + AIAsideUnitEnabled.objects.create( + course_key=course_key, + unit_key=unit_keys[1], + enabled=False + ) + + units = AIAsideUnitEnabled.objects.filter(course_key=course_key) + self.assertEqual(units.count(), 2) + + reset_course_unit_settings(course_key) + self.assertEqual(units.count(), 0) + + def test_is_course_settings_present(self): + course_key = course_keys[0] + + self.assertFalse(is_course_settings_present(course_key)) + + AIAsideCourseEnabled.objects.create( + course_key=course_key, + enabled=True, + ) + + self.assertTrue(is_course_settings_present(course_key)) diff --git a/tests/api/test_views.py b/tests/api/test_views.py index 73bac85..e34905a 100644 --- a/tests/api/test_views.py +++ b/tests/api/test_views.py @@ -109,21 +109,31 @@ def test_course_enabled_getter_404(self): def test_course_delete(self): course_id = course_keys[0] + unit_id = unit_keys[0] AIAsideCourseEnabled.objects.create( course_key=CourseKey.from_string(course_id), enabled=True, ) + AIAsideUnitEnabled.objects.create( + course_key=CourseKey.from_string(course_id), + unit_key=UsageKey.from_string(unit_id), + enabled=True, + ) + + courses = AIAsideCourseEnabled.objects.filter(course_key=course_id) + units = AIAsideUnitEnabled.objects.filter(course_key=course_id) + + self.assertEqual(courses.count(), 1) + self.assertEqual(units.count(), 1) api_url = reverse('api-course-settings', kwargs={'course_id': course_id}) response = self.client.delete(api_url) self.assertEqual(response.status_code, 200) self.assertEqual(response.data['response']['success'], True) - - res = AIAsideCourseEnabled.objects.filter(course_key=course_id) - - self.assertEqual(res.count(), 0) + self.assertEqual(courses.count(), 0) + self.assertEqual(units.count(), 0) def test_course_delete_404(self): course_id = course_keys[1] @@ -288,3 +298,24 @@ def test_unit_delete_404(self): response = self.client.delete(api_url) self.assertEqual(response.status_code, 404) + + def test_course_enabled_setter_enable_valid_and_reset(self): + course_id = course_keys[0] + unit_id = unit_keys[0] + + units = AIAsideUnitEnabled.objects.filter(course_key=course_id) + AIAsideUnitEnabled.objects.create( + course_key=CourseKey.from_string(course_id), + unit_key=UsageKey.from_string(unit_id), + enabled=True, + ) + + self.assertEqual(units.count(), 1) + + api_url = reverse('api-course-settings', kwargs={'course_id': course_id}) + + self.client.post(api_url, {'enabled': True, 'reset': False}, format='json') + self.assertEqual(units.count(), 1) + + self.client.post(api_url, {'enabled': False, 'reset': True}, format='json') + self.assertEqual(units.count(), 0)