Commit 27435397 authored by Robert's avatar Robert

Merge branch '413-delete-user' into release-2-3

parents c75e2651 50f6293a
Pipeline #1246 failed with stage
in 59 seconds
......@@ -2,7 +2,7 @@ import django.views.generic.edit
from django.utils.timezone import now
import core
import features
from features.associations.views import AssociationMixin
from . import models
......@@ -47,9 +47,7 @@ class ContributionFormMixin(django.views.generic.edit.FormMixin):
return self.object.get_absolute_url()
class Delete(
features.associations.views.AssociationMixin, core.views.PermissionMixin,
django.views.generic.UpdateView):
class Delete(AssociationMixin, core.views.PermissionMixin, django.views.generic.UpdateView):
permission_required = 'contributions.delete'
model = models.Contribution
fields = []
......
......@@ -64,6 +64,17 @@ class Gestalt(core.models.Model):
def can_login(self):
return self.user.has_usable_password()
def delete(self, *args, **kwargs):
data = self.get_data()
unknown_gestalt = Gestalt.objects.get(id=settings.GROUPRISE_UNKNOWN_GESTALT_ID)
data['associations'].update(entity_id=unknown_gestalt.id)
data['contributions'].update(author=unknown_gestalt)
data['images'].update(creator=unknown_gestalt)
data['memberships_created'].update(created_by=unknown_gestalt)
data['versions'].update(author=unknown_gestalt)
data['votes'].update(voter=unknown_gestalt)
self.user.delete()
def get_absolute_url(self):
if self.public:
return self.get_profile_url()
......@@ -73,6 +84,29 @@ class Gestalt(core.models.Model):
def get_contact_url(self):
return urls.reverse('create-gestalt-conversation', args=(self.pk,))
def get_data(self):
'''
Return all data directly related to this gestalt. May be used e.g. in conjunction with
deleting users.
'''
data = {}
data['gestalt'] = self
data['user'] = self.user
# data['groups_created'] = ?
data['memberships'] = self.memberships
data['subscriptions'] = self.subscriptions
data['tokens'] = self.permissiontoken_set
data['settings'] = self.gestaltsetting_set
data['associations'] = self.associations
data['contributions'] = self.contributions
data['images'] = self.images
data['memberships_created'] = self.memberships_created
data['versions'] = self.versions
data['votes'] = self.votes
return data
def get_profile_url(self):
return urls.reverse(
'entity', args=[type(self).objects.get(pk=self.pk).user.username])
......
......@@ -18,6 +18,8 @@ add_perm('gestalten.change', is_authenticated & is_self)
add_perm('gestalten.change_email', is_authenticated)
add_perm('gestalten.change_password', is_authenticated)
add_perm('gestalten.delete', is_authenticated)
add_perm('account.confirm', always_allow)
add_perm('account.set_password', is_authenticated)
add_perm('account.signup', ~is_authenticated)
{% extends 'stadt/stadt.html' %}
{% block title %}Benutzerkonto löschen - {{ block.super }}{% endblock %}
{% block menu %}{% menu 'gestalt' %}{% endblock %}
{% block heading_title_text %}Einstellungen{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
<p><strong>Möchtest Du Dein Benutzerkonto wirklich löschen?</strong></p>
<p><strong>Folgende Daten werden unwiderruflich gelöscht:</strong></p>
<ul>
<li>Benutzerkonto und Profil mit allen Einstellungen und E-Mail-Adressen</li>
<li>{{ view.data.subscriptions.count }} Abonnements von Gruppen</li>
<li>{{ view.data.memberships.count }} Mitgliedschaften in Gruppen</li>
</ul>
<p><strong>Folgende Daten werden unwiderruflich als <em>Unbekannte Gestalt</em> markiert:</strong></p>
<ul>
<li>{{ view.data.versions.count }} Beitragsversionen</li>
<li>{{ view.data.contributions.count }} Kommentare und Nachrichten</li>
<li>{{ view.data.images.count }} Bilder</li>
<li>{{ view.data.votes.count }} Stimmen in Umfragen</li>
</ul>
{% if about_group %}
<p>Solltest Du damit nicht einverstanden sein, führe diesen Schritt bitte nicht aus sondern
<a href="{% url 'create-group-conversation' about_group.pk %}">schreib uns stattdessen eine Nachricht</a>.</p>
{% endif %}
<button class="btn btn-danger">
Benutzerkonto löschen
</button>
</form>
{% endblock %}
......@@ -22,4 +22,15 @@
<button class="btn btn-primary">Einstellungen speichern</button>
</form>
<div class="section section-publish section-article" data-component="publish">
<h2>Gefahrenbereich</h2>
<div class="row">
<div class="col-md-5">
<a href="{% url 'delete-gestalt' %}" class="btn btn-danger btn-sm btn-block">
Benutzerkonto löschen
</a>
</div>
</div>
</div>
{% endblock %}
......@@ -2,6 +2,8 @@ from django.contrib import auth
from django.urls import reverse
from django.test import TestCase
from . import models
class GestaltMixin:
@classmethod
......@@ -62,6 +64,10 @@ class Settings(TestCase):
class AuthenticatedSettings(AuthenticatedMixin, TestCase):
def setUp(self):
auth.get_user_model().objects.create(email='unknown@example.org', username='unknown')
super().setUp()
def test_authenticated_settings(self):
# general settings accessible
r = self.client.get('/')
......@@ -90,3 +96,14 @@ class AuthenticatedSettings(AuthenticatedMixin, TestCase):
self.assertEqual(r.status_code, 200)
r = self.client.get(password_settings_url)
self.assertEqual(r.status_code, 302)
def test_delete(self):
delete_url = reverse('delete-gestalt')
r = self.client.get(reverse('settings'))
self.assertContains(r, 'href="{}'.format(delete_url))
r = self.client.get(delete_url)
self.assertEqual(r.status_code, 200)
r = self.client.post(delete_url)
self.assertRedirects(r, '/')
self.assertFalse(models.Gestalt.objects.filter(pk=self.gestalt.pk).exists())
......@@ -24,6 +24,11 @@ urlpatterns = [
views.Update.as_view(),
name='settings'),
url(
r'^stadt/settings/gestalt/delete/$',
views.Delete.as_view(),
name='delete-gestalt'),
url(
r'^stadt/settings/images/$',
views.UpdateImages.as_view(),
......
......@@ -5,7 +5,7 @@ from allauth.account import views as allauth_views
from crispy_forms import layout
from django.urls import reverse
from django.views import generic
from django.views.generic import edit as edit_views, UpdateView
from django.views.generic import edit as edit_views, DeleteView, UpdateView
import core
from core import views as utils_views
......@@ -32,6 +32,17 @@ class Create(utils_views.ActionMixin, views.SignupView):
return views.LoginView.get_success_url(self)
class Delete(PermissionMixin, DeleteView):
permission_required = 'gestalten.delete'
template_name = 'gestalten/delete.html'
success_url = '/'
def get_object(self):
gestalt = self.request.user.gestalt
self.data = gestalt.get_data()
return gestalt
class Detail(
core.views.PermissionMixin, django.views.generic.list.MultipleObjectMixin,
django.views.generic.DetailView):
......
......@@ -226,6 +226,8 @@ HAS_PIWIK = True
STADTGESTALTEN_INTRO_TEXT = ''
GROUPRISE_UNKNOWN_GESTALT_ID = 1
# Authentication
# http://django-allauth.readthedocs.org/
......
......@@ -5,3 +5,4 @@ DATABASES = {
'ENGINE': 'django.db.backends.sqlite3',
}
}
GROUPRISE_UNKNOWN_GESTALT_ID = 1
......@@ -6,7 +6,6 @@ urlpatterns = [
urls.url(r'^stadt/admin/', admin.site.urls),
urls.url(r'^stadt/api/', urls.include('core.api_urls')),
urls.url(r'^stadt/', urls.include('features.associations.urls')),
urls.url(r'^stadt/', urls.include('features.conversations.urls')),
urls.url(r'^stadt/', urls.include('features.memberships.urls')),
urls.url(r'^stadt/', urls.include('features.sharing.urls')),
......@@ -26,5 +25,6 @@ urlpatterns = [
urls.url(r'^', urls.include('features.tags.urls')),
# matches */*/
urls.url(r'^', urls.include('features.associations.urls')),
urls.url(r'^', urls.include('features.content.urls')),
] + static.static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment