Skip to content

Commit

Permalink
Added function to allow a user to delete/anonymize their own account.
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuastegmaier committed Mar 6, 2024
1 parent 79d1d2c commit 5bcbdca
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 0 deletions.
4 changes: 4 additions & 0 deletions concordia/admin/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ def anonymize_action(modeladmin, request, queryset):
count = queryset.count()
for user_account in queryset:
user_account.username = "Anonymized %s" % uuid.uuid4()
user_account.first_name = ""
user_account.last_name = ""
user_account.email = ""
user_account.set_unusable_password()
user_account.is_staff = False
user_account.is_superuser = False
user_account.is_active = False
user_account.save()

Expand Down
6 changes: 6 additions & 0 deletions concordia/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ def clean_email(self):
return data


class AccountDeletionForm(forms.Form):
def __init__(self, *, request, **kwargs):
self.request = request
super().__init__(**kwargs)


class ContactUsForm(forms.Form):
referrer = forms.CharField(
label="Referring Page", widget=forms.HiddenInput(), required=False
Expand Down
40 changes: 40 additions & 0 deletions concordia/templates/account/account_deletion.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{% extends "base.html" %}

{% load bootstrap4 %}

{% block main_content %}
<div class="container">
<ul class="nav nav-tabs mb-4" id="nav-tab" role="tablist">
<li class="nav-item">
<a class="nav-link font-weight-bold" aria-selected="false" id="contributions-tab" type="button" role="tab" href="{% url 'user-profile' %}">My Contributions</a>
</li>
<li class="nav-item">
<a class="nav-link font-weight-bold" aria-selected="false" id="recent-tab" type="button" role="tab" aria-controls="recent" href="{% url 'user-profile' %}#recent">Recent Pages Worked On</a>
</li>
<li class="nav-item">
<a class="nav-link font-weight-bold active" aria-selected="true" id="account-tab" type="button" role="tab" href="{% url 'user-profile' %}#account">Account Settings</a>
</li>
</ul>
<div class="row">
<div class="col-md-8 mx-auto p-3">



</div>
</div>
<div class="col-12 col-md-10 py-3 mt-4 change-options">
<div class="d-flex">
<h2>Delete your account?</h2>
</div>
<div class="d-flex">
<p>This cannot be undone!</p>
</div>
<form class="form" action="{% url 'account-deletion' %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="input-group-append">
{% bootstrap_button "Delete Account" button_type="submit" button_class="btn btn-primary rounded-0" name="submit_delete" %}
</div>
</form>
</div>
</div>
{% endblock main_content %}
7 changes: 7 additions & 0 deletions concordia/templates/account/profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ <h2>Account Settings</h2>
</div>
</form>
</div>
<div class="col-12 col-md-10 py-3 mt-4 change-options">
<div class="row justify-content-start">
<div class="btn-row col-md-10">
<a class="btn btn-primary rounded-0" href="{% url 'account-deletion' %}">Delete Account</a>
</div>
</div>
</div>
</div>
</div>
<div class="row tab-pane fade{% if active_tab == 'contributions' %} show active{% endif %}" id="contributions" role="tabpanel">
Expand Down
4 changes: 4 additions & 0 deletions concordia/templates/emails/delete_account_body.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Your By the People account has been deleted.

Sorry to see you go,
-- The By the People team
1 change: 1 addition & 0 deletions concordia/templates/emails/delete_account_subject.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Your By the People account has been deleted
5 changes: 5 additions & 0 deletions concordia/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@
views.EmailReconfirmationView.as_view(),
name="email-reconfirmation",
),
path(
"account/delete/",
views.AccountDeletionView.as_view(),
name="account-deletion",
),
path(
".well-known/change-password", # https://wicg.github.io/change-password-url/
RedirectView.as_view(pattern_name="password_change"),
Expand Down
72 changes: 72 additions & 0 deletions concordia/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import random
import re
import uuid
from functools import wraps
from logging import getLogger
from smtplib import SMTPException
Expand All @@ -15,6 +16,7 @@
from captcha.models import CaptchaStore
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import (
Expand Down Expand Up @@ -63,6 +65,7 @@

from concordia.api_views import APIDetailView, APIListView
from concordia.forms import (
AccountDeletionForm,
ActivateAndSetPasswordForm,
AllowInactivePasswordResetForm,
ContactUsForm,
Expand Down Expand Up @@ -645,6 +648,75 @@ def send_reconfirmation_email(self, user):
)


@method_decorator(never_cache, name="dispatch")
class AccountDeletionView(LoginRequiredMixin, FormView):
template_name = "account/account_deletion.html"
form_class = AccountDeletionForm
success_url = reverse_lazy("homepage")
email_body_template = "emails/delete_account_body.txt"
email_subject_template = "emails/delete_account_subject.txt"

def get_form_kwargs(self):
# We expose the request object to the form so we can use it
# to log the user out after deletion
kwargs = super().get_form_kwargs()
kwargs["request"] = self.request
return kwargs

def form_valid(self, form):
self.delete_user(form.request.user, form.request)
return super().form_valid(form)

def delete_user(self, user, request=None):
logger.info("Deletion request for %s", user)
email = user.email
if user.transcription_set.exists():
logger.info("Anonymizing %s", user)
user.username = "Anonymized %s" % uuid.uuid4()
user.first_name = ""
user.last_name = ""
user.email = ""
user.set_unusable_password()
user.is_staff = False
user.is_superuser = False
user.is_active = False
user.save()
else:
logger.info("Deleting %s", user)
user.delete()
self.send_deletion_email(email)
if request:
logout(request)
return redirect("homepage")

def send_deletion_email(self, email):
context = {}
subject = render_to_string(
template_name=self.email_subject_template,
context=context,
request=self.request,
)
# Ensure subject is a single line
subject = "".join(subject.splitlines())
message = render_to_string(
template_name=self.email_body_template,
context=context,
request=self.request,
)
try:
send_mail(
subject,
message=message,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[email],
)
except SMTPException:
logger.exception(
"Unable to send account deletion email to %s",
email,
)


@method_decorator(default_cache_control, name="dispatch")
class HomeView(ListView):
template_name = "home.html"
Expand Down

0 comments on commit 5bcbdca

Please sign in to comment.