"""Local account views -- the ones allauth doesn't ship. Today: just `close_account` ("permanently delete this user"). Logout is allauth's; we override its template, not its view. """ from __future__ import annotations from django.contrib import messages from django.contrib.auth import logout from django.contrib.auth.decorators import login_required from django.db import transaction from django.http import HttpResponseForbidden from django.shortcuts import redirect, render from django.views.decorators.http import require_http_methods from apps.submissions.models import Submission @login_required @require_http_methods(["GET", "POST"]) def close_account(request): """Permanently delete the signed-in user and all of their submissions. GET -> render a "are you sure?" confirmation page, showing how many of their submissions will go with the user. POST -> delete the rows, log the user out, redirect to the dashboard. Refuses staff users (`is_staff=True`): they'd be locking themselves (and possibly the only operator) out of /admin/ with no recourse, so that path requires another operator to remove the row via /admin/auth/user/ instead. Why delete the submissions too: `Submission.submitted_by` uses `on_delete=SET_NULL`, but the `CheckConstraint` on the model requires that EITHER `submitted_by` OR `guest_email` is non-null. OAuth-created submissions have `guest_email=NULL`, so SET_NULL would violate the constraint at delete time. Simpler + matches user expectation of "delete my account": wipe the rows wholesale. The `post_delete` signal in `apps/submissions/signals.py` unlinks the uploaded STLs at the same time. """ if request.user.is_staff: return HttpResponseForbidden( "Staff users cannot close their own account from here. " "Ask another operator to remove the row via /admin/auth/user/." ) if request.method == "POST": user = request.user username = user.get_username() with transaction.atomic(): Submission.objects.filter(submitted_by=user).delete() logout(request) user.delete() messages.info( request, f"Account {username} and all of your prints have been " f"permanently deleted.", ) return redirect("dashboard:index") submission_count = Submission.objects.filter(submitted_by=request.user).count() return render( request, "account/close.html", {"submission_count": submission_count}, )