Browse Source

"Close Account" section in Settings page. Fixes #95

pull/114/merge
Pēteris Caune 8 years ago
parent
commit
e685154cc2
8 changed files with 140 additions and 7 deletions
  1. +57
    -0
      hc/accounts/tests/test_close_account.py
  2. +1
    -0
      hc/accounts/urls.py
  3. +23
    -0
      hc/accounts/views.py
  4. +8
    -0
      hc/payments/models.py
  5. +1
    -1
      hc/payments/tests/test_cancel_plan.py
  6. +1
    -6
      hc/payments/views.py
  7. +6
    -0
      static/css/settings.css
  8. +43
    -0
      templates/accounts/profile.html

+ 57
- 0
hc/accounts/tests/test_close_account.py View File

@ -0,0 +1,57 @@
from django.contrib.auth.models import User
from mock import patch
from hc.test import BaseTestCase
from hc.api.models import Check
from hc.payments.models import Subscription
class CloseAccountTestCase(BaseTestCase):
@patch("hc.payments.models.Subscription.cancel")
def test_it_works(self, mock_cancel):
Check.objects.create(user=self.alice, tags="foo a-B_1 baz@")
Subscription.objects.create(user=self.alice, subscription_id="123")
self.client.login(username="[email protected]", password="password")
r = self.client.post("/accounts/close/")
self.assertEqual(r.status_code, 302)
# Alice should be gone
alices = User.objects.filter(username="alice")
self.assertFalse(alices.exists())
# Alice should be gone
alices = User.objects.filter(username="alice")
self.assertFalse(alices.exists())
# Bob's current team should be updated to self
self.bobs_profile.refresh_from_db()
self.assertEqual(self.bobs_profile.current_team, self.bobs_profile)
# Check should be gone
self.assertFalse(Check.objects.exists())
# Subscription should have been canceled
self.assertTrue(mock_cancel.called)
# Subscription should be gone
self.assertFalse(Subscription.objects.exists())
def test_partner_removal_works(self):
self.client.login(username="[email protected]", password="password")
r = self.client.post("/accounts/close/")
self.assertEqual(r.status_code, 302)
# Alice should be still present
self.alice.refresh_from_db()
self.profile.refresh_from_db()
# Bob should be gone
bobs = User.objects.filter(username="bob")
self.assertFalse(bobs.exists())
def test_it_rejects_get(self):
self.client.login(username="[email protected]", password="password")
r = self.client.get("/accounts/close/")
self.assertEqual(r.status_code, 405)

+ 1
- 0
hc/accounts/urls.py View File

@ -16,6 +16,7 @@ urlpatterns = [
url(r'^profile/$', views.profile, name="hc-profile"), url(r'^profile/$', views.profile, name="hc-profile"),
url(r'^profile/notifications/$', views.notifications, name="hc-notifications"), url(r'^profile/notifications/$', views.notifications, name="hc-notifications"),
url(r'^profile/badges/$', views.badges, name="hc-badges"), url(r'^profile/badges/$', views.badges, name="hc-badges"),
url(r'^close/$', views.close, name="hc-close"),
url(r'^unsubscribe_reports/([\w-]+)/$', url(r'^unsubscribe_reports/([\w-]+)/$',
views.unsubscribe_reports, name="hc-unsubscribe-reports"), views.unsubscribe_reports, name="hc-unsubscribe-reports"),


+ 23
- 0
hc/accounts/views.py View File

@ -12,12 +12,14 @@ from django.contrib.auth.models import User
from django.core import signing from django.core import signing
from django.http import HttpResponseForbidden, HttpResponseBadRequest from django.http import HttpResponseForbidden, HttpResponseBadRequest
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.views.decorators.http import require_POST
from hc.accounts.forms import (EmailPasswordForm, InviteTeamMemberForm, from hc.accounts.forms import (EmailPasswordForm, InviteTeamMemberForm,
RemoveTeamMemberForm, ReportSettingsForm, RemoveTeamMemberForm, ReportSettingsForm,
SetPasswordForm, TeamNameForm) SetPasswordForm, TeamNameForm)
from hc.accounts.models import Profile, Member from hc.accounts.models import Profile, Member
from hc.api.models import Channel, Check from hc.api.models import Channel, Check
from hc.lib.badges import get_badge_url from hc.lib.badges import get_badge_url
from hc.payments.models import Subscription
def _make_user(email): def _make_user(email):
@ -338,3 +340,24 @@ def switch_team(request, target_username):
request.user.profile.save() request.user.profile.save()
return redirect("hc-checks") return redirect("hc-checks")
@require_POST
@login_required
def close(request):
user = request.user
# Subscription needs to be canceled before it is deleted:
sub = Subscription.objects.filter(user=user).first()
if sub:
sub.cancel()
# Any users currently using this team need to switch to their own team:
for partner in Profile.objects.filter(current_team=user.profile):
partner.current_team = partner
partner.save()
user.delete()
request.session.flush()
return redirect("hc-index")

+ 8
- 0
hc/payments/models.py View File

@ -39,6 +39,14 @@ class Subscription(models.Model):
self._pm = braintree.PaymentMethod.find(self.payment_method_token) self._pm = braintree.PaymentMethod.find(self.payment_method_token)
return self._pm return self._pm
def cancel(self):
if self.subscription_id:
braintree.Subscription.cancel(self.subscription_id)
self.subscription_id = ""
self.plan_id = ""
self.save()
def pm_is_credit_card(self): def pm_is_credit_card(self):
return isinstance(self._get_braintree_payment_method(), return isinstance(self._get_braintree_payment_method(),
braintree.credit_card.CreditCard) braintree.credit_card.CreditCard)


+ 1
- 1
hc/payments/tests/test_cancel_plan.py View File

@ -13,7 +13,7 @@ class CancelPlanTestCase(BaseTestCase):
self.sub.plan_id = "P5" self.sub.plan_id = "P5"
self.sub.save() self.sub.save()
@patch("hc.payments.views.braintree")
@patch("hc.payments.models.braintree")
def test_it_works(self, mock_braintree): def test_it_works(self, mock_braintree):
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")


+ 1
- 6
hc/payments/views.py View File

@ -156,12 +156,7 @@ def update_payment_method(request):
@require_POST @require_POST
def cancel_plan(request): def cancel_plan(request):
sub = Subscription.objects.get(user=request.user) sub = Subscription.objects.get(user=request.user)
braintree.Subscription.cancel(sub.subscription_id)
sub.subscription_id = ""
sub.plan_id = ""
sub.save()
sub.cancel()
return redirect("hc-pricing") return redirect("hc-pricing")


+ 6
- 0
static/css/settings.css View File

@ -17,4 +17,10 @@
.page-profile .icon-ok { .page-profile .icon-ok {
color: #5cb85c; color: #5cb85c;
}
#close-account {
margin-left: 24px;
border-color: #d43f3a;
color: #d43f3a;
} }

+ 43
- 0
templates/accounts/profile.html View File

@ -136,6 +136,22 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-body settings-block">
{% csrf_token %}
<h2>Close Account</h2>
<a
id="close-account"
href="#"
class="btn btn-default pull-right"
data-toggle="modal"
data-target="#close-account-modal">Close Account</a>
This will permanently remove your healthchecks.io account
<form action="{% url 'hc-close' %}" method="post">
</form>
</div>
</div>
</div> </div>
</div> </div>
@ -268,6 +284,33 @@
</form> </form>
</div> </div>
</div> </div>
<div id="close-account-modal" class="modal">
<div class="modal-dialog">
<form id="close-account-form" method="post" action="{% url 'hc-close' %}">
{% csrf_token %}
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="remove-check-title">Close Account</h4>
</div>
<div class="modal-body">
<p></p>
<p>You are about to permanently remove
the account <strong>{{ profile }}</strong> and all
of its associated checks and integrations.</p>
<p>Are you sure?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button
type="submit"
class="btn btn-danger">Close Account</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}


Loading…
Cancel
Save