From 95301920082ce4575ba3a564fa5a7e2b2181dee0 Mon Sep 17 00:00:00 2001 From: Alie Langston Date: Thu, 5 Sep 2024 10:41:15 -0400 Subject: [PATCH] feat: add LMS retirement listener for VerificationAttempts --- lms/djangoapps/verify_student/models.py | 10 +++++ lms/djangoapps/verify_student/signals.py | 10 ++++- .../verify_student/tests/factories.py | 7 +++- .../verify_student/tests/test_signals.py | 40 +++++++++++++++++-- 4 files changed, 61 insertions(+), 6 deletions(-) diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py index 72b52d403849..57ef79e8b05b 100644 --- a/lms/djangoapps/verify_student/models.py +++ b/lms/djangoapps/verify_student/models.py @@ -1214,3 +1214,13 @@ class VerificationAttempt(TimeStampedModel): null=True, blank=True, ) + + @classmethod + def retire_user(cls, user_id): + """ + Retire user as part of GDPR pipeline + + :param user_id: int + """ + verification_attempts = cls.objects.filter(user_id=user_id) + verification_attempts.delete() diff --git a/lms/djangoapps/verify_student/signals.py b/lms/djangoapps/verify_student/signals.py index d929af68dd06..ae54deb74214 100644 --- a/lms/djangoapps/verify_student/signals.py +++ b/lms/djangoapps/verify_student/signals.py @@ -10,9 +10,9 @@ from xmodule.modulestore.django import SignalHandler, modulestore from common.djangoapps.student.models_api import get_name, get_pending_name_change -from openedx.core.djangoapps.user_api.accounts.signals import USER_RETIRE_LMS_CRITICAL +from openedx.core.djangoapps.user_api.accounts.signals import USER_RETIRE_LMS_CRITICAL, USER_RETIRE_LMS_MISC -from .models import SoftwareSecurePhotoVerification, VerificationDeadline +from .models import SoftwareSecurePhotoVerification, VerificationDeadline, VerificationAttempt log = logging.getLogger(__name__) @@ -75,3 +75,9 @@ def send_idv_update(sender, instance, **kwargs): # pylint: disable=unused-argum photo_id_name=instance.name, full_name=full_name ) + + +@receiver(USER_RETIRE_LMS_MISC) +def _listen_for_lms_retire_verification_attempts(sender, **kwargs): # pylint: disable=unused-argument + user = kwargs.get('user') + VerificationAttempt.retire_user(user.id) diff --git a/lms/djangoapps/verify_student/tests/factories.py b/lms/djangoapps/verify_student/tests/factories.py index da35e98cc53f..d7eaeaf30211 100644 --- a/lms/djangoapps/verify_student/tests/factories.py +++ b/lms/djangoapps/verify_student/tests/factories.py @@ -3,7 +3,7 @@ """ from factory.django import DjangoModelFactory -from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, SSOVerification +from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, SSOVerification, VerificationAttempt class SoftwareSecurePhotoVerificationFactory(DjangoModelFactory): @@ -19,3 +19,8 @@ class Meta: class SSOVerificationFactory(DjangoModelFactory): class Meta(): model = SSOVerification + + +class VerificationAttemptFactory(DjangoModelFactory): + class Meta: + model = VerificationAttempt diff --git a/lms/djangoapps/verify_student/tests/test_signals.py b/lms/djangoapps/verify_student/tests/test_signals.py index fb32edeccde0..8d607988d4b4 100644 --- a/lms/djangoapps/verify_student/tests/test_signals.py +++ b/lms/djangoapps/verify_student/tests/test_signals.py @@ -10,9 +10,20 @@ from common.djangoapps.student.models_api import do_name_change_request from common.djangoapps.student.tests.factories import UserFactory -from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline -from lms.djangoapps.verify_student.signals import _listen_for_course_publish, _listen_for_lms_retire -from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory +from lms.djangoapps.verify_student.models import ( + SoftwareSecurePhotoVerification, + VerificationDeadline, + VerificationAttempt +) +from lms.djangoapps.verify_student.signals import ( + _listen_for_course_publish, + _listen_for_lms_retire, + _listen_for_lms_retire_verification_attempts +) +from lms.djangoapps.verify_student.tests.factories import ( + SoftwareSecurePhotoVerificationFactory, + VerificationAttemptFactory +) from openedx.core.djangoapps.user_api.accounts.tests.retirement_helpers import fake_completed_retirement from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order @@ -174,3 +185,26 @@ def test_post_save_signal_pending_name(self, mock_signal): photo_id_name=attempt.name, full_name=pending_name_change.new_name ) + + +class RetirementSignalVerificationAttemptsTest(ModuleStoreTestCase): + """ + Tests for the LMS User Retirement signal for Verification Attempts + """ + + def setUp(self): + super().setUp() + self.user = UserFactory.create() + self.other_user = UserFactory.create() + VerificationAttemptFactory.create(user=self.user) + VerificationAttemptFactory.create(user=self.other_user) + + def test_retirement_signal(self): + _listen_for_lms_retire_verification_attempts(sender=self.__class__, user=self.user) + self.assertEqual(len(VerificationAttempt.objects.filter(user=self.user)), 0) + self.assertEqual(len(VerificationAttempt.objects.filter(user=self.other_user)), 1) + + def test_retirement_signal_no_attempts(self): + no_attempt_user = UserFactory.create() + _listen_for_lms_retire_verification_attempts(sender=self.__class__, user=no_attempt_user) + self.assertEqual(len(VerificationAttempt.objects.all()), 2)