diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 86e2b15..16e2707 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,8 +13,13 @@ Change Log Unreleased ~~~~~~~~~~ + +[3.0.0] - 2024-09-30 +~~~~~~~~~~~~~~~~~~~~ * Add platform verification id field to the VerifiedName model * Integrate platform verification id into app +* Added event handlers for new IDV events on the VerifiedName model +* Removed event handlers for SoftwareSecurePhotoVerification updates. This is a breaking change. [2.4.0] - 2024-04-23 ~~~~~~~~~~~~~~~~~~~~ diff --git a/edx_name_affirmation/__init__.py b/edx_name_affirmation/__init__.py index 89d2851..a600169 100644 --- a/edx_name_affirmation/__init__.py +++ b/edx_name_affirmation/__init__.py @@ -2,4 +2,4 @@ Django app housing name affirmation logic. """ -__version__ = '2.4.2' +__version__ = '3.0.0' diff --git a/edx_name_affirmation/apps.py b/edx_name_affirmation/apps.py index dd4942d..e5a08fb 100644 --- a/edx_name_affirmation/apps.py +++ b/edx_name_affirmation/apps.py @@ -25,13 +25,9 @@ class EdxNameAffirmationConfig(AppConfig): 'relative_path': 'handlers', 'receivers': [ { - 'receiver_func_name': 'idv_attempt_handler', - 'signal_path': 'lms.djangoapps.verify_student.signals.signals.idv_update_signal', - }, - { - 'receiver_func_name': 'idv_delete_handler', + 'receiver_func_name': 'platform_verification_delete_handler', 'signal_path': 'django.db.models.signals.post_delete', - 'sender_path': 'lms.djangoapps.verify_student.models.SoftwareSecurePhotoVerification', + 'sender_path': 'lms.djangoapps.verify_student.models.VerificationAttempt', }, { 'receiver_func_name': 'proctoring_attempt_handler', diff --git a/edx_name_affirmation/handlers.py b/edx_name_affirmation/handlers.py index a276772..6fa918b 100644 --- a/edx_name_affirmation/handlers.py +++ b/edx_name_affirmation/handlers.py @@ -4,6 +4,13 @@ import logging +from openedx_events.learning.signals import ( + IDV_ATTEMPT_APPROVED, + IDV_ATTEMPT_CREATED, + IDV_ATTEMPT_DENIED, + IDV_ATTEMPT_PENDING +) + from django.contrib.auth import get_user_model from django.db.models.signals import post_save from django.dispatch.dispatcher import receiver @@ -35,56 +42,60 @@ def verified_name_approved(sender, instance, **kwargs): # pylint: disable=unuse ) -def idv_attempt_handler(attempt_id, user_id, status, photo_id_name, full_name, **kwargs): +@receiver(IDV_ATTEMPT_APPROVED) +@receiver(IDV_ATTEMPT_CREATED) +@receiver(IDV_ATTEMPT_DENIED) +@receiver(IDV_ATTEMPT_PENDING) +def handle_idv_event(sender, signal, **kwargs): # pylint: disable=unused-argument """ - Receiver for IDV attempt updates - - Args: - attempt_id(int): ID associated with the IDV attempt - user_id(int): ID associated with the IDV attempt's user - status(str): status in IDV language for the IDV attempt - photo_id_name(str): name to be used as verified name - full_name(str): user's pending name change or current profile name + Trigger update to verified names based on open edX IDV events. """ - trigger_status = VerifiedNameStatus.trigger_state_change_from_idv(status) - - # only trigger celery task if status is relevant to name affirmation - if trigger_status: - log.info('VerifiedName: idv_attempt_handler triggering Celery task for user %(user_id)s ' - 'with photo_id_name %(photo_id_name)s and status %(status)s', - { - 'user_id': user_id, - 'photo_id_name': photo_id_name, - 'status': status - } - ) - idv_update_verified_name_task.delay(attempt_id, user_id, trigger_status, photo_id_name, full_name) + event_data = kwargs.get('idv_attempt') + user = User.objects.get(id=event_data.user.id) + + # If the user has a pending name change, use that as the full name + try: + user_full_name = user.pending_name_change + except AttributeError: + user_full_name = event_data.user.pii.name + + status = None + if signal == IDV_ATTEMPT_APPROVED: + status = VerifiedNameStatus.APPROVED + elif signal == IDV_ATTEMPT_CREATED: + status = VerifiedNameStatus.PENDING + elif signal == IDV_ATTEMPT_PENDING: + status = VerifiedNameStatus.SUBMITTED + elif signal == IDV_ATTEMPT_DENIED: + status = VerifiedNameStatus.DENIED else: - log.info('VerifiedName: idv_attempt_handler will not trigger Celery task for user %(user_id)s ' - 'with photo_id_name %(photo_id_name)s because of status %(status)s', - { - 'user_id': user_id, - 'photo_id_name': photo_id_name, - 'status': status - } - ) + log.info(f'IDV_ATTEMPT {signal} signal not recognized') # driven by receiver decorator so should never happen + return + log.info(f'IDV_ATTEMPT {status} signal triggering Celery task for user {user.id} ' + f'with name {event_data.name}') + idv_update_verified_name_task.delay( + event_data.attempt_id, + user.id, + status, + event_data.name, + user_full_name, + ) -def idv_delete_handler(sender, instance, signal, **kwargs): # pylint: disable=unused-argument - """ - Receiver for IDV attempt deletions - Args: - attempt_id(int): ID associated with the IDV attempt +def platform_verification_delete_handler(sender, instance, signal, **kwargs): # pylint: disable=unused-argument + """ + Receiver for VerificationAttempt deletions """ - idv_attempt_id = instance.id + platform_verification_attempt_id = instance.id log.info( - 'VerifiedName: idv_delete_handler triggering Celery task for idv_attempt_id=%(idv_attempt_id)s', + 'VerifiedName: platform_verification_delete_handler triggering Celery task for ' + 'platform_verification_attempt_id=%(platform_verification_attempt_id)s', { - 'idv_attempt_id': idv_attempt_id, + 'platform_verification_attempt_id': platform_verification_attempt_id, } ) - delete_verified_name_task.delay(idv_attempt_id, None) + delete_verified_name_task.delay(platform_verification_attempt_id, None) def proctoring_attempt_handler( diff --git a/edx_name_affirmation/statuses.py b/edx_name_affirmation/statuses.py index 7358a51..914af34 100644 --- a/edx_name_affirmation/statuses.py +++ b/edx_name_affirmation/statuses.py @@ -32,22 +32,6 @@ class VerifiedNameStatus(str, Enum): APPROVED = "approved" DENIED = "denied" - @classmethod - def trigger_state_change_from_idv(cls, idv_status): - """ - Return the translated IDV status if it should trigger a state transition, otherwise return None - """ - # mapping from an idv status (key) to it's associated verified name status (value). We only want to - # include idv statuses that would cause a status transition for a verified name - idv_state_transition_mapping = { - 'created': cls.PENDING, - 'submitted': cls.SUBMITTED, - 'approved': cls.APPROVED, - 'denied': cls.DENIED - } - - return idv_state_transition_mapping.get(idv_status, None) - @classmethod def trigger_state_change_from_proctoring(cls, proctoring_status): """ diff --git a/edx_name_affirmation/tasks.py b/edx_name_affirmation/tasks.py index d76eb15..4475f8b 100644 --- a/edx_name_affirmation/tasks.py +++ b/edx_name_affirmation/tasks.py @@ -43,21 +43,24 @@ def idv_update_verified_name_task(self, attempt_id, user_id, name_affirmation_st # want to grab all verified names for the same user and name combination, because # some of those records may already be associated with a different IDV attempt. verified_names = VerifiedName.objects.filter( - (Q(verification_attempt_id=attempt_id) | Q(verification_attempt_id__isnull=True)) + (Q(platform_verification_attempt_id=attempt_id) | Q(platform_verification_attempt_id__isnull=True)) & Q(user__id=user_id) & Q(verified_name=photo_id_name) ).order_by('-created') + verified_names_updated = False if verified_names: # if there are VerifiedName objects, we want to update existing entries # for each attempt with no attempt id (either proctoring or idv), update attempt id updated_for_attempt_id = verified_names.filter( proctored_exam_attempt_id=None, - verification_attempt_id=None - ).update(verification_attempt_id=attempt_id) + verification_attempt_id=None, + platform_verification_attempt_id=None + ).update(platform_verification_attempt_id=attempt_id) if updated_for_attempt_id: + verified_names_updated = True log.info( - 'Updated VerifiedNames for user={user_id} to verification_attempt_id={attempt_id}'.format( + 'Updated VerifiedNames for user={user_id} to platform_verification_attempt_id={attempt_id}'.format( user_id=user_id, attempt_id=attempt_id, ) @@ -65,7 +68,8 @@ def idv_update_verified_name_task(self, attempt_id, user_id, name_affirmation_st # then for all matching attempt ids, update the status verified_name_qs = verified_names.filter( - verification_attempt_id=attempt_id, + platform_verification_attempt_id=attempt_id, + verification_attempt_id=None, proctored_exam_attempt_id=None ) @@ -73,28 +77,30 @@ def idv_update_verified_name_task(self, attempt_id, user_id, name_affirmation_st for verified_name_obj in verified_name_qs: verified_name_obj.status = name_affirmation_status verified_name_obj.save() + verified_names_updated = True log.info( - 'Updated VerifiedNames for user={user_id} with verification_attempt_id={attempt_id} to ' + 'Updated VerifiedNames for user={user_id} with platform_verification_attempt_id={attempt_id} to ' 'have status={status}'.format( user_id=user_id, attempt_id=attempt_id, status=name_affirmation_status ) ) - else: - # otherwise if there are no entries, we want to create one. + + # if there are no entries to update, we want to create one. + if not verified_names_updated: user = User.objects.get(id=user_id) verified_name = VerifiedName.objects.create( user=user, verified_name=photo_id_name, profile_name=full_name, - verification_attempt_id=attempt_id, + platform_verification_attempt_id=attempt_id, status=name_affirmation_status, ) log.error( 'Created VerifiedName for user={user_id} to have status={status} ' - 'and verification_attempt_id={attempt_id}, because no matching ' + 'and platform_verification_attempt_id={attempt_id}, because no matching ' 'attempt_id or verified_name were found.'.format( user_id=user_id, attempt_id=attempt_id, @@ -187,23 +193,23 @@ def proctoring_update_verified_name_task( bind=True, autoretry_for=(Exception,), default_retry_delay=DEFAULT_RETRY_SECONDS, max_retries=MAX_RETRIES, ) @set_code_owner_attribute -def delete_verified_name_task(self, idv_attempt_id, proctoring_attempt_id): +def delete_verified_name_task(self, platform_verification_attempt_id, proctoring_attempt_id): """ Celery task to delete a verified name based on an idv or proctoring attempt """ # this case shouldn't happen, but should log as an error in case - if (idv_attempt_id and proctoring_attempt_id) or (not idv_attempt_id and not proctoring_attempt_id): + if (proctoring_attempt_id, platform_verification_attempt_id).count(None) != 1: log.error( - 'A maximum of one attempt id should be provided for either a proctored exam attempt or IDV attempt.' + 'A maximum of one attempt id should be provided' ) return log_message = {'field_name': '', 'attempt_id': ''} - if idv_attempt_id: - verified_names = VerifiedName.objects.filter(verification_attempt_id=idv_attempt_id) - log_message['field_name'] = 'verification_attempt_id' - log_message['attempt_id'] = idv_attempt_id + if platform_verification_attempt_id: + verified_names = VerifiedName.objects.filter(platform_verification_attempt_id=platform_verification_attempt_id) + log_message['field_name'] = 'platform_verification_attempt_id' + log_message['attempt_id'] = platform_verification_attempt_id else: verified_names = VerifiedName.objects.filter(proctored_exam_attempt_id=proctoring_attempt_id) log_message['field_name'] = 'proctored_exam_attempt_id' @@ -212,10 +218,10 @@ def delete_verified_name_task(self, idv_attempt_id, proctoring_attempt_id): if verified_names: log.info( 'Deleting {num_names} VerifiedName(s) associated with {field_name}=' - '{verification_attempt_id}'.format( + '{platform_verification_attempt_id}'.format( num_names=len(verified_names), field_name=log_message['field_name'], - verification_attempt_id=log_message['attempt_id'], + platform_verification_attempt_id=log_message['attempt_id'], ) ) verified_names.delete() diff --git a/edx_name_affirmation/tests/test_handlers.py b/edx_name_affirmation/tests/test_handlers.py index 0c237cc..db60fc9 100644 --- a/edx_name_affirmation/tests/test_handlers.py +++ b/edx_name_affirmation/tests/test_handlers.py @@ -4,13 +4,20 @@ import ddt from mock import MagicMock, patch +from openedx_events.learning.data import UserData, UserPersonalData, VerificationAttemptData +from openedx_events.learning.signals import ( + IDV_ATTEMPT_APPROVED, + IDV_ATTEMPT_CREATED, + IDV_ATTEMPT_DENIED, + IDV_ATTEMPT_PENDING +) from django.contrib.auth import get_user_model from django.test import TestCase from edx_name_affirmation.handlers import ( - idv_attempt_handler, - idv_delete_handler, + handle_idv_event, + platform_verification_delete_handler, proctoring_attempt_handler, proctoring_delete_handler ) @@ -56,7 +63,7 @@ def test_post_save_verified_name_approved(self, status, should_send): user=self.user, verified_name='Jonathan Doe', profile_name=self.profile_name, - verification_attempt_id=self.idv_attempt_id + platform_verification_attempt_id=self.idv_attempt_id ) verified_name_obj.status = status verified_name_obj.save() @@ -73,34 +80,49 @@ class IDVSignalTests(SignalTestCase): """ Test for idv_attempt_handler """ + def _handle_idv_event(self, idv_signal, attempt_id): + """ Call IDV handler with a mock event """ + user_data = UserData( + id=self.user.id, + is_active=True, + pii=UserPersonalData( + username=self.user.username, + email=self.user.email, + name=self.profile_name, + ) + ) + event_data = VerificationAttemptData( + attempt_id=attempt_id, + user=user_data, + status='mock-platform-status', + name=self.verified_name, + ) + event_kwargs = { + 'idv_attempt': event_data + } + handle_idv_event(None, idv_signal, **event_kwargs) def test_idv_create_verified_name(self): """ Test that if no verified name exists for the name or attempt id, create one """ - idv_attempt_handler( - self.idv_attempt_id, - self.user.id, - 'created', - self.verified_name, - self.profile_name - ) + self._handle_idv_event(IDV_ATTEMPT_CREATED, self.idv_attempt_id) # make sure that verifiedname is created with relevant data - verified_name = VerifiedName.objects.get(verification_attempt_id=self.idv_attempt_id) + verified_name = VerifiedName.objects.get(platform_verification_attempt_id=self.idv_attempt_id) self.assertEqual(verified_name.status, VerifiedNameStatus.PENDING) - self.assertEqual(verified_name.verification_attempt_id, self.idv_attempt_id) + self.assertEqual(verified_name.platform_verification_attempt_id, self.idv_attempt_id) self.assertEqual(verified_name.verified_name, self.verified_name) self.assertEqual(verified_name.profile_name, self.profile_name) @ddt.data( - ('created', VerifiedNameStatus.PENDING), - ('submitted', VerifiedNameStatus.SUBMITTED), - ('approved', VerifiedNameStatus.APPROVED), - ('denied', VerifiedNameStatus.DENIED) + (IDV_ATTEMPT_CREATED, VerifiedNameStatus.PENDING), + (IDV_ATTEMPT_PENDING, VerifiedNameStatus.SUBMITTED), + (IDV_ATTEMPT_APPROVED, VerifiedNameStatus.APPROVED), + (IDV_ATTEMPT_DENIED, VerifiedNameStatus.DENIED) ) @ddt.unpack - def test_idv_update_multiple_verified_names(self, idv_status, expected_status): + def test_idv_update_multiple_verified_names(self, idv_signal, expected_status): """ If a VerifiedName(s) for a user and verified name exist, ensure that it is updated properly """ @@ -119,19 +141,13 @@ def test_idv_update_multiple_verified_names(self, idv_status, expected_status): user=self.user, verified_name=self.verified_name, profile_name=self.profile_name, - verification_attempt_id=self.idv_attempt_id + platform_verification_attempt_id=self.idv_attempt_id ) - idv_attempt_handler( - self.idv_attempt_id, - self.user.id, - idv_status, - self.verified_name, - self.profile_name - ) + self._handle_idv_event(idv_signal, self.idv_attempt_id) # check that the attempt id and status have been updated for all three VerifiedNames - self.assertEqual(len(VerifiedName.objects.filter(verification_attempt_id=self.idv_attempt_id)), 3) + self.assertEqual(len(VerifiedName.objects.filter(platform_verification_attempt_id=self.idv_attempt_id)), 3) self.assertEqual(len(VerifiedName.objects.filter(status=expected_status)), 3) def test_idv_create_with_existing_verified_names(self): @@ -144,23 +160,17 @@ def test_idv_create_with_existing_verified_names(self): user=self.user, verified_name=self.verified_name, profile_name=self.profile_name, - verification_attempt_id=previous_id, + platform_verification_attempt_id=previous_id, status='denied' ) # create an IDV attempt with the same user and names as above, but change the attempt ID to a unique value - idv_attempt_handler( - self.idv_attempt_id, - self.user.id, - 'submitted', - self.verified_name, - self.profile_name - ) + self._handle_idv_event(IDV_ATTEMPT_PENDING, self.idv_attempt_id) - verified_name = VerifiedName.objects.get(verification_attempt_id=self.idv_attempt_id) + verified_name = VerifiedName.objects.get(platform_verification_attempt_id=self.idv_attempt_id) self.assertEqual(verified_name.status, VerifiedNameStatus.SUBMITTED) - previous_name = VerifiedName.objects.get(verification_attempt_id=previous_id) + previous_name = VerifiedName.objects.get(platform_verification_attempt_id=previous_id) self.assertEqual(previous_name.status, VerifiedNameStatus.DENIED) def test_idv_does_not_update_verified_name_by_proctoring(self): @@ -181,27 +191,51 @@ def test_idv_does_not_update_verified_name_by_proctoring(self): profile_name=self.profile_name ) - idv_attempt_handler( - self.idv_attempt_id, - self.user.id, - 'submitted', - self.verified_name, - self.profile_name - ) + self._handle_idv_event(IDV_ATTEMPT_PENDING, self.idv_attempt_id) # check that the attempt id and status have only been updated for the record that does not have a proctored # exam attempt id - self.assertEqual(len(VerifiedName.objects.filter(verification_attempt_id=self.idv_attempt_id)), 1) + self.assertEqual(len(VerifiedName.objects.filter(platform_verification_attempt_id=self.idv_attempt_id)), 1) + self.assertEqual(len(VerifiedName.objects.filter(status=VerifiedNameStatus.SUBMITTED)), 1) + + def test_idv_does_not_update_old_verification_types(self): + """ + The verfication_attempt_id field is no longer supported by edx-platform. These records should no be + updated by idv events. + """ + VerifiedName.objects.create( + user=self.user, + verified_name=self.verified_name, + profile_name=self.profile_name, + verification_attempt_id=123, + status=VerifiedNameStatus.APPROVED, + ) + VerifiedName.objects.create( + user=self.user, + verified_name=self.verified_name, + profile_name=self.profile_name, + verification_attempt_id=456, + status=VerifiedNameStatus.SUBMITTED, + ) + + self._handle_idv_event(IDV_ATTEMPT_CREATED, self.idv_attempt_id) + self.assertEqual(len(VerifiedName.objects.filter( + status=VerifiedNameStatus.PENDING, + platform_verification_attempt_id=self.idv_attempt_id, + )), 1) + + # old records remain untouched self.assertEqual(len(VerifiedName.objects.filter(status=VerifiedNameStatus.SUBMITTED)), 1) + self.assertEqual(len(VerifiedName.objects.filter(status=VerifiedNameStatus.APPROVED)), 1) @ddt.data( - ('created', VerifiedNameStatus.PENDING), - ('submitted', VerifiedNameStatus.SUBMITTED), - ('approved', VerifiedNameStatus.APPROVED), - ('denied', VerifiedNameStatus.DENIED) + (IDV_ATTEMPT_CREATED, VerifiedNameStatus.PENDING), + (IDV_ATTEMPT_PENDING, VerifiedNameStatus.SUBMITTED), + (IDV_ATTEMPT_APPROVED, VerifiedNameStatus.APPROVED), + (IDV_ATTEMPT_DENIED, VerifiedNameStatus.DENIED) ) @ddt.unpack - def test_idv_update_one_verified_name(self, idv_status, expected_status): + def test_idv_update_one_verified_name(self, idv_signal, expected_status): """ If a VerifiedName(s) for a user and verified name exist, ensure that it is updated properly """ @@ -210,19 +244,13 @@ def test_idv_update_one_verified_name(self, idv_status, expected_status): user=self.user, verified_name=self.verified_name, profile_name=self.profile_name, - verification_attempt_id=self.idv_attempt_id + platform_verification_attempt_id=self.idv_attempt_id ) - idv_attempt_handler( - self.idv_attempt_id, - self.user.id, - idv_status, - self.verified_name, - self.profile_name - ) + self._handle_idv_event(idv_signal, self.idv_attempt_id) # check that the attempt id and status have been updated for all three VerifiedNames - self.assertEqual(len(VerifiedName.objects.filter(verification_attempt_id=self.idv_attempt_id)), 1) + self.assertEqual(len(VerifiedName.objects.filter(platform_verification_attempt_id=self.idv_attempt_id)), 1) self.assertEqual(len(VerifiedName.objects.filter(status=expected_status)), 1) # If the status is approved, ensure that the post_save signal is called @@ -231,25 +259,6 @@ def test_idv_update_one_verified_name(self, idv_status, expected_status): else: mock_signal.assert_not_called() - @ddt.data( - 'ready', - 'must_retry', - ) - @patch('edx_name_affirmation.tasks.idv_update_verified_name_task.delay') - def test_idv_non_trigger_status(self, status, mock_task): - """ - Test that a celery task is not triggered if a non-relevant status is received - """ - idv_attempt_handler( - self.idv_attempt_id, - self.user.id, - status, - self.verified_name, - self.profile_name - ) - - mock_task.assert_not_called() - @patch('edx_name_affirmation.tasks.delete_verified_name_task.delay') def test_idv_delete_handler(self, mock_task): """ @@ -257,7 +266,7 @@ def test_idv_delete_handler(self, mock_task): """ mock_idv_object = MagicMock() mock_idv_object.id = 'abcdef' - idv_delete_handler( + platform_verification_delete_handler( {}, mock_idv_object, '', diff --git a/edx_name_affirmation/tests/test_tasks.py b/edx_name_affirmation/tests/test_tasks.py index 58b4485..8c3aab7 100644 --- a/edx_name_affirmation/tests/test_tasks.py +++ b/edx_name_affirmation/tests/test_tasks.py @@ -63,7 +63,7 @@ def test_idv_delete(self): Assert that only relevant VerifiedNames are deleted for a given idv_attempt_id """ # associated test object with an idv attempt - self.verified_name_obj.verification_attempt_id = self.idv_attempt_id + self.verified_name_obj.platform_verification_attempt_id = self.idv_attempt_id self.verified_name_obj.save() other_attempt_id = 123456 @@ -73,7 +73,7 @@ def test_idv_delete(self): user=self.user, verified_name='Jonathan X Doe', profile_name='Jon D', - verification_attempt_id=self.idv_attempt_id + platform_verification_attempt_id=self.idv_attempt_id ).save() # create VerifiedName not associated with idv attempt @@ -81,7 +81,7 @@ def test_idv_delete(self): user=self.user, verified_name='Jonathan X Doe', profile_name='Jon D', - verification_attempt_id=other_attempt_id + platform_verification_attempt_id=other_attempt_id ) other_verified_name_obj.save() @@ -91,8 +91,8 @@ def test_idv_delete(self): ) # check that there is only VerifiedName object - self.assertEqual(len(VerifiedName.objects.filter(verification_attempt_id=self.idv_attempt_id)), 0) - self.assertEqual(len(VerifiedName.objects.filter(verification_attempt_id=other_attempt_id)), 1) + self.assertEqual(len(VerifiedName.objects.filter(platform_verification_attempt_id=self.idv_attempt_id)), 0) + self.assertEqual(len(VerifiedName.objects.filter(platform_verification_attempt_id=other_attempt_id)), 1) def test_proctoring_delete(self): """ diff --git a/requirements/base.in b/requirements/base.in index 1f7b965..0dd53f5 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -12,3 +12,5 @@ edx-celeryutils edx-django-utils>=3.12.0 edx-drf-extensions edx-toggles # Feature toggles management + +openedx-events diff --git a/requirements/base.txt b/requirements/base.txt index 86555cc..193167d 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -8,6 +8,8 @@ amqp==5.2.0 # via kombu asgiref==3.8.1 # via django +attrs==24.2.0 + # via openedx-events billiard==4.2.0 # via celery celery==5.4.0 @@ -57,6 +59,7 @@ django==4.2.16 # edx-drf-extensions # edx-toggles # jsonfield + # openedx-events django-config-models==2.7.0 # via -r requirements/base.in django-crum==0.7.9 @@ -92,6 +95,8 @@ drf-yasg==1.21.7 # via edx-api-doc-tools edx-api-doc-tools==1.8.0 # via -r requirements/base.in +edx-ccx-keys==1.3.0 + # via openedx-events edx-celeryutils==1.3.0 # via -r requirements/base.in edx-django-utils==5.15.0 @@ -100,12 +105,18 @@ edx-django-utils==5.15.0 # django-config-models # edx-drf-extensions # edx-toggles + # openedx-events edx-drf-extensions==10.4.0 # via -r requirements/base.in -edx-opaque-keys==2.11.0 - # via edx-drf-extensions +edx-opaque-keys[django]==2.11.0 + # via + # edx-ccx-keys + # edx-drf-extensions + # openedx-events edx-toggles==5.2.0 # via -r requirements/base.in +fastavro==1.9.7 + # via openedx-events idna==3.8 # via requests inflection==0.5.1 @@ -120,6 +131,8 @@ markupsafe==2.1.5 # via jinja2 newrelic==9.13.0 # via edx-django-utils +openedx-events==9.14.1 + # via -r requirements/base.in packaging==24.1 # via drf-yasg pbr==6.1.0 @@ -153,7 +166,9 @@ requests==2.32.3 semantic-version==2.10.0 # via edx-drf-extensions six==1.16.0 - # via python-dateutil + # via + # edx-ccx-keys + # python-dateutil sqlparse==0.5.1 # via django stevedore==5.3.0 diff --git a/requirements/dev.txt b/requirements/dev.txt index bea86a5..392992b 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -33,10 +33,6 @@ certifi==2024.8.30 # via # -r requirements/quality.txt # requests -cffi==1.17.1 - # via - # -r requirements/quality.txt - # cryptography chardet==5.2.0 # via # -r requirements/ci.txt @@ -69,10 +65,6 @@ colorama==0.4.6 # tox coverage==7.6.1 # via -r requirements/ci.txt -cryptography==43.0.1 - # via - # -r requirements/quality.txt - # secretstorage diff-cover==9.1.1 # via -r requirements/dev.in dill==0.3.8 @@ -127,11 +119,6 @@ jaraco-functools==4.0.2 # via # -r requirements/quality.txt # keyring -jeepney==0.8.0 - # via - # -r requirements/quality.txt - # keyring - # secretstorage jinja2==3.1.4 # via # -r requirements/quality.txt @@ -141,12 +128,8 @@ keyring==25.3.0 # via # -r requirements/quality.txt # twine -lxml[html-clean,html_clean]==5.3.0 - # via - # edx-i18n-tools - # lxml-html-clean -lxml-html-clean==0.2.2 - # via lxml +lxml[html_clean]==5.3.0 + # via edx-i18n-tools markdown-it-py==3.0.0 # via # -r requirements/quality.txt @@ -207,10 +190,6 @@ polib==1.2.0 # via edx-i18n-tools pycodestyle==2.12.1 # via -r requirements/quality.txt -pycparser==2.22 - # via - # -r requirements/quality.txt - # cffi pydantic==2.9.0 # via # -r requirements/quality.txt @@ -293,10 +272,6 @@ rstcheck-core==1.2.1 # via # -r requirements/quality.txt # rstcheck -secretstorage==3.3.3 - # via - # -r requirements/quality.txt - # keyring shellingham==1.5.4 # via # -r requirements/quality.txt diff --git a/requirements/doc.txt b/requirements/doc.txt index ab0eecb..a478942 100644 --- a/requirements/doc.txt +++ b/requirements/doc.txt @@ -10,6 +10,8 @@ amqp==5.2.0 # via kombu asgiref==3.8.1 # via django +attrs==24.2.0 + # via openedx-events babel==2.16.0 # via sphinx billiard==4.2.0 @@ -61,6 +63,7 @@ django==4.2.16 # edx-drf-extensions # edx-toggles # jsonfield + # openedx-events django-config-models==2.7.0 # via -r requirements/base.in django-crum==0.7.9 @@ -104,6 +107,8 @@ drf-yasg==1.21.7 # via edx-api-doc-tools edx-api-doc-tools==1.8.0 # via -r requirements/base.in +edx-ccx-keys==1.3.0 + # via openedx-events edx-celeryutils==1.3.0 # via -r requirements/base.in edx-django-utils==5.15.0 @@ -112,14 +117,20 @@ edx-django-utils==5.15.0 # django-config-models # edx-drf-extensions # edx-toggles + # openedx-events edx-drf-extensions==10.4.0 # via -r requirements/base.in -edx-opaque-keys==2.11.0 - # via edx-drf-extensions +edx-opaque-keys[django]==2.11.0 + # via + # edx-ccx-keys + # edx-drf-extensions + # openedx-events edx-sphinx-theme==3.1.0 # via -r requirements/doc.in edx-toggles==5.2.0 # via -r requirements/base.in +fastavro==1.9.7 + # via openedx-events idna==3.8 # via requests imagesize==1.4.1 @@ -140,6 +151,8 @@ newrelic==9.13.0 # via edx-django-utils nh3==0.2.18 # via readme-renderer +openedx-events==9.14.1 + # via -r requirements/base.in packaging==24.1 # via # drf-yasg @@ -189,6 +202,7 @@ semantic-version==2.10.0 # via edx-drf-extensions six==1.16.0 # via + # edx-ccx-keys # edx-sphinx-theme # pockets # python-dateutil diff --git a/requirements/quality.txt b/requirements/quality.txt index 932eade..a465ef4 100644 --- a/requirements/quality.txt +++ b/requirements/quality.txt @@ -16,8 +16,6 @@ backports-tarfile==1.2.0 # via jaraco-context certifi==2024.8.30 # via requests -cffi==1.17.1 - # via cryptography charset-normalizer==3.3.2 # via requests click==8.1.7 @@ -30,8 +28,6 @@ click-log==0.4.0 # via edx-lint code-annotations==1.8.0 # via edx-lint -cryptography==43.0.1 - # via secretstorage dill==0.3.8 # via pylint django==4.2.16 @@ -60,10 +56,6 @@ jaraco-context==6.0.1 # via keyring jaraco-functools==4.0.2 # via keyring -jeepney==0.8.0 - # via - # keyring - # secretstorage jinja2==3.1.4 # via code-annotations keyring==25.3.0 @@ -90,8 +82,6 @@ platformdirs==4.2.2 # via pylint pycodestyle==2.12.1 # via -r requirements/quality.in -pycparser==2.22 - # via cffi pydantic==2.9.0 # via rstcheck-core pydantic-core==2.23.2 @@ -138,8 +128,6 @@ rstcheck==6.2.4 # via -r requirements/quality.in rstcheck-core==1.2.1 # via rstcheck -secretstorage==3.3.3 - # via keyring shellingham==1.5.4 # via typer six==1.16.0 diff --git a/requirements/test.txt b/requirements/test.txt index 595e511..d14ef23 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -12,6 +12,10 @@ asgiref==3.8.1 # via # -r requirements/base.txt # django +attrs==24.2.0 + # via + # -r requirements/base.txt + # openedx-events billiard==4.2.0 # via # -r requirements/base.txt @@ -84,6 +88,7 @@ ddt==1.7.2 # edx-drf-extensions # edx-toggles # jsonfield + # openedx-events django-config-models==2.7.0 # via -r requirements/base.txt django-crum==0.7.9 @@ -127,6 +132,10 @@ drf-yasg==1.21.7 # edx-api-doc-tools edx-api-doc-tools==1.8.0 # via -r requirements/base.txt +edx-ccx-keys==1.3.0 + # via + # -r requirements/base.txt + # openedx-events edx-celeryutils==1.3.0 # via -r requirements/base.txt edx-django-utils==5.15.0 @@ -135,14 +144,21 @@ edx-django-utils==5.15.0 # django-config-models # edx-drf-extensions # edx-toggles + # openedx-events edx-drf-extensions==10.4.0 # via -r requirements/base.txt -edx-opaque-keys==2.11.0 +edx-opaque-keys[django]==2.11.0 # via # -r requirements/base.txt + # edx-ccx-keys # edx-drf-extensions + # openedx-events edx-toggles==5.2.0 # via -r requirements/base.txt +fastavro==1.9.7 + # via + # -r requirements/base.txt + # openedx-events idna==3.8 # via # -r requirements/base.txt @@ -175,6 +191,8 @@ newrelic==9.13.0 # via # -r requirements/base.txt # edx-django-utils +openedx-events==9.14.1 + # via -r requirements/base.txt packaging==24.1 # via # -r requirements/base.txt @@ -247,6 +265,7 @@ semantic-version==2.10.0 six==1.16.0 # via # -r requirements/base.txt + # edx-ccx-keys # python-dateutil sqlparse==0.5.1 # via