From a0a16237181d8bb03922a9e9a736cce5cee59dd4 Mon Sep 17 00:00:00 2001 From: DerGut Date: Mon, 30 Apr 2018 17:36:27 +0200 Subject: [PATCH] Adds the unsubscribe feature --- creator/management/commands/send_mails.py | 22 ++++++++++--- .../templates/creator/subscription_email.html | 3 +- ...cribe.html => subscription_subscribe.html} | 0 .../creator/subscription_unsubscribe.html | 11 +++++++ creator/urls.py | 5 +-- creator/views.py | 32 +++++++++++++++++-- 6 files changed, 64 insertions(+), 9 deletions(-) rename creator/templates/creator/{subscribe.html => subscription_subscribe.html} (100%) create mode 100644 creator/templates/creator/subscription_unsubscribe.html diff --git a/creator/management/commands/send_mails.py b/creator/management/commands/send_mails.py index eb1ba9d..e223540 100644 --- a/creator/management/commands/send_mails.py +++ b/creator/management/commands/send_mails.py @@ -2,6 +2,7 @@ from django.core import mail from django.core.management.base import BaseCommand +from django.core.signing import Signer from django.template.loader import render_to_string from easy_pdf.rendering import render_to_pdf @@ -15,6 +16,11 @@ class Command(BaseCommand): help = 'Looks up all mail subscriptions in the DB, generates the corresponding PDF and sends off an email' + def __init__(self): + super().__init__() + + self.signer = Signer() + def handle(self, *args, **options): logger.info("Starting to send todays email subscriptions") @@ -45,20 +51,28 @@ def handle(self, *args, **options): else: logger.info('No subscriptions found') - @classmethod - def new_email(cls, subscription, pdf): + def new_email(self, subscription, pdf): """Send email with pdf as attachment""" subject = "StundenzettelCreator - Your monthly timesheet" + unsubscribe_hash = self.signer.sign(subscription.pk) + text_content = """ Hey {first_name}, here is your monthly timesheet from StundenzettelCreator. + You can always unsubscribe with this link: http://stundenzettel-creator/unsubscribe/{hash} + Bye - """.format(first_name=subscription.first_name) - html_content = render_to_string('creator/subscription_email.html', {'context': subscription}) + """.format(first_name=subscription.first_name, hash=unsubscribe_hash) + html_content = render_to_string( + 'creator/subscription_email.html', + context={ + 'subscription': subscription, + 'hash': unsubscribe_hash + }) from_email = "subscription@stundenzettel-creator.xyz" recipient_list = [subscription.email] diff --git a/creator/templates/creator/subscription_email.html b/creator/templates/creator/subscription_email.html index 6a7b1d5..84cd55c 100644 --- a/creator/templates/creator/subscription_email.html +++ b/creator/templates/creator/subscription_email.html @@ -7,7 +7,8 @@ Hey {{ first_name }}, -

here is your monthly timesheet from StundenzettelCreator.

+

here is your monthly timesheet from StundenzettelCreator. + You can always unsubscribe by visiting unsubscribe

Bye

diff --git a/creator/templates/creator/subscribe.html b/creator/templates/creator/subscription_subscribe.html similarity index 100% rename from creator/templates/creator/subscribe.html rename to creator/templates/creator/subscription_subscribe.html diff --git a/creator/templates/creator/subscription_unsubscribe.html b/creator/templates/creator/subscription_unsubscribe.html new file mode 100644 index 0000000..327efdc --- /dev/null +++ b/creator/templates/creator/subscription_unsubscribe.html @@ -0,0 +1,11 @@ +{% extends "creator/base.html" %} + +{% block container %} + {% if status == 'success' %} +

Success

+

You successfully unsubscribed.

+ {% else %} +

We are sorry, something went wrong.

+

Please try visiting {% url 'unsubscribe' %}

+ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/creator/urls.py b/creator/urls.py index 5eefc59..0c235df 100644 --- a/creator/urls.py +++ b/creator/urls.py @@ -1,10 +1,11 @@ from django.conf.urls import url -from creator.views import DetailsFormView, ResultPdfView, SubscriptionFormView, SuccessView +from creator.views import DetailsFormView, ResultPdfView, SubscriptionFormView, SuccessView, UnsubscribeView urlpatterns = [ url(r'^$', DetailsFormView.as_view(), name='index'), url(r'^result/', ResultPdfView.as_view(), name='result'), url(r'^subscribe/', SubscriptionFormView.as_view(), name='subscribe'), - url(r'^success/', SuccessView.as_view(), name='success') + url(r'^success/', SuccessView.as_view(), name='success'), + url(r'^unsubscribe/(?P[*]{32})/', UnsubscribeView.as_view(), name='unsubscribe') ] diff --git a/creator/views.py b/creator/views.py index 2e0a94f..24b0a57 100644 --- a/creator/views.py +++ b/creator/views.py @@ -1,9 +1,11 @@ import calendar import datetime +import logging import random import numpy as np -from django.shortcuts import get_object_or_404 +from django.core import signing +from django.shortcuts import get_object_or_404, render from django.views.generic import DetailView from django.views.generic.edit import FormView from easy_pdf.views import PDFTemplateView @@ -12,6 +14,8 @@ from creator.forms import DetailsForm, SubscriptionForm from creator.models import Subscription +logger = logging.getLogger(__name__) + def format_timedelta(td): """Format datetime.timedelta as "hh:mm".""" @@ -145,7 +149,7 @@ def form_valid(self, form): class SubscriptionFormView(FormView): - template_name = 'creator/subscribe.html' + template_name = 'creator/subscription_subscribe.html' form_class = SubscriptionForm success_url = '/success/' @@ -174,6 +178,30 @@ def get_object(self, queryset=None): ) +def unsubscribe(request, hash): + signer = signing.Signer() + try: + id = signer.unsign(hash) + Subscription.objects.get(pk=id).delete() + context = { + 'status': 'success', + } + except signing.BadSignature: + context = { + 'status': 'failed', + 'reason': 'This link is not working properly.' + } + logger.error('Tampering with unsubscribe link detected') + except: + context = { + 'status': 'failed', + 'reason': 'unknown' + } + + + return render(request, 'creator/subscription_unsubscribe.html', context=context) + + class ResultPdfView(PDFTemplateView): template_name = 'creator/timesheet.html'