Skip to content

feat: suspend user accounts from the staff console (GDPR Art. 18)#607

Open
Joe-Heffer-Shef wants to merge 7 commits into
mainfrom
feat/suspend-user-account
Open

feat: suspend user accounts from the staff console (GDPR Art. 18)#607
Joe-Heffer-Shef wants to merge 7 commits into
mainfrom
feat/suspend-user-account

Conversation

@Joe-Heffer-Shef

@Joe-Heffer-Shef Joe-Heffer-Shef commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Closes #584

Summary

Implements the Right to Restriction of Processing (UK GDPR Article 18): staff can temporarily suspend a user account — the user can no longer log in or submit data, but all their personal data and survey responses are retained, not deleted. The action is fully reversible. This is distinct from full erasure (#570).

Built on Django's existing User.is_active flag, so there is no migration and no cascade deletion.

Screenshots

/console/users/4/
image
/console/users/4/suspend/
image
image
image

Changes

  • Views (home/views/console.py)
    • ConsoleSuspendUserView — GET renders a confirmation page, POST sets is_active=False. Guarded against suspending yourself or a superuser (raises PermissionDenied).
    • ConsoleUnsuspendUserView — POST restores is_active=True.
    • ConsoleUserListView now lists all users (previously active-only, which would have hidden suspended accounts).
  • URLs (home/urls.py) — admin_suspend_user, admin_unsuspend_user.
  • Templates
    • New console/suspend_user_confirm.html — mirrors the existing remove-member confirm dialog (type the email to enable submit); copy makes clear data is retained.
    • console/users.html — new Status column (Active / Suspended badges).
    • console/user_detail.htmlSuspended badge + Suspend account / Lift suspension actions (hidden for self and superusers).
  • Login (home/views/auth.py) — a suspended user with correct credentials sees a clear "Your account has been suspended" message instead of the generic error. Only shown when the password actually matches, so it does not reveal which emails exist.

Acceptance criteria

  • Staff console includes a "Suspend account" action per user
  • A suspended user cannot log in (clear error message)
  • Suspension is reversible by staff (toggle on/off)
  • Suspension state is visible in the staff console user list
  • Suspension does not cascade-delete any related data (test-verified)

Testing

  • python manage.py test home.tests.test_console_views home.tests.test_user_views → 45 tests OK
  • python manage.py check --fail-level WARNING → no issues
  • makemigrations --check --dry-run → no changes detected

10 new tests cover staff-only access, the suspend/unsuspend round-trip, no-cascade (memberships survive), the self/superuser guards (403), suspended-user visibility in the list, and the clear-vs-generic login messages.

Follow-up

An audit trail (who/when) is out of scope here — a separate audit-log PR is in flight. Follow-up issue to wire suspension into it will be linked.

🤖 Generated with Claude Code

Implements the Right to Restriction of Processing: staff can temporarily
freeze a user account so they cannot log in, while retaining all their data.

- Add ConsoleSuspendUserView (confirm + suspend) and ConsoleUnsuspendUserView
  to the staff console, reusing Django's is_active flag (no migration, no
  cascade delete). Guards prevent suspending yourself or a superuser.
- Show all users in the console list with an Active/Suspended status column,
  and add suspend/lift-suspension actions on the user detail page.
- Show a clear "account has been suspended" message on login for suspended
  users with valid credentials, instead of the generic error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Joe-Heffer-Shef

Copy link
Copy Markdown
Collaborator Author

Follow-up for the audit trail tracked in #608.

@Joe-Heffer-Shef Joe-Heffer-Shef self-assigned this Jul 8, 2026
@Joe-Heffer-Shef Joe-Heffer-Shef added enhancement New feature or request data-protection Ethics, privacy, data sharing, and data management labels Jul 8, 2026
Resolves conflicts in home/views/console.py and home/tests/test_console_views.py
between this branch's account suspension feature and main's GDPR user
deletion/anonymisation feature.

Both features flip User.is_active to False for different reasons, so the
console user list's active/deleted/all status filter and badges could not
tell a suspended account from a deleted (anonymised) one. Added
User.is_deleted (keyed off the deleted.invalid email domain anonymise()
already uses) to disambiguate, and used it to:
- keep the "deleted" status filter scoped to anonymised accounts only, so
  suspended accounts still show under "active"
- show the correct badge (Deleted vs Suspended) in the user list and user
  detail page
- stop the suspend/unsuspend actions from being offered for already-deleted
  accounts

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Joe-Heffer-Shef

This comment was marked as resolved.

Joe-Heffer-Shef and others added 5 commits July 8, 2026 15:06
Extends the console suspend-user guard to reject staff targets, not just self and superuser accounts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Use AllowAllUsersModelBackend and confirm_login_allowed() to surface the
suspended-account message from the same authenticate() call, instead of
a separate check_password() call that could leak suspension status via
timing.
Suspended accounts were only distinguishable via a per-row badge under
the "Active" tab, with no way to filter down to just them.
viewed_user.email was interpolated into a JS string comparison relying
on HTML auto-escaping rather than JS-context escaping. Low risk since
the field is EmailField-constrained, but escapejs is the correct
filter here and for future reuse of this confirmation pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ount

# Conflicts:
#	home/constants.py
#	home/models.py
#	home/services/user.py
@Joe-Heffer-Shef
Joe-Heffer-Shef requested a review from twinkarma July 8, 2026 15:22

@twinkarma twinkarma left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

data-protection Ethics, privacy, data sharing, and data management enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Right to Restriction: ability to suspend a user account without deleting data

2 participants