Implement "delete account" function

Make it possible for users to delete their own account and all associated
information from the database, should they decide they no longer wish to
use it.

(I checked the implications of doing this on our model structure -
anything with a foreign key to user is safe to delete with the exception
of RRS MaintenancePlan.admin which I needed to change on_delete for so
that it doesn't get deleted with the user).

Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
This commit is contained in:
Paul Eggleton 2018-10-17 10:21:55 +13:00
parent 23194fc5d4
commit 2c8f979f9c
6 changed files with 114 additions and 3 deletions

View File

@ -4,9 +4,12 @@
#
# Licensed under the MIT license, see COPYING.MIT for details
from django import forms
from captcha.fields import CaptchaField
from registration.forms import RegistrationForm
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.models import User
from django.contrib.auth.hashers import check_password
class CaptchaRegistrationForm(RegistrationForm):
@ -14,3 +17,18 @@ class CaptchaRegistrationForm(RegistrationForm):
class CaptchaPasswordResetForm(PasswordResetForm):
captcha = CaptchaField(label='Verification', help_text='Please enter the letters displayed for verification purposes', error_messages={'invalid':'Incorrect entry, please try again'})
class DeleteAccountForm(forms.ModelForm):
confirm_password = forms.CharField(widget=forms.PasswordInput)
class Meta:
model = User
fields = ('confirm_password', )
def clean(self):
cleaned_data = super(DeleteAccountForm, self).clean()
confirm_password = cleaned_data.get('confirm_password')
if not check_password(confirm_password, self.instance.password):
self.add_error('confirm_password', 'Password does not match.')
return cleaned_data

View File

@ -4,9 +4,15 @@
#
# Licensed under the MIT license, see COPYING.MIT for details
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.core.exceptions import PermissionDenied
from django.shortcuts import render
from django.contrib import messages
from django.contrib.auth import logout
from registration.backends.model_activation.views import RegistrationView
from django.contrib.auth.views import PasswordResetView
from layerindex.auth_forms import CaptchaRegistrationForm, CaptchaPasswordResetForm
from layerindex.auth_forms import CaptchaRegistrationForm, CaptchaPasswordResetForm, DeleteAccountForm
class CaptchaRegistrationView(RegistrationView):
@ -28,3 +34,30 @@ class CaptchaRegistrationView(RegistrationView):
class CaptchaPasswordResetView(PasswordResetView):
form_class = CaptchaPasswordResetForm
def delete_account_view(request, template_name):
if not request.user.is_authenticated():
raise PermissionDenied
if request.user.is_superuser:
# It's not really appropriate for the superuser to be deleted this way
raise PermissionDenied
if request.method == 'POST':
form = DeleteAccountForm(request.POST, instance=request.user)
if form.is_valid():
# Naturally we don't call form.save() here !
# Take a copy of request.user as it is about to be invalidated by logout()
user = request.user
logout(request)
user.delete()
messages.add_message(request, messages.SUCCESS,
'Your user account has been successfully deleted')
return HttpResponseRedirect(reverse('frontpage'))
else:
form = DeleteAccountForm(instance=request.user)
return render(request, template_name, {
'user': request.user,
'form': form,
})

View File

@ -29,7 +29,7 @@ class MaintenancePlan(models.Model):
email_subject = models.CharField(max_length=255, blank=True, default='[Recipe reporting system] Upgradable recipe name list', help_text='Subject line of automated emails')
email_from = models.CharField(max_length=255, blank=True, help_text='Sender for automated emails')
email_to = models.CharField(max_length=255, blank=True, help_text='Recipient for automated emails (separate multiple addresses with ;)')
admin = models.ForeignKey(User, blank=True, null=True, help_text='Plan administrator')
admin = models.ForeignKey(User, blank=True, null=True, help_text='Plan administrator', on_delete=models.SET_NULL)
maintainer_style = models.CharField(max_length=1, choices=MAINTENANCEPLAN_MAINTAINER_STYLE, default='L', help_text='Maintainer tracking style for the layers within this plan')
def get_default_release(self):

View File

@ -0,0 +1,56 @@
{% extends "base.html" %}
{% load i18n %}
{% comment %}
layerindex-web - delete account confirmation template
Copyright (C) 2018 Intel Corporation
Licensed under the MIT license, see COPYING.MIT for details
{% endcomment %}
<!--
{% autoescape on %}
{% block title_append %} - delete account{% endblock %}
{% endautoescape %}
-->
{% block content %}
{% autoescape on %}
<h2>Delete account</h2>
<p>Are you sure you want to delete your account under the name &quot;{{ user.username }}&quot;? <strong>This will remove all associated records and cannot be undone!</strong></p>
<p>To confirm, please enter your password below and then click on <em>Delete account</em>.</p>
<form action="" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in form.visible_fields %}
{% if field.errors %}
<div class="form-group alert alert-danger">
{{ field.errors }}
{% else %}
<div class="form-group">
{% endif %}
<div class="control-label {% if field.required %}requiredlabel{% endif %}">
{{ field.label_tag }}
</div>
<div class="controls">
{{ field }}
</div>
</div>
{% endfor %}
<input type="submit" value="Delete account" class="btn btn-danger" />
<a href="{% url 'profile' %}" class="btn btn-default">Cancel</a>
</form>
{% endautoescape %}
{% endblock %}

View File

@ -43,6 +43,7 @@
<input type="submit" class="btn btn-default" value="{% trans 'Save' %}" />
<a class="btn btn-default" href="{% url 'frontpage' %}">{% trans 'Cancel' %}</a>
<a class="btn btn-danger" href="{% url 'delete_account' %}">{% trans 'Delete account' %}</a>
{% csrf_token %}
</form>

View File

@ -8,7 +8,7 @@
from django.conf.urls import include, url
from django.core.urlresolvers import reverse_lazy
from django.views.generic import RedirectView
from layerindex.auth_views import CaptchaRegistrationView, CaptchaPasswordResetView
from layerindex.auth_views import CaptchaRegistrationView, CaptchaPasswordResetView, delete_account_view
from django.contrib import admin
admin.autodiscover()
@ -25,6 +25,9 @@ urlpatterns = [
name='auth_password_reset'),
url(r'^accounts/register/$', CaptchaRegistrationView.as_view(),
name='registration_register'),
url(r'^accounts/delete/$', delete_account_view,
{'template_name': 'layerindex/deleteaccount.html'},
name='delete_account'),
url(r'^accounts/', include('registration.backends.default.urls')),
url(r'^captcha/', include('captcha.urls')),
]