Skip to content

Commit

Permalink
Add ability to delete account and PSF membership status
Browse files Browse the repository at this point in the history
Fixes #757
  • Loading branch information
berkerpeksag committed Sep 10, 2017
1 parent 2f23ff7 commit dd23e6c
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 11 deletions.
16 changes: 15 additions & 1 deletion static/sass/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,20 @@
}
}

%button-style-red {
@extend %button-style;
@include vertical-gradient( lighten($red, 10%), $red );
@include pe-border( $color-top: darken($red, 20%), $color-right: darken($red, 20%), $color-bottom: darken($red, 20%), $color-left: darken($red, 20%) );

color: $white !important;

&:hover, &:active {
background-color: inherit;
color: $white !important;
@include vertical-gradient( lighten($red, 20%), $red );
}
}

%button-style-blue {
@extend %button-style;
color: $grey-lightest !important;
Expand Down Expand Up @@ -512,4 +526,4 @@
@include transform( translate3d( $menuwidth, 0, 0 ) );
}
}
}
}
46 changes: 40 additions & 6 deletions static/sass/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@
clear: both; }

/* Buttons */
.psf-widget .button, .python-needs-you-widget .button, .header-banner .button, .header-banner a.button, button[type=submit], .search-button, #dive-into-python .flex-control-paging a, .text form button, .text form input[type=submit],
.psf-widget .button, .python-needs-you-widget .button, .header-banner .button, .header-banner a.button, form.deletion-form button[type="submit"], button[type=submit], .search-button, #dive-into-python .flex-control-paging a, .text form button, .text form input[type=submit],
.sidebar-widget form button,
.sidebar-widget form input[type=submit], input[type=submit], input[type=reset], button, a.button, .button {
cursor: pointer;
Expand Down Expand Up @@ -211,11 +211,11 @@
-webkit-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.05), inset 0 0 5px rgba(255, 255, 255, 0.5);
-moz-box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.05), inset 0 0 5px rgba(255, 255, 255, 0.5);
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.05), inset 0 0 5px rgba(255, 255, 255, 0.5); }
.header-banner .button:hover, .header-banner a.button:hover, .search-button:hover, #dive-into-python .flex-control-paging a:hover, .text form button:hover, .text form input[type=submit]:hover,
.header-banner .button:hover, .header-banner a.button:hover, form.deletion-form button[type="submit"]:hover, .search-button:hover, #dive-into-python .flex-control-paging a:hover, .text form button:hover, .text form input[type=submit]:hover,
.sidebar-widget form button:hover,
.sidebar-widget form input[type=submit]:hover, input[type=submit]:hover, input[type=reset]:hover, button:hover, .button:hover, .header-banner .button:focus, .header-banner a.button:focus, .search-button:focus, #dive-into-python .flex-control-paging a:focus, .text form button:focus, .text form input[type=submit]:focus,
.sidebar-widget form input[type=submit]:hover, input[type=submit]:hover, input[type=reset]:hover, button:hover, .button:hover, .header-banner .button:focus, .header-banner a.button:focus, form.deletion-form button[type="submit"]:focus, .search-button:focus, #dive-into-python .flex-control-paging a:focus, .text form button:focus, .text form input[type=submit]:focus,
.sidebar-widget form button:focus,
.sidebar-widget form input[type=submit]:focus, input[type=submit]:focus, input[type=reset]:focus, button:focus, .button:focus, .header-banner .button:active, .header-banner a.button:active, .search-button:active, #dive-into-python .flex-control-paging a:active, .text form button:active, .text form input[type=submit]:active,
.sidebar-widget form input[type=submit]:focus, input[type=submit]:focus, input[type=reset]:focus, button:focus, .button:focus, .header-banner .button:active, .header-banner a.button:active, form.deletion-form button[type="submit"]:active, .search-button:active, #dive-into-python .flex-control-paging a:active, .text form button:active, .text form input[type=submit]:active,
.sidebar-widget form button:active,
.sidebar-widget form input[type=submit]:active, input[type=submit]:active, input[type=reset]:active, button:active, .button:active {
color: #1a1a1a !important;
Expand Down Expand Up @@ -252,6 +252,32 @@
background-image: -o-linear-gradient(#ffeba9 10%, #ffd343 90%);
background-image: linear-gradient(#ffeba9 10%, #ffd343 90%); }

form.deletion-form button[type="submit"] {
background-color: #b55863;
*zoom: 1;
filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFC57B84', endColorstr='#FFB55863');
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #c57b84), color-stop(90%, #b55863));
background-image: -webkit-linear-gradient(#c57b84 10%, #b55863 90%);
background-image: -moz-linear-gradient(#c57b84 10%, #b55863 90%);
background-image: -o-linear-gradient(#c57b84 10%, #b55863 90%);
background-image: linear-gradient(#c57b84 10%, #b55863 90%);
border-top: 1px solid #74333b;
border-right: 1px solid #74333b;
border-bottom: 1px solid #74333b;
border-left: 1px solid #74333b;
color: white !important; }
form.deletion-form button[type="submit"]:hover, form.deletion-form button[type="submit"]:active {
background-color: inherit;
color: white !important;
background-color: #b55863;
*zoom: 1;
filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFD49FA5', endColorstr='#FFB55863');
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(10%, #d49fa5), color-stop(90%, #b55863));
background-image: -webkit-linear-gradient(#d49fa5 10%, #b55863 90%);
background-image: -moz-linear-gradient(#d49fa5 10%, #b55863 90%);
background-image: -o-linear-gradient(#d49fa5 10%, #b55863 90%);
background-image: linear-gradient(#d49fa5 10%, #b55863 90%); }

button[type=submit], .search-button, #dive-into-python .flex-control-paging a, .text form button, .text form input[type=submit],
.sidebar-widget form button,
.sidebar-widget form input[type=submit] {
Expand Down Expand Up @@ -2983,14 +3009,22 @@ p.admonition-title {
p.admonition-title:after {
content: ":"; }

div.warning {
div.warning, form.deletion-form {
background-color: #ffe4e4;
border: 1px solid #ff6666;
padding: 0.3125em 0.625em;
margin-bottom: 1.5625em; }
div.warning > p {
div.warning > p, form.deletion-form > p {
margin-bottom: 0; }

form.deletion-form {
border-radius: 0.625em;
padding: 0.9375em; }
form.deletion-form button[type="submit"] {
margin-top: 0.625em; }
form.deletion-form label {
color: #b55863; }

div.sidebar {
margin: 0 0 0.5em 1em;
border: 1px solid #ddddbb;
Expand Down
15 changes: 15 additions & 0 deletions static/sass/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2215,6 +2215,21 @@ div.warning {
}
}

form.deletion-form {
@extend div.warning;

border-radius: px2em( 10px );
padding: px2em( 15px );

button[type="submit"] {
@extend %button-style-red;
margin-top: px2em( 10px );
}
label {
color: $red;
}
}

div.sidebar {
margin: 0 0 0.5em 1em;
border: 1px solid #ddb;
Expand Down
15 changes: 15 additions & 0 deletions templates/users/membership_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,21 @@ <h1 class="default-title">Register to become a PSF Member</h1>
</p>
</form>

{% if request.user.has_membership %}
<form action="{% url 'users:user_membership_delete' user.username %}"
method="post"
class="deletion-form"
onsubmit="return confirm('Are you sure?');">
{% csrf_token %}
<p>
<label for="id_delete_membership">
You can delete your PSF membership if you don't want to get emails from the PSF.
</label>
<button type="submit" id="id_delete_membership">Delete your PSF membership</button>
</p>
</form>
{% endif %}

</article>

{% endblock user_content %}
13 changes: 13 additions & 0 deletions templates/users/user_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ <h1 class="default-title">Edit information for: <strong>{% firstof user.get_full
<button type="submit" name="Submit">Save Profile</button>
</p>
</form>

<form action="{% url 'users:user_delete' user.username %}"
method="post"
class="deletion-form"
onsubmit="return confirm('Are you sure? There is no way to get back your account.');">
{% csrf_token %}
<p>
<label for="id_delete_account">
Note that once you delete your account, there is no going back.
</label>
<button type="submit" id="id_delete_account">Delete this account</button>
</p>
</form>
</article>

{% endblock user_content %}
72 changes: 72 additions & 0 deletions users/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.test import TestCase

from users.factories import UserFactory
from users.models import Membership

from ..factories import MembershipFactory

Expand Down Expand Up @@ -290,3 +291,74 @@ def test_user_list(self):
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.context['user_list']), 1)

def test_user_delete_needs_to_be_logged_in(self):
url = reverse('users:user_delete', kwargs={'slug': self.user.username})
response = self.client.delete(url)
self.assertRedirects(
response,
'{}?next={}'.format(reverse('account_login'), url)
)

def test_user_delete_invalid_request_method(self):
url = reverse('users:user_delete', kwargs={'slug': self.user.username})
self.client.login(username=self.user.username, password='password')
response = self.client.get(url)
self.assertEqual(response.status_code, 405)

def test_user_delete_different_user(self):
url = reverse('users:user_delete', kwargs={'slug': self.user.username})
self.client.login(username=self.user2.username, password='password')
response = self.client.delete(url)
self.assertEqual(response.status_code, 403)

def test_user_delete(self):
url = reverse('users:user_delete', kwargs={'slug': self.user.username})
self.client.login(username=self.user.username, password='password')
response = self.client.delete(url)
self.assertRedirects(response, reverse('home'))
self.assertRaises(User.DoesNotExist, User.objects.get, username=self.user.username)
self.assertRaises(Membership.DoesNotExist, Membership.objects.get, creator=self.user)

def test_membership_delete_needs_to_be_logged_in(self):
url = reverse('users:user_membership_delete', kwargs={'slug': self.user2.username})
response = self.client.delete(url)
self.assertRedirects(
response,
'{}?next={}'.format(reverse('account_login'), url)
)

def test_membership_delete_invalid_request_method(self):
url = reverse('users:user_membership_delete', kwargs={'slug': self.user2.username})
self.client.login(username=self.user2.username, password='password')
response = self.client.get(url)
self.assertEqual(response.status_code, 405)

def test_membership_delete_different_user_membership(self):
user = UserFactory()
self.assertTrue(user.has_membership)
url = reverse('users:user_membership_delete', kwargs={'slug': user.username})
self.client.login(username=self.user2.username, password='password')
response = self.client.delete(url)
self.assertEqual(response.status_code, 403)

def test_membership_does_not_exist(self):
self.assertFalse(self.user.has_membership)
url = reverse('users:user_membership_delete', kwargs={'slug': self.user.username})
self.client.login(username=self.user.username, password='password')
response = self.client.delete(url)
self.assertEqual(response.status_code, 404)

def test_membership_delete(self):
self.assertTrue(self.user2.has_membership)
url = reverse('users:user_membership_delete', kwargs={'slug': self.user2.username})
self.client.login(username=self.user2.username, password='password')
response = self.client.delete(url)
self.assertRedirects(
response,
reverse('users:user_detail', kwargs={'slug': self.user2.username})
)
# TODO: We can't use 'self.user2.refresh_from_db()' because
# of https://code.djangoproject.com/ticket/27846.
with self.assertRaises(Membership.DoesNotExist):
Membership.objects.get(pk=self.user2.membership.pk)
2 changes: 2 additions & 0 deletions users/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
}),
url(r'^membership/$', views.MembershipCreate.as_view(), name='user_membership_create'),
url(r'^membership/edit/$', views.MembershipUpdate.as_view(), name='user_membership_edit'),
url(r'^membership/delete/(?P<slug>[-_\w\@\.+]+)/$', views.MembershipDeleteView.as_view(), name='user_membership_delete'),
url(r'^membership/thanks/$', views.MembershipThanks.as_view(), name='user_membership_thanks'),
url(r'^membership/affirm/$', views.MembershipVoteAffirm.as_view(), name='membership_affirm_vote'),
url(r'^membership/affirm/done/$', views.MembershipVoteAffirmDone.as_view(), name='membership_affirm_vote_done'),
url(r'^(?P<slug>[-_\w\@\.+]+)/delete/$', views.UserDeleteView.as_view(), name='user_delete'),
url(r'^(?P<slug>[-_\w\@\.+]+)/$', views.UserDetail.as_view(), name='user_detail'),
]
35 changes: 31 additions & 4 deletions users/views.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from django.contrib.auth import authenticate, login
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import UserPassesTestMixin
from django.conf import settings
from django.core.mail import send_mail
from django.urls import reverse
from django.urls import reverse, reverse_lazy
from django.http import Http404
from django.shortcuts import render, redirect
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.generic import (
CreateView, DetailView, ListView, TemplateView, UpdateView
CreateView, DetailView, ListView, TemplateView, UpdateView, DeleteView,
)

from allauth.account.views import SignupView
Expand All @@ -18,9 +19,11 @@
from .forms import (
UserProfileForm, MembershipForm, MembershipUpdateForm,
)
from .models import User, Membership
from .models import Membership
from .paginators import UserPaginator

User = get_user_model()


class MembershipCreate(LoginRequiredMixin, CreateView):
model = Membership
Expand Down Expand Up @@ -137,3 +140,27 @@ class HoneypotSignupView(SignupView):
@method_decorator(check_honeypot)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)


class UserDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = User
success_url = reverse_lazy('home')
slug_field = 'username'
raise_exception = True
http_method_names = ['post', 'delete']

def test_func(self):
return self.get_object() == self.request.user


class MembershipDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Membership
slug_field = 'creator__username'
raise_exception = True
http_method_names = ['post', 'delete']

def get_success_url(self):
return reverse('users:user_detail', kwargs={'slug': self.request.user.username})

def test_func(self):
return self.get_object().creator == self.request.user

0 comments on commit dd23e6c

Please sign in to comment.