From 2579a6d6aca7f65e8dbdda58066f294790808986 Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Thu, 28 May 2026 10:16:53 +0000 Subject: [PATCH 1/5] fix: ensure PII is cleared from historical certificate records during retirement --- lms/djangoapps/certificates/api.py | 4 ++++ .../purge_pii_from_generatedcertificates.py | 22 ++++++++++++------- ...st_purge_pii_from_generatedcertificates.py | 15 +++++++++++++ lms/djangoapps/certificates/models.py | 4 ++-- lms/djangoapps/certificates/tests/test_api.py | 21 ++++++++++++++++++ 5 files changed, 56 insertions(+), 10 deletions(-) diff --git a/lms/djangoapps/certificates/api.py b/lms/djangoapps/certificates/api.py index aed7d1559d91..ef78d9cf5293 100644 --- a/lms/djangoapps/certificates/api.py +++ b/lms/djangoapps/certificates/api.py @@ -977,6 +977,9 @@ def clear_pii_from_certificate_records_for_user(user): model's custom `save()` function, nor fire any Django signals (which is desired at the time of writing). There is nothing to update in our external systems by this update. + Both the live `certificates_generatedcertificate` table and the django-simple-history audit table + `certificates_historicalgeneratedcertificate` are updated so that no snapshot retains the learner's name. + Args: user (User): The User instance of the learner actively being retired. @@ -984,6 +987,7 @@ def clear_pii_from_certificate_records_for_user(user): None """ GeneratedCertificate.objects.filter(user=user).update(name="") + GeneratedCertificate.history.filter(user=user).update(name="") def get_cert_history_for_course_id(course_id): diff --git a/lms/djangoapps/certificates/management/commands/purge_pii_from_generatedcertificates.py b/lms/djangoapps/certificates/management/commands/purge_pii_from_generatedcertificates.py index c478fd4fe0c5..5ee857c7adb7 100644 --- a/lms/djangoapps/certificates/management/commands/purge_pii_from_generatedcertificates.py +++ b/lms/djangoapps/certificates/management/commands/purge_pii_from_generatedcertificates.py @@ -1,6 +1,7 @@ """ A management command, designed to be run once by Open edX Operators, to obfuscate learner PII from the -`Certificates_GeneratedCertificate` table that should have been purged during learner retirement. +`certificates_generatedcertificate` and `certificates_historicalgeneratedcertificate` tables that should have been +purged during learner retirement. A fix has been included in the retirement pipeline to properly purge this data during learner retirement. This can be used to purge PII from accounts that have already been retired. @@ -20,11 +21,12 @@ class Command(BaseCommand): """ - This management command performs a bulk update on `GeneratedCertificate` instances. This means that it will not - invoke the custom save() function defined as part of the `GeneratedCertificate` model, and thus will not emit any - Django signals throughout the system after the update occurs. This is desired behavior. We are using this - management command to purge remnant PII, retired elsewhere in the system, that should have already been removed - from the Certificates tables. We don't need updates to propogate to external systems (like the Credentials IDA). + This management command performs bulk updates on `GeneratedCertificate` instances and their django-simple-history + audit rows in `certificates_historicalgeneratedcertificate`. This means that it will not invoke the custom save() + function defined as part of the `GeneratedCertificate` model, and thus will not emit any Django signals throughout + the system after the update occurs. This is desired behavior. We are using this management command to purge remnant + PII, retired elsewhere in the system, that should have already been removed from the Certificates tables. We don't + need updates to propogate to external systems (like the Credentials IDA). This management command functions by requesting a list of learners' user_ids whom have completed their journey through the retirement pipeline. The `get_retired_user_ids` utility function is responsible for filtering out any @@ -41,8 +43,8 @@ class Command(BaseCommand): """ help = """ - Purges learners' full names from the `Certificates_GeneratedCertificate` table if their account has been - successfully retired. + Purges learners' full names from the `certificates_generatedcertificate` and + `certificates_historicalgeneratedcertificate` tables if their account has been successfully retired. """ def add_arguments(self, parser): @@ -59,6 +61,10 @@ def handle(self, *args, **options): f"Purging `name` from the certificate records of the following users: {retired_user_ids}" ) GeneratedCertificate.objects.filter(user_id__in=retired_user_ids).update(name="") + log.warning( + f"Purging `name` from the historical certificate records of the following users: {retired_user_ids}" + ) + GeneratedCertificate.history.filter(user_id__in=retired_user_ids).update(name="") else: log.info( "DRY RUN: running this management command would purge `name` data from the following users: " diff --git a/lms/djangoapps/certificates/management/commands/tests/test_purge_pii_from_generatedcertificates.py b/lms/djangoapps/certificates/management/commands/tests/test_purge_pii_from_generatedcertificates.py index 50855ccaa804..2e2b6df6dd91 100644 --- a/lms/djangoapps/certificates/management/commands/tests/test_purge_pii_from_generatedcertificates.py +++ b/lms/djangoapps/certificates/management/commands/tests/test_purge_pii_from_generatedcertificates.py @@ -89,6 +89,16 @@ def test_management_command(self): cert_for_retired_user = GeneratedCertificate.objects.get(user_id=self.user_retired) assert cert_for_retired_user.name == "" + active_history_names = list( + GeneratedCertificate.history.filter(user=self.user_active).values_list("name", flat=True) + ) + assert all(n == self.user_active_name for n in active_history_names) + + retired_history_names = list( + GeneratedCertificate.history.filter(user=self.user_retired).values_list("name", flat=True) + ) + assert all(n == "" for n in retired_history_names) + def test_management_command_dry_run(self): """ Verify that the management command does not purge any data when invoked with the `--dry-run` flag @@ -111,4 +121,9 @@ def test_management_command_dry_run(self): cert_for_retired_user = GeneratedCertificate.objects.get(user_id=self.user_retired) assert cert_for_retired_user.name == self.user_retired_name + retired_history_names = list( + GeneratedCertificate.history.filter(user=self.user_retired).values_list("name", flat=True) + ) + assert all(n == self.user_retired_name for n in retired_history_names) + assert logger.records[0].msg == expected_log_msg diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index 755a9c784c53..166985bf6451 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -170,9 +170,9 @@ class GeneratedCertificate(models.Model): # noqa: DJ008 """ Base model for generated course certificates - .. pii: PII can exist in the generated certificate linked to in this model. Certificate data is currently retained. + .. pii: PII can exist in the generated certificate linked to in this model. .. pii_types: name, username - .. pii_retirement: retained + .. pii_retirement: local_api course_id - Course run key created_date - Date and time the certificate was created diff --git a/lms/djangoapps/certificates/tests/test_api.py b/lms/djangoapps/certificates/tests/test_api.py index c2673e36703e..911a4f834997 100644 --- a/lms/djangoapps/certificates/tests/test_api.py +++ b/lms/djangoapps/certificates/tests/test_api.py @@ -1277,6 +1277,27 @@ def test_clear_pii_from_certificate_records(self): assert cert_course1.name == "" assert cert_course2.name == "" + def test_clear_pii_from_certificate_records_clears_history_table(self): + """ + Verify that `clear_pii_from_certificate_records_for_user` also blanks `name` in the + django-simple-history audit table (`certificates_historicalgeneratedcertificate`). + """ + history_names = list( + GeneratedCertificate.history.filter(user=self.user).values_list("name", flat=True) + ) + assert all(n == self.user_full_name for n in history_names), ( + "Expected all history rows to contain the full name before retirement." + ) + + clear_pii_from_certificate_records_for_user(self.user) + + history_names_after = list( + GeneratedCertificate.history.filter(user=self.user).values_list("name", flat=True) + ) + assert all(n == "" for n in history_names_after), ( + "Expected all history rows to have name blanked after retirement." + ) + class GetCourseIdsForUsernameTests(TestCase): """ From 3f2037ab9e021b46ef9dc69fb2eb07c475f0b3f1 Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Fri, 29 May 2026 07:24:45 +0000 Subject: [PATCH 2/5] feat: add waffle flag to enable historical PII retirement in certificate records --- lms/djangoapps/certificates/api.py | 10 ++++++---- lms/djangoapps/certificates/config.py | 13 ++++++++++++- .../purge_pii_from_generatedcertificates.py | 10 ++++++---- ...test_purge_pii_from_generatedcertificates.py | 10 +++++++--- lms/djangoapps/certificates/tests/test_api.py | 17 +++++++++++------ 5 files changed, 42 insertions(+), 18 deletions(-) diff --git a/lms/djangoapps/certificates/api.py b/lms/djangoapps/certificates/api.py index ef78d9cf5293..9d5f87d15c9d 100644 --- a/lms/djangoapps/certificates/api.py +++ b/lms/djangoapps/certificates/api.py @@ -23,7 +23,7 @@ from common.djangoapps.student.api import is_user_enrolled_in_course from common.djangoapps.student.models import CourseEnrollment from lms.djangoapps.branding import api as branding_api -from lms.djangoapps.certificates.config import AUTO_CERTIFICATE_GENERATION as _AUTO_CERTIFICATE_GENERATION +from lms.djangoapps.certificates.config import AUTO_CERTIFICATE_GENERATION as _AUTO_CERTIFICATE_GENERATION, ENABLE_HISTORICAL_PII_RETIREMENT from lms.djangoapps.certificates.data import CertificateStatuses, GeneratedCertificateData from lms.djangoapps.certificates.generation_handler import generate_certificate_task as _generate_certificate_task from lms.djangoapps.certificates.generation_handler import is_on_certificate_allowlist as _is_on_certificate_allowlist @@ -977,8 +977,9 @@ def clear_pii_from_certificate_records_for_user(user): model's custom `save()` function, nor fire any Django signals (which is desired at the time of writing). There is nothing to update in our external systems by this update. - Both the live `certificates_generatedcertificate` table and the django-simple-history audit table - `certificates_historicalgeneratedcertificate` are updated so that no snapshot retains the learner's name. + When the ``certificates.enable_historical_pii_retirement`` waffle flag is enabled, the django-simple-history audit + table ``certificates_historicalgeneratedcertificate`` is also updated so that no snapshot retains the learner's + name. Args: user (User): The User instance of the learner actively being retired. @@ -987,7 +988,8 @@ def clear_pii_from_certificate_records_for_user(user): None """ GeneratedCertificate.objects.filter(user=user).update(name="") - GeneratedCertificate.history.filter(user=user).update(name="") + if ENABLE_HISTORICAL_PII_RETIREMENT.is_enabled(): + GeneratedCertificate.history.filter(user=user).update(name="") def get_cert_history_for_course_id(course_id): diff --git a/lms/djangoapps/certificates/config.py b/lms/djangoapps/certificates/config.py index 1a5fd387581b..9464dad7c063 100644 --- a/lms/djangoapps/certificates/config.py +++ b/lms/djangoapps/certificates/config.py @@ -2,7 +2,7 @@ This module contains various configuration settings via waffle switches for the Certificates app. """ -from edx_toggles.toggles import WaffleSwitch +from edx_toggles.toggles import WaffleFlag, WaffleSwitch # Namespace WAFFLE_NAMESPACE = 'certificates' @@ -14,3 +14,14 @@ # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2017-09-14 AUTO_CERTIFICATE_GENERATION = WaffleSwitch(f"{WAFFLE_NAMESPACE}.auto_certificate_generation", __name__) + +# .. toggle_name: certificates.enable_historical_pii_retirement +# .. toggle_implementation: WaffleFlag +# .. toggle_default: False +# .. toggle_description: Clears the `name` field in the django-simple-history audit table for +# retiring users' certificate records. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2026-05-29 +ENABLE_HISTORICAL_PII_RETIREMENT = WaffleFlag( + f"{WAFFLE_NAMESPACE}.enable_historical_pii_retirement", __name__ +) diff --git a/lms/djangoapps/certificates/management/commands/purge_pii_from_generatedcertificates.py b/lms/djangoapps/certificates/management/commands/purge_pii_from_generatedcertificates.py index 5ee857c7adb7..8b01418b8528 100644 --- a/lms/djangoapps/certificates/management/commands/purge_pii_from_generatedcertificates.py +++ b/lms/djangoapps/certificates/management/commands/purge_pii_from_generatedcertificates.py @@ -12,6 +12,7 @@ from django.contrib.auth import get_user_model from django.core.management.base import BaseCommand +from lms.djangoapps.certificates.config import ENABLE_HISTORICAL_PII_RETIREMENT from lms.djangoapps.certificates.models import GeneratedCertificate from openedx.core.djangoapps.user_api.api import get_retired_user_ids @@ -61,10 +62,11 @@ def handle(self, *args, **options): f"Purging `name` from the certificate records of the following users: {retired_user_ids}" ) GeneratedCertificate.objects.filter(user_id__in=retired_user_ids).update(name="") - log.warning( - f"Purging `name` from the historical certificate records of the following users: {retired_user_ids}" - ) - GeneratedCertificate.history.filter(user_id__in=retired_user_ids).update(name="") + if ENABLE_HISTORICAL_PII_RETIREMENT.is_enabled(): + log.warning( + f"Purging `name` from the historical certificate records of the following users: {retired_user_ids}" + ) + GeneratedCertificate.history.filter(user_id__in=retired_user_ids).update(name="") else: log.info( "DRY RUN: running this management command would purge `name` data from the following users: " diff --git a/lms/djangoapps/certificates/management/commands/tests/test_purge_pii_from_generatedcertificates.py b/lms/djangoapps/certificates/management/commands/tests/test_purge_pii_from_generatedcertificates.py index 2e2b6df6dd91..47a92cdd3bf0 100644 --- a/lms/djangoapps/certificates/management/commands/tests/test_purge_pii_from_generatedcertificates.py +++ b/lms/djangoapps/certificates/management/commands/tests/test_purge_pii_from_generatedcertificates.py @@ -4,9 +4,11 @@ from django.core.management import call_command +from edx_toggles.toggles.testutils import override_waffle_flag from testfixtures import LogCapture from common.djangoapps.student.tests.factories import UserFactory +from lms.djangoapps.certificates.config import ENABLE_HISTORICAL_PII_RETIREMENT from lms.djangoapps.certificates.data import CertificateStatuses from lms.djangoapps.certificates.models import GeneratedCertificate from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory @@ -82,7 +84,8 @@ def test_management_command(self): cert_for_retired_user = GeneratedCertificate.objects.get(user_id=self.user_retired) assert cert_for_retired_user.name == self.user_retired_name - call_command("purge_pii_from_generatedcertificates") + with override_waffle_flag(ENABLE_HISTORICAL_PII_RETIREMENT, active=True): + call_command("purge_pii_from_generatedcertificates") cert_for_active_user = GeneratedCertificate.objects.get(user_id=self.user_active) assert cert_for_active_user.name == self.user_active_name @@ -113,8 +116,9 @@ def test_management_command_dry_run(self): cert_for_retired_user = GeneratedCertificate.objects.get(user_id=self.user_retired) assert cert_for_retired_user.name == self.user_retired_name - with LogCapture() as logger: - call_command("purge_pii_from_generatedcertificates", "--dry-run") + with override_waffle_flag(ENABLE_HISTORICAL_PII_RETIREMENT, active=True): + with LogCapture() as logger: + call_command("purge_pii_from_generatedcertificates", "--dry-run") cert_for_active_user = GeneratedCertificate.objects.get(user_id=self.user_active) assert cert_for_active_user.name == self.user_active_name diff --git a/lms/djangoapps/certificates/tests/test_api.py b/lms/djangoapps/certificates/tests/test_api.py index 911a4f834997..4d87783fae6b 100644 --- a/lms/djangoapps/certificates/tests/test_api.py +++ b/lms/djangoapps/certificates/tests/test_api.py @@ -14,7 +14,7 @@ from django.test.utils import override_settings from django.urls import reverse from django.utils import timezone -from edx_toggles.toggles.testutils import override_waffle_switch +from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch from freezegun import freeze_time from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import CourseLocator @@ -54,7 +54,7 @@ remove_allowlist_entry, set_cert_generation_enabled, ) -from lms.djangoapps.certificates.config import AUTO_CERTIFICATE_GENERATION +from lms.djangoapps.certificates.config import AUTO_CERTIFICATE_GENERATION, ENABLE_HISTORICAL_PII_RETIREMENT from lms.djangoapps.certificates.models import ( CertificateGenerationConfiguration, CertificateStatuses, @@ -1279,17 +1279,22 @@ def test_clear_pii_from_certificate_records(self): def test_clear_pii_from_certificate_records_clears_history_table(self): """ - Verify that `clear_pii_from_certificate_records_for_user` also blanks `name` in the - django-simple-history audit table (`certificates_historicalgeneratedcertificate`). + Verify that `clear_pii_from_certificate_records_for_user` blanks `name` in the + django-simple-history audit table only when the ``certificates.enable_historical_pii_retirement`` + waffle flag is enabled, and leaves it untouched when the flag is disabled. """ + with override_waffle_flag(ENABLE_HISTORICAL_PII_RETIREMENT, active=False): + clear_pii_from_certificate_records_for_user(self.user) + history_names = list( GeneratedCertificate.history.filter(user=self.user).values_list("name", flat=True) ) assert all(n == self.user_full_name for n in history_names), ( - "Expected all history rows to contain the full name before retirement." + "History rows should be untouched when the waffle flag is disabled." ) - clear_pii_from_certificate_records_for_user(self.user) + with override_waffle_flag(ENABLE_HISTORICAL_PII_RETIREMENT, active=True): + clear_pii_from_certificate_records_for_user(self.user) history_names_after = list( GeneratedCertificate.history.filter(user=self.user).values_list("name", flat=True) From 672b2f0f57ff362558ac7581d4ad8ad0f8eb9d03 Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Fri, 29 May 2026 08:01:28 +0000 Subject: [PATCH 3/5] fix: separate import statements for certificate generation configuration --- lms/djangoapps/certificates/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/certificates/api.py b/lms/djangoapps/certificates/api.py index 9d5f87d15c9d..69da143c6ead 100644 --- a/lms/djangoapps/certificates/api.py +++ b/lms/djangoapps/certificates/api.py @@ -23,7 +23,8 @@ from common.djangoapps.student.api import is_user_enrolled_in_course from common.djangoapps.student.models import CourseEnrollment from lms.djangoapps.branding import api as branding_api -from lms.djangoapps.certificates.config import AUTO_CERTIFICATE_GENERATION as _AUTO_CERTIFICATE_GENERATION, ENABLE_HISTORICAL_PII_RETIREMENT +from lms.djangoapps.certificates.config import AUTO_CERTIFICATE_GENERATION as _AUTO_CERTIFICATE_GENERATION +from lms.djangoapps.certificates.config import ENABLE_HISTORICAL_PII_RETIREMENT from lms.djangoapps.certificates.data import CertificateStatuses, GeneratedCertificateData from lms.djangoapps.certificates.generation_handler import generate_certificate_task as _generate_certificate_task from lms.djangoapps.certificates.generation_handler import is_on_certificate_allowlist as _is_on_certificate_allowlist From a29f936c07e9c748765d9200f07e5d416934f69a Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Fri, 29 May 2026 13:06:43 +0000 Subject: [PATCH 4/5] fix: rename waffle flag for historical PII retirement to reflect redaction functionality --- lms/djangoapps/certificates/api.py | 6 +++--- lms/djangoapps/certificates/config.py | 6 +++--- .../commands/purge_pii_from_generatedcertificates.py | 4 ++-- .../tests/test_purge_pii_from_generatedcertificates.py | 6 +++--- lms/djangoapps/certificates/tests/test_api.py | 8 ++++---- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lms/djangoapps/certificates/api.py b/lms/djangoapps/certificates/api.py index 69da143c6ead..24e810fd8ed7 100644 --- a/lms/djangoapps/certificates/api.py +++ b/lms/djangoapps/certificates/api.py @@ -24,7 +24,7 @@ from common.djangoapps.student.models import CourseEnrollment from lms.djangoapps.branding import api as branding_api from lms.djangoapps.certificates.config import AUTO_CERTIFICATE_GENERATION as _AUTO_CERTIFICATE_GENERATION -from lms.djangoapps.certificates.config import ENABLE_HISTORICAL_PII_RETIREMENT +from lms.djangoapps.certificates.config import ENABLE_REDACT_HISTORICAL_PII_RETIREMENT from lms.djangoapps.certificates.data import CertificateStatuses, GeneratedCertificateData from lms.djangoapps.certificates.generation_handler import generate_certificate_task as _generate_certificate_task from lms.djangoapps.certificates.generation_handler import is_on_certificate_allowlist as _is_on_certificate_allowlist @@ -978,7 +978,7 @@ def clear_pii_from_certificate_records_for_user(user): model's custom `save()` function, nor fire any Django signals (which is desired at the time of writing). There is nothing to update in our external systems by this update. - When the ``certificates.enable_historical_pii_retirement`` waffle flag is enabled, the django-simple-history audit + When the ``certificates.enable_redact_historical_pii_retirement`` waffle flag is enabled, the django-simple-history audit table ``certificates_historicalgeneratedcertificate`` is also updated so that no snapshot retains the learner's name. @@ -989,7 +989,7 @@ def clear_pii_from_certificate_records_for_user(user): None """ GeneratedCertificate.objects.filter(user=user).update(name="") - if ENABLE_HISTORICAL_PII_RETIREMENT.is_enabled(): + if ENABLE_REDACT_HISTORICAL_PII_RETIREMENT.is_enabled(): GeneratedCertificate.history.filter(user=user).update(name="") diff --git a/lms/djangoapps/certificates/config.py b/lms/djangoapps/certificates/config.py index 9464dad7c063..217cbecb1cec 100644 --- a/lms/djangoapps/certificates/config.py +++ b/lms/djangoapps/certificates/config.py @@ -15,13 +15,13 @@ # .. toggle_creation_date: 2017-09-14 AUTO_CERTIFICATE_GENERATION = WaffleSwitch(f"{WAFFLE_NAMESPACE}.auto_certificate_generation", __name__) -# .. toggle_name: certificates.enable_historical_pii_retirement +# .. toggle_name: certificates.enable_redact_historical_pii_retirement # .. toggle_implementation: WaffleFlag # .. toggle_default: False # .. toggle_description: Clears the `name` field in the django-simple-history audit table for # retiring users' certificate records. # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2026-05-29 -ENABLE_HISTORICAL_PII_RETIREMENT = WaffleFlag( - f"{WAFFLE_NAMESPACE}.enable_historical_pii_retirement", __name__ +ENABLE_REDACT_HISTORICAL_PII_RETIREMENT = WaffleFlag( + f"{WAFFLE_NAMESPACE}.enable_redact_historical_pii_retirement", __name__ ) diff --git a/lms/djangoapps/certificates/management/commands/purge_pii_from_generatedcertificates.py b/lms/djangoapps/certificates/management/commands/purge_pii_from_generatedcertificates.py index 8b01418b8528..bfd8274c9ed7 100644 --- a/lms/djangoapps/certificates/management/commands/purge_pii_from_generatedcertificates.py +++ b/lms/djangoapps/certificates/management/commands/purge_pii_from_generatedcertificates.py @@ -12,7 +12,7 @@ from django.contrib.auth import get_user_model from django.core.management.base import BaseCommand -from lms.djangoapps.certificates.config import ENABLE_HISTORICAL_PII_RETIREMENT +from lms.djangoapps.certificates.config import ENABLE_REDACT_HISTORICAL_PII_RETIREMENT from lms.djangoapps.certificates.models import GeneratedCertificate from openedx.core.djangoapps.user_api.api import get_retired_user_ids @@ -62,7 +62,7 @@ def handle(self, *args, **options): f"Purging `name` from the certificate records of the following users: {retired_user_ids}" ) GeneratedCertificate.objects.filter(user_id__in=retired_user_ids).update(name="") - if ENABLE_HISTORICAL_PII_RETIREMENT.is_enabled(): + if ENABLE_REDACT_HISTORICAL_PII_RETIREMENT.is_enabled(): log.warning( f"Purging `name` from the historical certificate records of the following users: {retired_user_ids}" ) diff --git a/lms/djangoapps/certificates/management/commands/tests/test_purge_pii_from_generatedcertificates.py b/lms/djangoapps/certificates/management/commands/tests/test_purge_pii_from_generatedcertificates.py index 47a92cdd3bf0..2cc52f0ff32e 100644 --- a/lms/djangoapps/certificates/management/commands/tests/test_purge_pii_from_generatedcertificates.py +++ b/lms/djangoapps/certificates/management/commands/tests/test_purge_pii_from_generatedcertificates.py @@ -8,7 +8,7 @@ from testfixtures import LogCapture from common.djangoapps.student.tests.factories import UserFactory -from lms.djangoapps.certificates.config import ENABLE_HISTORICAL_PII_RETIREMENT +from lms.djangoapps.certificates.config import ENABLE_REDACT_HISTORICAL_PII_RETIREMENT from lms.djangoapps.certificates.data import CertificateStatuses from lms.djangoapps.certificates.models import GeneratedCertificate from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory @@ -84,7 +84,7 @@ def test_management_command(self): cert_for_retired_user = GeneratedCertificate.objects.get(user_id=self.user_retired) assert cert_for_retired_user.name == self.user_retired_name - with override_waffle_flag(ENABLE_HISTORICAL_PII_RETIREMENT, active=True): + with override_waffle_flag(ENABLE_REDACT_HISTORICAL_PII_RETIREMENT, active=True): call_command("purge_pii_from_generatedcertificates") cert_for_active_user = GeneratedCertificate.objects.get(user_id=self.user_active) @@ -116,7 +116,7 @@ def test_management_command_dry_run(self): cert_for_retired_user = GeneratedCertificate.objects.get(user_id=self.user_retired) assert cert_for_retired_user.name == self.user_retired_name - with override_waffle_flag(ENABLE_HISTORICAL_PII_RETIREMENT, active=True): + with override_waffle_flag(ENABLE_REDACT_HISTORICAL_PII_RETIREMENT, active=True): with LogCapture() as logger: call_command("purge_pii_from_generatedcertificates", "--dry-run") diff --git a/lms/djangoapps/certificates/tests/test_api.py b/lms/djangoapps/certificates/tests/test_api.py index 4d87783fae6b..a1915f899f50 100644 --- a/lms/djangoapps/certificates/tests/test_api.py +++ b/lms/djangoapps/certificates/tests/test_api.py @@ -54,7 +54,7 @@ remove_allowlist_entry, set_cert_generation_enabled, ) -from lms.djangoapps.certificates.config import AUTO_CERTIFICATE_GENERATION, ENABLE_HISTORICAL_PII_RETIREMENT +from lms.djangoapps.certificates.config import AUTO_CERTIFICATE_GENERATION, ENABLE_REDACT_HISTORICAL_PII_RETIREMENT from lms.djangoapps.certificates.models import ( CertificateGenerationConfiguration, CertificateStatuses, @@ -1280,10 +1280,10 @@ def test_clear_pii_from_certificate_records(self): def test_clear_pii_from_certificate_records_clears_history_table(self): """ Verify that `clear_pii_from_certificate_records_for_user` blanks `name` in the - django-simple-history audit table only when the ``certificates.enable_historical_pii_retirement`` + django-simple-history audit table only when the ``certificates.enable_redact_historical_pii_retirement`` waffle flag is enabled, and leaves it untouched when the flag is disabled. """ - with override_waffle_flag(ENABLE_HISTORICAL_PII_RETIREMENT, active=False): + with override_waffle_flag(ENABLE_REDACT_HISTORICAL_PII_RETIREMENT, active=False): clear_pii_from_certificate_records_for_user(self.user) history_names = list( @@ -1293,7 +1293,7 @@ def test_clear_pii_from_certificate_records_clears_history_table(self): "History rows should be untouched when the waffle flag is disabled." ) - with override_waffle_flag(ENABLE_HISTORICAL_PII_RETIREMENT, active=True): + with override_waffle_flag(ENABLE_REDACT_HISTORICAL_PII_RETIREMENT, active=True): clear_pii_from_certificate_records_for_user(self.user) history_names_after = list( From 21572afe319f6742dac1c181f6ca9b74d77b2e86 Mon Sep 17 00:00:00 2001 From: Tarun Tak Date: Fri, 29 May 2026 13:16:18 +0000 Subject: [PATCH 5/5] fix: improve formatting in clear_pii_from_certificate_records_for_user docstring --- lms/djangoapps/certificates/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/certificates/api.py b/lms/djangoapps/certificates/api.py index 24e810fd8ed7..9c5fc9fffb3e 100644 --- a/lms/djangoapps/certificates/api.py +++ b/lms/djangoapps/certificates/api.py @@ -978,9 +978,9 @@ def clear_pii_from_certificate_records_for_user(user): model's custom `save()` function, nor fire any Django signals (which is desired at the time of writing). There is nothing to update in our external systems by this update. - When the ``certificates.enable_redact_historical_pii_retirement`` waffle flag is enabled, the django-simple-history audit - table ``certificates_historicalgeneratedcertificate`` is also updated so that no snapshot retains the learner's - name. + When the ``certificates.enable_redact_historical_pii_retirement`` waffle flag is enabled, the + django-simple-history audit table ``certificates_historicalgeneratedcertificate`` is also updated so that + no snapshot retains the learner's name. Args: user (User): The User instance of the learner actively being retired.