← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands-website/delete_user into lp:widelands-website

 

kaputtnik has proposed merging lp:~widelands-dev/widelands-website/delete_user into lp:widelands-website.

Commit message:
Make it possible to 'delete a user, which means clean his data and show 'Deleted' in every place where a username is shown.

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1790307 in Widelands Website: "Make it possible to 'delete' a user"
  https://bugs.launchpad.net/widelands-website/+bug/1790307

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands-website/delete_user/+merge/354978

Added a link in the edit profile page which switch to a page showing what deletion means. After clicking on 'I am sure, delete me', the following changes are made:

- Log the user immediately out
- Delete possible playtime scheduling dates
- Delete the Online Gaming Password
- Clean the users profile page, including physically deleting his current image of avatar
- Deactivate all subscriptions for this user
- Put all his PMs in the users trash
- Set the user inactive, also set 'is_staff' and 'is_superuser' to false
- Set the users E-Mail to the value of settings.DELETED_MAIL_ADDRESS
- Show the value of settings.DELETED_USERNAME in all places where a username is shown on the website. Wehen hovering over such a name a tooltip is shown saying 'This user has left our community'
- Prevent sending PMs to a deleted user
- Prevent showing the users profile by directly typing the url in the browsers addressbar

The decision when to show the DELETED_USERNAME is done by comparing the value of the new boolean field 'deleted' in the model wlprofile.Profile. I have added this new field because other comparisons (e.g. against 'is_staff' or the anonymized Email address) seems a bit iffy to me.

What username should shown for a deleted user? Currently this is 'Deleted', if we want another name it should be consistent with the username rules, which means it should contain only  alphanumeric, _, @, +, . and - characters.

Just found that the DELETED_USER_MAILADDRESS can also be an empty value. I will set it to an empty string then.

Additional cleanup:
- Removed unused functions for feeds
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands-website/delete_user into lp:widelands-website.
=== modified file 'django_messages_wl/views.py'
--- django_messages_wl/views.py	2018-04-18 12:04:12 +0000
+++ django_messages_wl/views.py	2018-09-15 07:44:50 +0000
@@ -15,7 +15,7 @@
     if request.is_ajax():
         q = request.GET.get('term', '')
 
-        usernames = User.objects.filter(username__icontains=q)
+        usernames = User.objects.exclude(is_active=False).filter(username__icontains=q)
         results = []
         for user in usernames:
             name_json = {'value': user.username}

=== modified file 'news/feeds.py'
--- news/feeds.py	2018-04-08 14:40:17 +0000
+++ news/feeds.py	2018-09-15 07:44:50 +0000
@@ -21,25 +21,3 @@
 
     def item_pubdate(self, item):
         return item.publish
-
-# Currently not used / not checked for compatibility for django 1.8
-
-
-class NewsPostsByCategory(Feed):
-    title = 'Widelands.org posts category feed'
-
-    def get_object(self, bits):
-        if len(bits) != 1:
-            raise ObjectDoesNotExist
-        return Category.objects.get(slug__exact=bits[0])
-
-    def link(self, item):
-        if not item:
-            raise FeedDoesNotExist
-        return item.get_absolute_url()
-
-    def description(self, item):
-        return 'Posts recently categorized as %s' % item.title
-
-    def items(self, item):
-        return item.post_set.published()[:10]

=== modified file 'news/urls.py'
--- news/urls.py	2016-12-13 18:28:51 +0000
+++ news/urls.py	2018-09-15 07:44:50 +0000
@@ -1,25 +1,25 @@
 from django.conf.urls import *
 from django.views.generic import ListView
 from news.views import NewsList, YearNews, MonthNews, NewsDetail, CategoryView
+from news.feeds import NewsPostsFeed
+
 
 urlpatterns = [
     url(r'^(?P<year>[0-9]{4})/(?P<month>[-\w]+)/(?P<day>[0-9]+)/(?P<slug>[-\w]+)/$',
         NewsDetail.as_view(),
         name='news_detail'),
-
     url(r'^(?P<year>\d{4})/(?P<month>[-\w]+)/$',
         MonthNews.as_view(),
         name='news_archive_month'),
-
     url(r'^(?P<year>\d{4})/$',
         YearNews.as_view(),
         name='news_archive_year'),
-
     url(r'^category/(?P<slug>[-\w]+)/',
         CategoryView.as_view(),
         name='category_posts'),
-
     url(r'^$',
         NewsList.as_view(template_name='news/post_list.html'),
         name='news_index'),
+    # Feed
+    url(r'^feed/$', NewsPostsFeed())
 ]

=== modified file 'pybb/feeds.py'
--- pybb/feeds.py	2018-04-08 14:40:17 +0000
+++ pybb/feeds.py	2018-09-15 07:44:50 +0000
@@ -3,6 +3,7 @@
 from django.core.exceptions import ObjectDoesNotExist
 from django.utils.feedgenerator import Atom1Feed
 from pybb.models import Post, Topic, Forum
+from django.conf import settings
 
 
 class PybbFeed(Feed):
@@ -60,11 +61,6 @@
     def items_for_object(self, obj):
         return Post.objects.filter(hidden=False, topic__forum=obj).order_by('-created')[:15]
 
-    def item_author_name(self, item):
-        """Takes the object returned by get_object and returns the feeds's
-        auhor's name as a Python string."""
-        return item.user.username
-
 # Validated through http://validator.w3.org/feed/
 
 
@@ -78,8 +74,3 @@
 
     def items_for_object(self, item):
         return Topic.objects.exclude(posts__hidden=True).filter(forum=item).order_by('-created')[:15]
-
-    def item_author_name(self, item):
-        """Takes the object returned by get_object and returns the feeds's
-        auhor's name as a Python string."""
-        return item.user.username

=== modified file 'settings.py'
--- settings.py	2018-09-04 18:17:07 +0000
+++ settings.py	2018-09-15 07:44:50 +0000
@@ -142,6 +142,7 @@
                 'django.template.context_processors.static',
                 'django.template.context_processors.tz',
                 'django_messages.context_processors.inbox',
+                'wlprofile.context_processors.deleted_user_data',
             ],
         },
     },
@@ -322,6 +323,13 @@
 # Number of stored users
 ONLINE_MAX = 25
 
+###########################################
+# Settings for users who deleted themself #
+###########################################
+
+DELETED_MAIL_ADDRESS = 'deleted@xxxxxx'
+DELETED_USERNAME = 'Deleted'
+
 try:
     from local_settings import *
 except ImportError:

=== modified file 'templates/django_messages/view.html'
--- templates/django_messages/view.html	2016-03-02 21:02:38 +0000
+++ templates/django_messages/view.html	2018-09-15 07:44:50 +0000
@@ -30,8 +30,9 @@
 			</td>
 		</tr>
 	</table>
-	{% ifequal message.recipient user %}
+	{% if message.recipient == user and not message.sender.wlprofile.deleted %}
+		{{ context.deleted_email_address }}
 		<button type="button" onclick="location.href='{% url 'messages_reply' message.id %}';">{% trans "Reply" %}</button>
-	{% endifequal %}
+	{% endif %}
 	<button type="button" onclick="location.href='{% url 'messages_delete' message.id %}';">{% trans "Delete" %}</button>
 {% endblock %}

=== modified file 'templates/mainpage.html'
--- templates/mainpage.html	2016-03-02 21:02:38 +0000
+++ templates/mainpage.html	2018-09-15 07:44:50 +0000
@@ -12,7 +12,7 @@
 {% block extra_head %}
 <meta name="google-site-verification" content="1A5uFV_zNuXazJ46-572-_lLzcCTEQ77iHaSPFZd53Y" />
 <link rel="stylesheet" type="text/css" media="all" href="{{ MEDIA_URL }}css/news.css" />
-<link rel="alternate" type="application/rss+xml" title="Widelands News" href="/feeds/news/" />
+<link rel="alternate" type="application/rss+xml" title="Widelands News" href="news/feed/" />
 
 {{ block.super}}{% endblock %}
 {% block content %}

=== modified file 'templates/pybb/feeds/posts_description.html'
--- templates/pybb/feeds/posts_description.html	2014-12-01 06:32:12 +0000
+++ templates/pybb/feeds/posts_description.html	2018-09-15 07:44:50 +0000
@@ -1,2 +1,6 @@
-{{ obj.user }}:<br>
+{% if obj.user.wlprofile.deleted %}
+    {{ DELETED_USERNAME }}
+{% else %}
+    {{ obj.user }}
+{% endif %}wrote:<br>
 {{ obj.body_html|safe }}

=== modified file 'templates/pybb/feeds/topics_description.html'
--- templates/pybb/feeds/topics_description.html	2014-12-01 06:32:12 +0000
+++ templates/pybb/feeds/topics_description.html	2018-09-15 07:44:50 +0000
@@ -1,2 +1,6 @@
-{{ obj.head.user }}:<br>
+{% if obj.head.user.wlprofile.deleted %}
+    {{ DELETED_USERNAME }}
+{% else %}
+    {{ obj.head.user }}
+{% endif %}wrote:<br>
 {{ obj.head.body_html|safe }}

=== modified file 'templates/pybb/last_posts.html'
--- templates/pybb/last_posts.html	2016-10-10 19:32:14 +0000
+++ templates/pybb/last_posts.html	2018-09-15 07:44:50 +0000
@@ -12,7 +12,7 @@
 				<li>
 					{{ post.topic.forum.name }}<br />
 					<a href="{{ post.get_absolute_url }}" title="{{ post.topic.name }}">{{ post.topic.name|pybb_cut_string:30 }}</a><br />
-					by <a href="{% url 'profile_view' post.user %}">{{post.user.username}}</a> {{ post.created|minutes }} ago
+					by {{ post.user|user_link }} {{ post.created|minutes }} ago
 				</li>
 			{% endfor %}
 		</ul>

=== modified file 'templates/wiki/feeds/history_description.html'
--- templates/wiki/feeds/history_description.html	2017-12-11 08:42:55 +0000
+++ templates/wiki/feeds/history_description.html	2018-09-15 07:44:50 +0000
@@ -1,4 +1,9 @@
 {% load i18n %}
 
-{% trans "edited by user" %} {{ obj.editor.username }} {% trans "at"%} {{ obj.modified }}<br>
+{% trans "Edited by user" %} {% if obj.editor.wlprofile.deleted %}
+        {{ DELETED_USERNAME }}
+    {% else %}
+        {{ obj.editor.username }}
+    {% endif %}
+    {% trans "at"%} {{ obj.modified }}<br>
 {{ obj.comment }}

=== modified file 'templates/wiki/recentchanges.html'
--- templates/wiki/recentchanges.html	2017-08-16 21:13:43 +0000
+++ templates/wiki/recentchanges.html	2018-09-15 07:44:50 +0000
@@ -3,6 +3,7 @@
 {% load humanize i18n %}
 {% load pagination_tags %}
 {% load custom_date %}
+{% load wlprofile_extras %}
 
 {% block title %}
 {% trans "Recent Changes" %} - {{ block.super }}
@@ -42,7 +43,7 @@
 			<a href="{% url 'wiki_article' change.article.title %}">{{ change.article.title }}</a>
 		</td>
 		<td>{{ change.modified|custom_date:user }}</td>
-		<td>{{ change.editor }}</td>
+		<td>{{ change.editor|user_link }}</td>
 		<td>{{ change.comment }}</td>
 	</tr>
 {% endfor %}

=== added file 'templates/wlprofile/delete_me.html'
--- templates/wlprofile/delete_me.html	1970-01-01 00:00:00 +0000
+++ templates/wlprofile/delete_me.html	2018-09-15 07:44:50 +0000
@@ -0,0 +1,39 @@
+{% extends "wlprofile/base.html" %}
+
+{% load i18n %}
+{% load wlprofile_extras %}
+
+{% block title %}
+{% trans "Delete me" %} - {{ block.super }}
+{% endblock %}
+
+{% block content %}
+<h1>{% trans "Delete your account" %}</h1>
+
+<div class="blogEntry">
+	<h3>Hi {{ user }},</h3>
+	<p>we are sorry that you want to leave our community <img src="/wlmedia/img/smileys/face-sad.png" alt="Sad smiley"></p>
+	<h3>What deleting yourself means:</h3>
+	<ul>
+		<li>Your account will be deactivated. This means:
+			<ul>
+				<li>You will immediately logged out and can't log in anymore.</li>
+				<li>The Username "{{ user }}" is preserved and can't be used anymore.</li>
+			</ul>
+		</li>
+		<li>Your <a href="{% url 'profile_edit' %}">profile</a> will be deleted and your currently used image of the avatar will be deleted.</li>
+		<li>All your <a href="{% url 'messages_inbox' %}">private messages</a> will be moved into the <a href="{% url 'messages_trash' %}">trash</a>.
+		They stay there until the sender or recipient has also deleted them.</li>
+		<li>All your <a href="{% url 'notification_notices' %}">subscriptions</a> will be removed.</li>
+		<li>Your email address will be anonymized, so you will not receive any notification mails anymore.</li>
+		<li>Everything you posted (forum posts, comments and uploaded maps) will <b>not</b> be deleted. Instead of your users name the string "{{ DELETED_USERNAME }}" will be shown.</li>
+		<li>Your online gaming password will be reset.</li>
+		<li>All dates given in the <a href="{% url 'scheduling_scheduling' %}">Playtime scheduler</a> will be deleted</li>
+	</ul>
+	<p class="errormessage"><b>This step can't be undone!</b></p>
+	<p>
+		<button type="button" onclick="location.href='{% url 'do_delete' %}';" style="font-weight: normal;">I am sure, please delete me</button>
+		<button type="button" onclick="location.href='{% url 'profile_edit' %}';">Cancel</button>
+	</p>
+</div>
+{% endblock %}

=== modified file 'templates/wlprofile/edit_profile.html'
--- templates/wlprofile/edit_profile.html	2016-06-26 09:04:15 +0000
+++ templates/wlprofile/edit_profile.html	2018-09-15 07:44:50 +0000
@@ -41,17 +41,12 @@
 		<input type="submit" value="{% trans "Save" %}" />
 		{% csrf_token %}
 	</form>
-	<br />
-	<br />
-	<p>
-		<a href="{% url 'auth_password_change' %}">Change website password</a>
-		<br />
-		You will be redirected to an encrypted connection. The website password is <strong>not</strong> transmitted in cleartext.
-	</p>
-	<p>
-		<a href="{% url 'wlggz_changepw' %}">Change online gaming password</a>
-		<br />
-		<strong class="errormessage">WARNING: The online gaming password is transmitted in cleartext. Do not use your website password!</strong>
-	</p>
+
+	<h2>Other options:</h2>
+	<ul>
+		<li><a href="{% url 'auth_password_change' %}">Change website password</a></li>
+		<li><a href="{% url 'wlggz_changepw' %}">Change online gaming password</a></li>
+		<li><a href="{% url 'delete_me' %}">Delete me</a></li>
+	</ul>
 </div>
 {% endblock %}

=== modified file 'templates/wlprofile/view_profile.html'
--- templates/wlprofile/view_profile.html	2017-07-27 06:01:32 +0000
+++ templates/wlprofile/view_profile.html	2018-09-15 07:44:50 +0000
@@ -4,7 +4,13 @@
 {% load custom_date %}
 
 {% block title %}
-{{ profile.user.username }}'s Profile - {{ block.super }}
+	{% if not profile.deleted %}
+		{{ profile.user.username }}'s
+	{% else %}
+		Deleted Users Profile
+	{% endif %}
+	- Profile
+	{{ block.super }}
 {% endblock %}
 
 {% block content %}
@@ -13,66 +19,76 @@
 		<a class="invertedColor" href="{% url 'profile_edit' %}">Edit Profile</a>
 	{% endifequal %}
 </div>
-<h1>{{ profile.user.username }}'s Profile</h1>
+<h1>{% if not profile.deleted %}
+		{{ profile.user.username }}'s
+	{% else %}
+		Deleted Users
+	{% endif %}
+	Profile
+</h1>
 
 <div class="blogEntry">
-	<table>
-		<tr>
-			<td>
-			{% if profile.avatar %}
-				<img src="{{ profile.avatar.url }}" alt="Avatar" />
-			{% endif %}
-			</td>
-			<td>
-			{% ifnotequal user profile.user %}
-				<button onclick="window.location.href='{% url 'messages_compose_to' profile.user %}';">
-					<img src="{{ MEDIA_URL }}forum/img/send_pm.png" alt ="" class="middle" />
-					<span class="middle">{% trans "Send PM" %}</span>
-				</button>
-			{% endifnotequal %}
-			</td>
-		</tr>
-		<tr>
-			<td class="grey">Joined:</td>
-			<td>{{ profile.user.date_joined|custom_date:user|title }}</td>
-		</tr>
-		<tr>
-			<td class="grey">Forum Posts:</td>
-			<td>{{ profile.post_count }}</td>
-		</tr>
-		<tr>
-			<td class="grey">Website:</td>
-			<td><a href="{{profile.site}}">{{ profile.site }}</a></td>
-		</tr>
-		<tr>
-			<td class="grey">Location:</td>
-			<td>{{ profile.location }}</td>
-		</tr>
-		<tr>
-			<td class="grey">Jabber:</td>
-			<td>{{ profile.jabber }}</td>
-		</tr>
-		<tr>
-			<td class="grey">ICQ:</td>
-			<td>{{ profile.icq }}</td>
-		</tr>
-		<tr>
-			<td class="grey">MSN:</td>
-			<td>{{ profile.msn }}</td>
-		</tr>
-		<tr>
-			<td class="grey">AIM:</td>
-			<td>{{ profile.aim }}</td>
-		</tr>
-		<tr>
-			<td class="grey">Yahoo:</td>
-			<td>{{ profile.yahoo }}</td>
-		</tr>
-		<tr>
-			<td class="grey">Signature:</td>
-			<td>{{ profile.signature|urlize|linebreaks }}</td>
-		</tr>
-	</table>
+	{% if not profile.deleted %}
+		<table>
+			<tr>
+				<td>
+				{% if profile.avatar %}
+					<img src="{{ profile.avatar.url }}" alt="Avatar" />
+				{% endif %}
+				</td>
+				<td>
+				{% ifnotequal user profile.user %}
+					<button onclick="window.location.href='{% url 'messages_compose_to' profile.user %}';">
+						<img src="{{ MEDIA_URL }}forum/img/send_pm.png" alt ="" class="middle" />
+						<span class="middle">{% trans "Send PM" %}</span>
+					</button>
+				{% endifnotequal %}
+				</td>
+			</tr>
+			<tr>
+				<td class="grey">Joined:</td>
+				<td>{{ profile.user.date_joined|custom_date:user|title }}</td>
+			</tr>
+			<tr>
+				<td class="grey">Forum Posts:</td>
+				<td>{{ profile.post_count }}</td>
+			</tr>
+			<tr>
+				<td class="grey">Website:</td>
+				<td><a href="{{profile.site}}">{{ profile.site }}</a></td>
+			</tr>
+			<tr>
+				<td class="grey">Location:</td>
+				<td>{{ profile.location }}</td>
+			</tr>
+			<tr>
+				<td class="grey">Jabber:</td>
+				<td>{{ profile.jabber }}</td>
+			</tr>
+			<tr>
+				<td class="grey">ICQ:</td>
+				<td>{{ profile.icq }}</td>
+			</tr>
+			<tr>
+				<td class="grey">MSN:</td>
+				<td>{{ profile.msn }}</td>
+			</tr>
+			<tr>
+				<td class="grey">AIM:</td>
+				<td>{{ profile.aim }}</td>
+			</tr>
+			<tr>
+				<td class="grey">Yahoo:</td>
+				<td>{{ profile.yahoo }}</td>
+			</tr>
+			<tr>
+				<td class="grey">Signature:</td>
+				<td>{{ profile.signature|urlize|linebreaks }}</td>
+			</tr>
+		</table>
+	{% else %}
+		<p>This user has deleted his data ...</p>
+	{% endif %}
 </div>
 
 {% endblock %}

=== modified file 'urls.py'
--- urls.py	2018-05-24 19:17:36 +0000
+++ urls.py	2018-09-15 07:44:50 +0000
@@ -4,7 +4,6 @@
 from django.contrib import admin
 admin.autodiscover()
 
-from news.feeds import NewsPostsFeed
 from django.views.generic.base import RedirectView
 from django.views.generic import TemplateView
 from django.contrib.syndication.views import Feed
@@ -28,9 +27,6 @@
     url(r'^accounts/', include('registration.backends.hmac.urls')),
     url('^', include('django.contrib.auth.urls')),
 
-    # Feed for news
-    url(r'^feeds/news/$', NewsPostsFeed()),
-
     # Formerly 3rd party
     url(r'^notification/', include('notification.urls')),
     

=== modified file 'wiki/feeds.py'
--- wiki/feeds.py	2016-12-13 18:28:51 +0000
+++ wiki/feeds.py	2018-09-15 07:44:50 +0000
@@ -19,19 +19,12 @@
 
     def item_pubdate(self, item):
         """Return the item's pubdate.
-
+    
         It's this modified date
-
+    
         """
         return item.modified
 
-    def item_author_name(self, item):
-        """Takes the object returned by items(), and returns the feeds's
-        auhor's name as a Python string."""
-        if item.is_anonymous_change():
-            return 'Anonymous'
-        return item.editor.username
-
 # Validated through http://validator.w3.org/feed/
 
 
@@ -84,10 +77,3 @@
 
     def item_updateddate(self, item):
         return item.modified
-
-    def item_author_name(self, item):
-        """Takes the object returned by items(), and returns the feeds's
-        auhor's name as a Python string."""
-        if item.is_anonymous_change():
-            return 'Anonymous'
-        return item.editor.username

=== added file 'wlprofile/context_processors.py'
--- wlprofile/context_processors.py	1970-01-01 00:00:00 +0000
+++ wlprofile/context_processors.py	2018-09-15 07:44:50 +0000
@@ -0,0 +1,6 @@
+from django.conf import settings
+
+
+def deleted_user_data(request):
+    context = {'DELETED_USERNAME': settings.DELETED_USERNAME}
+    return context

=== added file 'wlprofile/migrations/0002_profile_deleted.py'
--- wlprofile/migrations/0002_profile_deleted.py	1970-01-01 00:00:00 +0000
+++ wlprofile/migrations/0002_profile_deleted.py	2018-09-15 07:44:50 +0000
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.12 on 2018-09-13 21:23
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('wlprofile', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='profile',
+            name='deleted',
+            field=models.BooleanField(default=False),
+        ),
+    ]

=== modified file 'wlprofile/models.py'
--- wlprofile/models.py	2016-12-13 18:28:51 +0000
+++ wlprofile/models.py	2018-09-15 07:44:50 +0000
@@ -48,6 +48,7 @@
                                 upload_to='wlprofile/avatars/', width=settings.AVATAR_WIDTH, height=settings.AVATAR_HEIGHT)
     show_signatures = models.BooleanField(
         _('Show signatures'), blank=True, default=True)
+    deleted = models.BooleanField(default=False)
 
     class Meta:
         verbose_name = _('Profile')

=== modified file 'wlprofile/templatetags/wlprofile_extras.py'
--- wlprofile/templatetags/wlprofile_extras.py	2018-04-08 14:40:17 +0000
+++ wlprofile/templatetags/wlprofile_extras.py	2018-09-15 07:44:50 +0000
@@ -12,12 +12,19 @@
 from django import template
 from django.urls import reverse
 from django.utils.safestring import mark_safe
+from django.contrib.auth.models import User
+from django.conf import settings
 
 register = template.Library()
 
 
 @register.filter
 def user_link(user):
-    data = u'<a href="%s">%s</a>' % (
-        reverse('profile_view', args=[user.username]), user.username)
+
+    if user.is_authenticated and user.wlprofile.deleted:
+        # Check for is_authenticated is needed for threadedcomments reply_to.js
+        return mark_safe('<span title="This user has left our community">{}</span>'.format(settings.DELETED_USERNAME))
+    else:
+        data = u'<a href="%s">%s</a>' % (
+            reverse('profile_view', args=[user.username]), user.username)
     return mark_safe(data)

=== modified file 'wlprofile/urls.py'
--- wlprofile/urls.py	2016-12-13 18:28:51 +0000
+++ wlprofile/urls.py	2018-09-15 07:44:50 +0000
@@ -14,6 +14,8 @@
 
 urlpatterns = [
     url(r'^edit/$', views.edit, name='profile_edit'),
+    url(r'^delete/$', views.delete_me, name='delete_me'),
+    url(r'^do_delete/$', views.do_delete, name='do_delete'),
     url(r'^(?P<user>.*)/$', views.view, name='profile_view'),
     url(r'^$', views.view, name='profile_view'),
 ]

=== modified file 'wlprofile/views.py'
--- wlprofile/views.py	2018-05-30 16:59:16 +0000
+++ wlprofile/views.py	2018-09-15 07:44:50 +0000
@@ -4,14 +4,102 @@
 from django.urls import reverse
 from django.contrib.auth.decorators import login_required
 from django.contrib.auth.models import User
-from django.shortcuts import render
+from django.shortcuts import render, redirect
 from django.http import HttpResponseRedirect
 from django.shortcuts import get_object_or_404
+from django.conf import settings
 
 from forms import EditProfileForm
-import settings
-
-# Settings
+
+
+@login_required
+def delete_me(request):
+    """Show a page to inform the user what deleting means."""
+
+    context = {
+        'user': request.user
+    }
+    return render(request, 'wlprofile/delete_me.html',
+                  context)
+
+
+@login_required
+def do_delete(request):
+    """Delete user specific data.
+
+    We can't really delete a user who has posted some valid posts (no spam) because otherwise
+    we have foreign keys constraints in the database. All we can do is to do some cleanup and
+    anonymization.
+    """
+
+    from django.contrib.auth import logout
+    from wlprofile.models import Profile
+    from notification.models import NoticeSetting
+
+    user = get_object_or_404(User, username=request.user)
+
+    # Log the user out. We do this as early as possible but after
+    # we get the User object.
+    logout(request)
+
+    # Clean possible Playtime availabilities
+    from wlscheduling.models import Availabilities
+    try:
+        events = Availabilities.objects.filter(user=user)
+        for event in events:
+            event.delete()
+    except:
+        pass
+            
+    # Clean the Online gaming password
+    from wlggz.models import GGZAuth
+    try:
+        ggz_user = GGZAuth.objects.get(user=user)
+        ggz_user.delete()
+    except:
+        pass
+
+    # Clean the profile
+    profile = user.wlprofile
+    upload_to = Profile._meta.get_field('avatar').upload_to
+    
+    if upload_to in profile.avatar.name:
+        # Delete the avatar file
+        profile.avatar.delete()
+    
+    # Delete the profile and recreate it to get a clean profile page
+    # We create a new one to have the anymous.png as avatar
+    profile.delete()
+    profile = Profile(user=user, deleted=True)
+    profile.save()
+
+    # Deactivate all subscriptions
+    notice_settings = NoticeSetting.objects.filter(user=user)
+    for setting in notice_settings:
+        setting.send = False
+        setting.save()
+
+    # Put all PMs in the trash of the user. They stay as long in the trash until the sender or recipient
+    # has also put the message in the trash.
+    from django_messages.models import Message
+    from datetime import datetime
+    messages = Message.objects.inbox_for(user)
+    for message in messages:
+        message.recipient_deleted_at = datetime.now()
+        message.save()
+    messages = Message.objects.outbox_for(user)
+    for message in messages:
+        message.sender_deleted_at = datetime.now()
+        message.save()
+
+    # Do some settings in django.auth.User
+    user.is_active = False
+    user.is_staff = False
+    user.is_superuser = False
+    user.email = settings.DELETED_MAIL_ADDRESS
+    user.save()
+
+    return redirect('mainpage')
 
 
 @login_required


Follow ups