Skip to content

fix: add command to release a retired learner's email for reuse - #460

Merged
robrap merged 5 commits into
release-ulmofrom
lp-1177-release-retired-learner-email
Sep 8, 2026
Merged

fix: add command to release a retired learner's email for reuse#460
robrap merged 5 commits into
release-ulmofrom
lp-1177-release-retired-learner-email

Conversation

@rahulkanneri-2u

@rahulkanneri-2u rahulkanneri-2u commented Sep 7, 2026

Copy link
Copy Markdown

Description

A retired learner's original email is permanently blocked from re-registration, because is_email_retired()
still finds a User row whose email matches the retired-hash of that address.

This adds release_retired_learner_email(user) (accounts/utils.py), which replaces a fully-retired user's
hashed email with a placeholder (retired_email_<user_id>@<RETIRED_EMAIL_DOMAIN>), clearing that match so the
original address becomes reusable. The UserRetirementStatus row and retirement history are left untouched.

Exposed via a new release_retired_user_email management command (--username or --user_id) for
Operator/Support use. No automatic trigger (waffle flag, API hook) is wired up so this is manual only, for now.

Supporting information

Testing instructions

  1. Retire a test user to the COMPLETE state.
  2. is_email_retired("learner@example.com") => True.
  3. ./manage.py lms release_retired_user_email --username <retired_username>
  4. is_email_retired("learner@example.com") => False; confirm re-registration succeeds.
  5. pytest openedx/core/djangoapps/user_api/accounts/tests/test_utils.py openedx/core/djangoapps/user_api/management/tests/test_release_retired_user_email.py

Deadline

None.

Other information

None.

Comment thread openedx/core/djangoapps/user_api/accounts/utils.py Outdated
@jristau1984
jristau1984 requested a review from robrap September 8, 2026 12:45
# Mutually exclusive + required enforces "exactly one of the two" for us,
# so handle() doesn't need to re-validate that itself.
identifier = parser.add_mutually_exclusive_group(required=True)
identifier.add_argument('--username', type=str, help='Username of the retired learner to release.')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this? Since it is a retired user, let's not use PII. Non-blocking, but we should fix before moving to openedx.


user.refresh_from_db()
assert user.email == released_email
assert 'Successfully released email' in capsys.readouterr().out

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: Wondering if mocking locker like in

is more common and a better practice. I can think of pros and cons of both.

return create_retirement_status(user, state=RetirementState.objects.get(state_name=state_name))

def test_releases_email_when_retirement_complete(self):
user = UserFactory(email='retired__user_abc123@retired.invalid')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is retired.invalid the value of {settings.RETIRED_EMAIL_DOMAIN}? The inconsistency in the test is a little confusing.

Comment thread openedx/core/djangoapps/user_api/accounts/tests/test_utils.py Outdated
from completion.models import BlockCompletion
from completion.waffle import ENABLE_COMPLETION_TRACKING_SWITCH
from django.conf import settings
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't copy-past lint-amnesty. It will be removed from openedx altogether.

Suggested change
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
from django.contrib.auth.models import User # pylint: disable=imported-auth-user

@robrap
robrap enabled auto-merge (squash) September 8, 2026 16:41
@robrap

robrap commented Sep 8, 2026

Copy link
Copy Markdown

Closing/re-opening to rerun tests.

@robrap robrap closed this Sep 8, 2026
auto-merge was automatically disabled September 8, 2026 16:47

Pull request was closed

@robrap robrap reopened this Sep 8, 2026
@robrap
robrap enabled auto-merge (squash) September 8, 2026 16:50
@robrap
robrap merged commit ec21111 into release-ulmo Sep 8, 2026
133 of 166 checks passed
@robrap
robrap deleted the lp-1177-release-retired-learner-email branch September 8, 2026 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants