Browse Source

"Unsubscribe" link in alert emails. Fixes #111

pull/114/head
Pēteris Caune 8 years ago
parent
commit
6a1c5dd3b7
10 changed files with 90 additions and 35 deletions
  1. +5
    -0
      hc/api/models.py
  2. +0
    -17
      hc/api/tests/test_notify.py
  3. +1
    -6
      hc/api/transports.py
  4. +38
    -0
      hc/front/tests/test_unsubscribe_email.py
  5. +3
    -3
      hc/front/tests/test_verify_email.py
  6. +2
    -0
      hc/front/urls.py
  7. +13
    -0
      hc/front/views.py
  8. +2
    -2
      templates/bad_link.html
  9. +7
    -7
      templates/emails/alert-body-html.html
  10. +19
    -0
      templates/front/unsubscribe_success.html

+ 5
- 0
hc/api/models.py View File

@ -229,6 +229,11 @@ class Channel(models.Model):
verify_link = settings.SITE_ROOT + verify_link verify_link = settings.SITE_ROOT + verify_link
emails.verify_email(self.value, {"verify_link": verify_link}) emails.verify_email(self.value, {"verify_link": verify_link})
def get_unsub_link(self):
args = [self.code, self.make_token()]
verify_link = reverse("hc-unsubscribe-alerts", args=args)
return settings.SITE_ROOT + verify_link
@property @property
def transport(self): def transport(self):
if self.kind == "email": if self.kind == "email":


+ 0
- 17
hc/api/tests/test_notify.py View File

@ -146,23 +146,6 @@ class NotifyTestCase(BaseTestCase):
self.assertEqual(n.error, "Email not verified") self.assertEqual(n.error, "Email not verified")
self.assertEqual(len(mail.outbox), 0) self.assertEqual(len(mail.outbox), 0)
@override_settings(USE_PAYMENTS=True)
def test_email_contains_upgrade_notice(self):
self._setup_data("email", "[email protected]", status="up")
self.profile.team_access_allowed = False
self.profile.save()
self.channel.notify(self.check)
n = Notification.objects.get()
self.assertEqual(n.error, "")
# Check is up, payments are enabled, and the user does not have team
# access: the email should contain upgrade note
message = mail.outbox[0]
html, _ = message.alternatives[0]
assert "/pricing/" in html
@patch("hc.api.transports.requests.request") @patch("hc.api.transports.requests.request")
def test_pd(self, mock_post): def test_pd(self, mock_post):
self._setup_data("pd", "123") self._setup_data("pd", "123")


+ 1
- 6
hc/api/transports.py View File

@ -46,16 +46,11 @@ class Email(Transport):
if not self.channel.email_verified: if not self.channel.email_verified:
return "Email not verified" return "Email not verified"
show_upgrade_note = False
if settings.USE_PAYMENTS and check.status == "up":
if not check.user.profile.team_access_allowed:
show_upgrade_note = True
ctx = { ctx = {
"check": check, "check": check,
"checks": self.checks(), "checks": self.checks(),
"now": timezone.now(), "now": timezone.now(),
"show_upgrade_note": show_upgrade_note
"unsub_link": self.channel.get_unsub_link()
} }
emails.alert(self.channel.value, ctx) emails.alert(self.channel.value, ctx)


+ 38
- 0
hc/front/tests/test_unsubscribe_email.py View File

@ -0,0 +1,38 @@
from hc.api.models import Channel
from hc.test import BaseTestCase
class UnsubscribeEmailTestCase(BaseTestCase):
def setUp(self):
super(UnsubscribeEmailTestCase, self).setUp()
self.channel = Channel(user=self.alice, kind="email")
self.channel.value = "[email protected]"
self.channel.save()
def test_it_works(self):
token = self.channel.make_token()
url = "/integrations/%s/unsub/%s/" % (self.channel.code, token)
r = self.client.get(url)
self.assertContains(r, "has been unsubscribed", status_code=200)
q = Channel.objects.filter(code=self.channel.code)
self.assertEqual(q.count(), 0)
def test_it_checks_token(self):
url = "/integrations/%s/unsub/faketoken/" % self.channel.code
r = self.client.get(url)
self.assertContains(r, "link you just used is incorrect",
status_code=200)
def test_it_checks_channel_kind(self):
self.channel.kind = "webhook"
self.channel.save()
token = self.channel.make_token()
url = "/integrations/%s/unsub/%s/" % (self.channel.code, token)
r = self.client.get(url)
self.assertEqual(r.status_code, 400)

+ 3
- 3
hc/front/tests/test_verify_email.py View File

@ -14,7 +14,7 @@ class VerifyEmailTestCase(BaseTestCase):
token = self.channel.make_token() token = self.channel.make_token()
url = "/integrations/%s/verify/%s/" % (self.channel.code, token) url = "/integrations/%s/verify/%s/" % (self.channel.code, token)
r = self.client.post(url)
r = self.client.get(url)
assert r.status_code == 200, r.status_code assert r.status_code == 200, r.status_code
channel = Channel.objects.get(code=self.channel.code) channel = Channel.objects.get(code=self.channel.code)
@ -23,7 +23,7 @@ class VerifyEmailTestCase(BaseTestCase):
def test_it_handles_bad_token(self): def test_it_handles_bad_token(self):
url = "/integrations/%s/verify/bad-token/" % self.channel.code url = "/integrations/%s/verify/bad-token/" % self.channel.code
r = self.client.post(url)
r = self.client.get(url)
assert r.status_code == 200, r.status_code assert r.status_code == 200, r.status_code
channel = Channel.objects.get(code=self.channel.code) channel = Channel.objects.get(code=self.channel.code)
@ -35,5 +35,5 @@ class VerifyEmailTestCase(BaseTestCase):
token = self.channel.make_token() token = self.channel.make_token()
url = "/integrations/%s/verify/%s/" % (code, token) url = "/integrations/%s/verify/%s/" % (code, token)
r = self.client.post(url)
r = self.client.get(url)
assert r.status_code == 404 assert r.status_code == 404

+ 2
- 0
hc/front/urls.py View File

@ -27,6 +27,8 @@ channel_urls = [
url(r'^([\w-]+)/remove/$', views.remove_channel, name="hc-remove-channel"), url(r'^([\w-]+)/remove/$', views.remove_channel, name="hc-remove-channel"),
url(r'^([\w-]+)/verify/([\w-]+)/$', views.verify_email, url(r'^([\w-]+)/verify/([\w-]+)/$', views.verify_email,
name="hc-verify-email"), name="hc-verify-email"),
url(r'^([\w-]+)/unsub/([\w-]+)/$', views.unsubscribe_email,
name="hc-unsubscribe-alerts"),
] ]
urlpatterns = [ urlpatterns = [


+ 13
- 0
hc/front/views.py View File

@ -361,6 +361,19 @@ def verify_email(request, code, token):
return render(request, "bad_link.html") return render(request, "bad_link.html")
@uuid_or_400
def unsubscribe_email(request, code, token):
channel = get_object_or_404(Channel, code=code)
if channel.make_token() != token:
return render(request, "bad_link.html")
if channel.kind != "email":
return HttpResponseBadRequest()
channel.delete()
return render(request, "front/unsubscribe_success.html")
@login_required @login_required
@uuid_or_400 @uuid_or_400
def remove_channel(request, code): def remove_channel(request, code):


+ 2
- 2
templates/bad_link.html View File

@ -4,10 +4,10 @@
<div class="row"> <div class="row">
<div class="col-sm-6 col-sm-offset-3"> <div class="col-sm-6 col-sm-offset-3">
<div class="hc-dialog"> <div class="hc-dialog">
<h1>Incorrect Verification Link</h1>
<h1>Incorrect Confirmation Link</h1>
<div class="dialog-body"> <div class="dialog-body">
<p> <p>
The verification link you just used is incorrect.
The confirmation link you just used is incorrect.
If you copy-pasted the link, please make sure you did not If you copy-pasted the link, please make sure you did not
miss any characters. miss any characters.
</p> </p>


+ 7
- 7
templates/emails/alert-body-html.html View File

@ -15,13 +15,13 @@ Here is a summary of all your checks:
{% include "emails/summary-html.html" %} {% include "emails/summary-html.html" %}
{% if show_upgrade_note %}
<strong>P.S.</strong>
Find this service useful? Support it by upgrading to
a <a href="https://healthchecks.io/pricing/">premium account</a>!
<br /><br />
{% endif %}
Thanks,<br> Thanks,<br>
The {% escaped_site_name %} Team The {% escaped_site_name %} Team
{% endblock %} {% endblock %}
{% block unsub %}
<br>
<a href="{{ unsub_link }}" target="_blank" style="color: #666666; text-decoration: underline;">
Unsubscribe
</a>
{% endblock %}

+ 19
- 0
templates/front/unsubscribe_success.html View File

@ -0,0 +1,19 @@
{% extends "base.html" %}
{% load hc_extras %}
{% block content %}
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<div class="hc-dialog">
<h1>Unsubscribed</h1>
<div class="dialog-body">
<p>
Your email address has been unsubscribed from
{% site_name %} notifications.
</p>
</div>
</div>
</div>
</div>
{% endblock %}

Loading…
Cancel
Save