chore: handle save-for-later PII (#29696)
Added PII annotations for email fields in save-for-later models and also added user retirement signal receiver to remove related objects.
This commit is contained in:
@@ -7,14 +7,30 @@ from model_utils.models import TimeStampedModel
|
||||
from django.db import models
|
||||
from opaque_keys.edx.django.models import CourseKeyField
|
||||
|
||||
from openedx.core.djangolib.model_mixins import DeletableByUserValue
|
||||
|
||||
class SavedCourse(TimeStampedModel):
|
||||
|
||||
class SavedCourse(DeletableByUserValue, TimeStampedModel):
|
||||
"""
|
||||
Tracks save course by email.
|
||||
|
||||
.. pii: Stores email address of the User.
|
||||
.. pii_types: email_address
|
||||
.. pii_retirement: local_api
|
||||
"""
|
||||
user_id = models.IntegerField(null=True, blank=True)
|
||||
email = models.EmailField(db_index=True)
|
||||
course_id = CourseKeyField(max_length=255, db_index=True)
|
||||
|
||||
|
||||
class SavedProgram(TimeStampedModel):
|
||||
class SavedProgram(DeletableByUserValue, TimeStampedModel):
|
||||
"""
|
||||
Tracks save program by email.
|
||||
|
||||
.. pii: Stores email address of the User.
|
||||
.. pii_types: email_address
|
||||
.. pii_retirement: local_api
|
||||
"""
|
||||
user_id = models.IntegerField(null=True, blank=True)
|
||||
email = models.EmailField(db_index=True)
|
||||
program_uuid = models.UUIDField()
|
||||
|
||||
14
lms/djangoapps/save_for_later/signals.py
Normal file
14
lms/djangoapps/save_for_later/signals.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
Signal handler for save for later
|
||||
"""
|
||||
from django.dispatch.dispatcher import receiver
|
||||
|
||||
from openedx.core.djangoapps.user_api.accounts.signals import USER_RETIRE_LMS_CRITICAL
|
||||
from .models import SavedCourse, SavedProgram
|
||||
|
||||
|
||||
@receiver(USER_RETIRE_LMS_CRITICAL)
|
||||
def _listen_for_lms_retire(sender, **kwargs): # pylint: disable=unused-argument
|
||||
user = kwargs.get('user')
|
||||
SavedCourse.delete_by_user_value(user.id, field='user_id')
|
||||
SavedProgram.delete_by_user_value(user.id, field='user_id')
|
||||
0
lms/djangoapps/save_for_later/tests/__init__.py
Normal file
0
lms/djangoapps/save_for_later/tests/__init__.py
Normal file
42
lms/djangoapps/save_for_later/tests/test_signals.py
Normal file
42
lms/djangoapps/save_for_later/tests/test_signals.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
Unit tests for the signals
|
||||
"""
|
||||
from uuid import uuid4
|
||||
from django.test import TestCase
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from ..models import SavedCourse, SavedProgram
|
||||
from ..signals import _listen_for_lms_retire
|
||||
|
||||
|
||||
class RetirementSignalTest(TestCase):
|
||||
"""
|
||||
Tests for the user retirement signal
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = UserFactory()
|
||||
self.email = self.user.email
|
||||
|
||||
def _create_objects(self):
|
||||
"""
|
||||
Create test objects.
|
||||
"""
|
||||
SavedCourse.objects.create(user_id=self.user.id, email=self.email, course_id='course-v1:TestX+TestX101+1T2022')
|
||||
SavedProgram.objects.create(user_id=self.user.id, email=self.email, program_uuid=uuid4())
|
||||
|
||||
assert SavedCourse.objects.filter(email=self.email).exists()
|
||||
assert SavedProgram.objects.filter(email=self.email).exists()
|
||||
|
||||
def test_retire_success(self):
|
||||
self._create_objects()
|
||||
_listen_for_lms_retire(sender=self.__class__, user=self.user, email=self.email)
|
||||
|
||||
assert not SavedCourse.objects.filter(email=self.email).exists()
|
||||
assert not SavedProgram.objects.filter(email=self.email).exists()
|
||||
|
||||
def test_retire_success_no_entries(self):
|
||||
assert not SavedCourse.objects.filter(email=self.email).exists()
|
||||
assert not SavedProgram.objects.filter(email=self.email).exists()
|
||||
_listen_for_lms_retire(sender=self.__class__, user=self.user, email=self.email)
|
||||
Reference in New Issue
Block a user