fix: free retired learner's email on retirement completion - #458
Merged
Conversation
Alam-2U
approved these changes
Sep 4, 2026
naincy128
approved these changes
Sep 4, 2026
santhosh-apphelix-2u
approved these changes
Sep 4, 2026
Comment on lines
+23
to
+24
| parser.add_argument('--username', type=str, help='Username of the retired learner to free.') | ||
| parser.add_argument('--user_id', type=int, help='User ID of the retired learner to free.') |
Member
There was a problem hiding this comment.
Suggested change
| parser.add_argument('--username', type=str, help='Username of the retired learner to free.') | |
| parser.add_argument('--user_id', type=int, help='User ID of the retired learner to free.') | |
| user_group = parser.add_mutually_exclusive_group(required=True) | |
| user_group.add_argument('--username', type=str, help='Username of the retired learner to allow email reuse.',) | |
| user_group.add_argument('--user-id', type=int, help='User ID of the retired learner to allow email reuse.',) |
| old_email = user.email | ||
| user.email = old_email + freed_suffix | ||
| user.save(update_fields=['email']) | ||
| LOGGER.info(f"Freed retired email for user {user.id}: '{old_email}' -> '{user.email}'.") |
Member
There was a problem hiding this comment.
Logger to be sanitized - remove email & username.
Comment on lines
+43
to
+44
| logger.info(f'Successfully freed email for user {user.id}.') | ||
| print(f'Successfully freed email for user {user.id}.') |
Member
There was a problem hiding this comment.
Suggested change
| logger.info(f'Successfully freed email for user {user.id}.') | |
| print(f'Successfully freed email for user {user.id}.') | |
| logger.info(f'Successfully freed email for user {user.id}.') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Retirement rewrites
auth_user.emailto a deterministic hash and keeps that row forever for compliance, butis_email_retired()matches that hash permanently, so a retired learner could never register again with their own former email.This adds
free_retired_learner_email(user)(accounts/utils.py), which appends.freed.<user_id>to the retired-hash email once retirement isCOMPLETE. That's enough to stop it matchingis_email_retired(), without deleting the row or nulling the email (both break compliance/uniqueness constraints). It's a no-op if the email's already been freed.The handler behind
PATCH /api/user/v1/accounts/update_retirement_status/that tubular calls on every state transition so the email is freed automatically the moment retirement reachesCOMPLETE.A
free_retired_user_emailmanagement command (--username/--user_id) is also added, for backfilling learners retired before this existed.Impact: Learners can re-register with a previously-retired email once retirement completes.
Operators get a command to backfill older retirements. No UI changes, no migration.
Testing instructions
Other information
emailcolumn on already-retired rows theUserRetirementStatusaudit row is untouched.