Skip to content

Commit

Permalink
WIP account delete, include new links in user menu
Browse files Browse the repository at this point in the history
  • Loading branch information
rafalp committed May 31, 2024
1 parent 9b6eb00 commit 9b9673f
Show file tree
Hide file tree
Showing 12 changed files with 268 additions and 7 deletions.
32 changes: 30 additions & 2 deletions misago/account/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
from functools import cached_property

from django import forms
from django.core.exceptions import PermissionDenied
from django.contrib.auth import get_user_model
from django.utils.translation import pgettext, pgettext_lazy

from ..permissions.accounts import allow_delete_own_account
from ..users.validators import validate_username
from .namechanges import get_available_username_changes

Expand Down Expand Up @@ -176,15 +178,15 @@ def clean_username(self):

if not self.permissions.can_change_username:
raise forms.ValidationError(
pgettext_lazy(
pgettext(
"account username help",
"You can't change your username.",
),
)

if not self.available_changes.can_change_username:
raise forms.ValidationError(
pgettext_lazy(
pgettext(
"account username help",
"You can't change your username at the moment.",
),
Expand All @@ -207,3 +209,29 @@ def save(self):
del self.available_changes

return self.instance


class AccountDeleteForm(forms.Form):
password = forms.CharField(max_length=255, widget=forms.PasswordInput)

def __init__(self, *args, **kwargs):
self.instance = kwargs.pop("instance")

super().__init__(*args, **kwargs)

def clean_password(self):
data = self.cleaned_data["password"]
if not self.instance.check_password(data):
raise forms.ValidationError(
pgettext(
"account delete form",
"Entered password is incorrect.",
),
)

try:
allow_delete_own_account(self.instance)
except PermissionDenied as e:
raise forms.ValidationError(str(e))

return data
21 changes: 21 additions & 0 deletions misago/account/menus.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.http import HttpRequest
from django.utils.translation import pgettext_lazy

from ..menus.menu import Menu
Expand All @@ -17,3 +18,23 @@
label=pgettext_lazy("account settings page", "Username"),
icon="card_membership",
)


def auth_is_not_delegated(request: HttpRequest) -> bool:
return not request.settings.enable_oauth2_client


def can_delete_own_account(request: HttpRequest) -> bool:
if not auth_is_not_delegated(request):
return False

return request.settings.allow_delete_own_account


account_settings_menu.add_item(
key="delete",
url_name="misago:account-delete",
label=pgettext_lazy("account settings page", "Delete account"),
icon="cancel",
visible=can_delete_own_account,
)
10 changes: 10 additions & 0 deletions misago/account/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@
settings.AccountUsernameView.as_view(),
name="account-username",
),
path(
"delete/",
settings.AccountDeleteView.as_view(),
name="account-delete",
),
path(
"delete/completed/",
settings.account_delete_completed,
name="account-delete-completed",
),
path(
"validate/email/",
validate.email,
Expand Down
43 changes: 42 additions & 1 deletion misago/account/views/settings.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
from typing import Any

from django.contrib import messages
from django.contrib.auth import logout
from django.core.exceptions import PermissionDenied
from django.forms import Form
from django.http import HttpRequest, HttpResponse
from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import redirect, render
from django.utils.translation import pgettext, pgettext_lazy
from django.views import View


from ...pagination.cursor import paginate_queryset
from ...users.online.tracker import clear_tracking
from ...users.tasks import delete_user
from ..forms import (
AccountDeleteForm,
AccountPreferencesForm,
AccountUsernameForm,
notifications_preferences,
Expand Down Expand Up @@ -175,3 +180,39 @@ def get_username_history(self, request: HttpRequest):
return paginate_queryset(
request, request.user.namechanges.select_related("changed_by"), 10, "-id"
)


class AccountDeleteView(AccountSettingsFormView):
template_name = "misago/account/settings/delete.html"

def get_form_instance(self, request: HttpRequest) -> AccountDeleteForm:
if request.method == "POST":
return AccountDeleteForm(request.POST, instance=request.user)

return AccountDeleteForm(instance=request.user)

def post(self, request: HttpRequest) -> HttpResponse:
form = self.get_form_instance(request)
if form.is_valid():
logout(request)
clear_tracking(request)

form.instance.mark_for_delete()
delete_user.delay(form.instance.id, form.instance.id)

request.session["misago_deleted_account"] = form.instance.username
return redirect("misago:account-delete-completed")

return self.render(request, self.template_name, {"form": form})


def account_delete_completed(request):
deleted_account = request.session.get("misago_deleted_account")
if not deleted_account:
raise Http404()

return render(
request,
"misago/account/settings/delete_completed.html",
{"deleted_account": deleted_account},
)
25 changes: 25 additions & 0 deletions misago/permissions/accounts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from django.core.exceptions import PermissionDenied
from django.utils.translation import pgettext_lazy


def allow_delete_own_account(user):
if user.is_deleting_account:
raise PermissionDenied(
pgettext_lazy("users delete permission", "You can't delete your account.")
)

if user.is_misago_admin:
raise PermissionDenied(
pgettext_lazy(
"users delete permission",
"You can't delete your account because you are an administrator.",
)
)

if user.is_staff:
raise PermissionDenied(
pgettext_lazy(
"users delete permission",
"You can't delete your account because you are a staff user.",
)
)
48 changes: 48 additions & 0 deletions misago/templates/misago/account/settings/delete.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{% extends "misago/account/settings/base.html" %}
{% load i18n misagoforms %}


{% block title %}{% trans "Delete account" context "account delete page" %} | {{ block.super }}{% endblock %}


{% block page %}
<form action="{% url 'misago:account-delete' %}" method="post">
{% csrf_token %}
<div class="panel panel-default panel-form">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Delete account" context "account delete page" %}
</h3>
</div>
<div class="panel-body">
<p>
{% trans "This form allows you to delete your account. This action is not reversible." context "account delete help" %}
</p>
<p>
{% trans "Your account, including its profile details, IP addresses, and notifications, will be deleted." context "account delete help" %}
</p>
<p>
{% trans "Other content will NOT be deleted, but the username displayed next to it will be changed to one shared by all deleted accounts." context "account delete help" %}
</p>
<p>
{% trans "Your username and email address will become available again for use during registration or for other accounts to change to." context "account delete help" %}
</p>
</div>
<div class="panel-footer">
{% with form.password as field %}
{% for error in field.errors %}
<p class="text-danger">{{ error }}</p>
{% endfor %}
<div class="input-group">
<input type="password" class="form-control" maxlength="{{ field.field.max_length }}" id="{{ field.id_for_label }}" name="{{ field.html_name }}" placeholder="{% trans 'Enter your password to confirm' context 'account delete page form' %}" {{ field|requiredhtml }} />
<span class="input-group-btn">
<button class="btn btn-danger" type="submit">
{% trans "Delete my account" context "account delete submit btn" %}
</button>
</span>
</div>
{% endwith %}
</div>
</div>
</form>
{% endblock page %}
39 changes: 39 additions & 0 deletions misago/templates/misago/account/settings/delete_completed.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{% extends "misago/base.html" %}
{% load i18n %}


{% block title %}{% trans "Account deleted" context "account deleted page" %} | {{ block.super }}{% endblock %}


{% block content %}
<div class="page page-error">
<div class="container page-container">
<div class="message-panel">

<div class="message-icon">
<span class="material-icon">check</span>
</div>

<div class="message-body">
<p class="lead">
{% blocktrans trimmed with username=deleted_account context "account deleted page" %}
{{ username }}, your account is now being deleted.
{% endblocktrans %}
</p>
<p>
{% trans "You have been logged out, and your account will remain locked until it is deleted." context "account deleted page" %}
</p>
<p>
{% trans "This process may take a few minutes to complete." context "account deleted page" %}
</p>
<p>
<a href="{% url 'misago:index'%}" class="btn btn-default">
{% trans "Return to homepage" context "account deleted page" %}
</a>
</p>
</div>

</div>
</div>
</div>
{% endblock content %}
2 changes: 1 addition & 1 deletion misago/templates/misago/account/settings/preferences.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% extends "misago/account/settings/base.html" %}
{% load i18n misagoforms %}
{% load i18n %}


{% block title %}{% trans "Preferences" context "account preferences page" %} | {{ block.super }}{% endblock %}
Expand Down
2 changes: 1 addition & 1 deletion misago/templates/misago/account/settings/username.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% extends "misago/account/settings/base.html" %}
{% load i18n misagoforms %}
{% load i18n %}


{% block title %}{% trans "Username" context "account username page" %} | {{ block.super }}{% endblock %}
Expand Down
15 changes: 14 additions & 1 deletion misago/users/tasks.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from celery import shared_task
from django.contrib.auth import get_user_model

from ..conf.shortcuts import get_dynamic_settings
from ..permissions.permissionsid import get_permissions_id

from .deletesrecord import record_user_deleted_by_self

NOTIFY_CHUNK_SIZE = 20

Expand All @@ -16,3 +17,15 @@ def remove_group_from_users_groups_ids(group_id: int):
user.groups_ids.remove(group_id)
user.permissions_id = get_permissions_id(user.groups_ids)
user.save(update_fields=["groups_ids", "permissions_id"])


@shared_task(name="users.delete", serializer="json")
def delete_user(user_id: int):
user = User.objects.filter(id=user_id).first()
if not user:
return

record_user_deleted_by_self()

settings = get_dynamic_settings()
user.delete(anonymous_username=settings.anonymous_username)
26 changes: 26 additions & 0 deletions misago/users/tests/test_delete_user_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import pytest
from django.contrib.auth import get_user_model

from ..models.deleteduser import DeletedUser
from ..tasks import delete_user

User = get_user_model()


def test_delete_user_task_deletes_user(user):
delete_user(user.id)

with pytest.raises(User.DoesNotExist):
user.refresh_from_db()


def test_delete_user_task_records_user_deletion(user):
delete_user(user.id)

assert DeletedUser.objects.exists()


def test_delete_user_task_does_nothing_if_user_is_not_found(user):
delete_user(user.id + 1)

user.refresh_from_db()
12 changes: 11 additions & 1 deletion misago/users/usersmenus.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from django.urls import reverse

from ..account.menus import account_settings_menu
from .models import Rank
from .pages import usercp, users_list

Expand All @@ -19,11 +20,20 @@ def get_user_options_pages(request) -> List[dict]:
if not request.user.is_authenticated:
return links

for item in account_settings_menu.bind_to_request(request).items:
links.append(
{
"icon": item.icon,
"name": item.label,
"url": item.url,
}
)

for section in usercp.get_sections(request):
links.append(
{
"icon": section["icon"],
"name": str(section["name"]),
"name": str(section["name"]) + " (deprecated)",
"url": reverse(section["link"]),
}
)
Expand Down

0 comments on commit 9b9673f

Please sign in to comment.