From 0ccf2f7b61ea9889b1c03e51dfc847ca16cd98e7 Mon Sep 17 00:00:00 2001 From: luxagraf Date: Tue, 12 Feb 2019 14:31:53 -0600 Subject: finished up subscribe functions for lttr --- app/lttr/admin.py | 13 +++ app/lttr/forms.py | 25 ++++++ app/lttr/migrations/0003_newsletter_slug.py | 19 +++++ app/lttr/migrations/0004_auto_20190212_1529.py | 21 +++++ app/lttr/models.py | 95 ++++++++++------------ app/lttr/urls.py | 33 +++++++- app/lttr/views.py | 50 ++++++++++-- design/templates/lttr/confirm_activate.html | 17 ++++ design/templates/lttr/message/subscribe.html | 22 +++++ design/templates/lttr/message/subscribe.txt | 9 ++ .../templates/lttr/message/subscribe_subject.txt | 1 + design/templates/lttr/message/unsubscribe.html | 19 +++++ design/templates/lttr/message/unsubscribe.txt | 9 ++ .../templates/lttr/message/unsubscribe_subject.txt | 1 + design/templates/lttr/subscribed.html | 17 ++++ design/templates/lttr/subscriber_form.html | 5 +- 16 files changed, 296 insertions(+), 60 deletions(-) create mode 100644 app/lttr/admin.py create mode 100644 app/lttr/migrations/0003_newsletter_slug.py create mode 100644 app/lttr/migrations/0004_auto_20190212_1529.py create mode 100644 design/templates/lttr/confirm_activate.html create mode 100644 design/templates/lttr/message/subscribe.html create mode 100644 design/templates/lttr/message/subscribe.txt create mode 100644 design/templates/lttr/message/subscribe_subject.txt create mode 100644 design/templates/lttr/message/unsubscribe.html create mode 100644 design/templates/lttr/message/unsubscribe.txt create mode 100644 design/templates/lttr/message/unsubscribe_subject.txt create mode 100644 design/templates/lttr/subscribed.html diff --git a/app/lttr/admin.py b/app/lttr/admin.py new file mode 100644 index 0000000..1b919d6 --- /dev/null +++ b/app/lttr/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin + +from .models import NewsletterMailing, Subscriber + + +@admin.register(Subscriber) +class SubscriberAdmin(admin.ModelAdmin): + list_display = ('email_field', 'user', 'date_created', 'subscribed', 'subscribe_date', 'unsubscribed') + search_fields = ['email_field'] + list_filter = ['unsubscribed'] + + class Media: + js = ('next-prev-links.js',) diff --git a/app/lttr/forms.py b/app/lttr/forms.py index e1d4709..ad41d66 100644 --- a/app/lttr/forms.py +++ b/app/lttr/forms.py @@ -73,3 +73,28 @@ class SubscribeRequestForm(NewsletterForm): pass return data + + +class UpdateForm(NewsletterForm): + """ + This form allows one to actually update to or unsubscribe from the + newsletter. To do this, a correct activation code is required. + """ + + email_field = forms.EmailField( + label=("e-mail"), validators=[validate_email_nouser], disabled=True + ) + + def clean_user_activation_code(self): + data = self.cleaned_data['user_activation_code'] + + if data != self.instance.activation_code: + raise ValidationError( + ('The validation code supplied by you does not match.') + ) + + return data + + user_activation_code = forms.CharField( + label=("Activation code"), max_length=40 + ) diff --git a/app/lttr/migrations/0003_newsletter_slug.py b/app/lttr/migrations/0003_newsletter_slug.py new file mode 100644 index 0000000..ea20b4b --- /dev/null +++ b/app/lttr/migrations/0003_newsletter_slug.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.5 on 2019-02-11 22:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('lttr', '0002_subscriber_email_field'), + ] + + operations = [ + migrations.AddField( + model_name='newsletter', + name='slug', + field=models.SlugField(default='friends', unique=True), + preserve_default=False, + ), + ] diff --git a/app/lttr/migrations/0004_auto_20190212_1529.py b/app/lttr/migrations/0004_auto_20190212_1529.py new file mode 100644 index 0000000..4b810c9 --- /dev/null +++ b/app/lttr/migrations/0004_auto_20190212_1529.py @@ -0,0 +1,21 @@ +# Generated by Django 2.1.5 on 2019-02-12 15:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('lttr', '0003_newsletter_slug'), + ] + + operations = [ + migrations.RemoveField( + model_name='subscriber', + name='create_date', + ), + migrations.RemoveField( + model_name='subscriber', + name='ip', + ), + ] diff --git a/app/lttr/models.py b/app/lttr/models.py index 029022c..ec5e897 100644 --- a/app/lttr/models.py +++ b/app/lttr/models.py @@ -28,6 +28,7 @@ def make_activation_code(): class Newsletter(models.Model): """ A model for Newletters. Might I one day have two? I might. """ title = models.CharField(max_length=250) + slug = models.SlugField(db_index=True, unique=True) def __str__(self): return self.title @@ -36,7 +37,7 @@ class Newsletter(models.Model): return reverse("lttr:detail", kwargs={"slug": self.slug}) def subscribe_url(self): - return reverse('newsletter_subscribe_request', kwargs={'newsletter_slug': self.slug}) + return reverse('lttr:newsletter_subscribe_request', kwargs={'newsletter_slug': self.slug}) def unsubscribe_url(self): return reverse('newsletter_unsubscribe_request', kwargs={'newsletter_slug': self.slug}) @@ -50,6 +51,44 @@ class Newsletter(models.Model): def get_subscriptions(self): return Subscriber.objects.filter(newsletter=self, subscribed=True) + def get_templates(self, action): + """ + Return a subject, text, HTML tuple with e-mail templates for + a particular action. Returns a tuple with subject, text and e-mail + template. + """ + + assert action in ACTIONS + ('message', ), 'Unknown action: %s' % action + + # Common substitutions for filenames + tpl_subst = { + 'action': action, + 'newsletter': self.slug + } + + # Common root path for all the templates + tpl_root = 'lttr/message/' + + subject_template = select_template([ + tpl_root + '%(newsletter)s/%(action)s_subject.txt' % tpl_subst, + tpl_root + '%(action)s_subject.txt' % tpl_subst, + ]) + + text_template = select_template([ + tpl_root + '%(newsletter)s/%(action)s.txt' % tpl_subst, + tpl_root + '%(action)s.txt' % tpl_subst, + ]) + + html_template = select_template([ + tpl_root + '%(newsletter)s/%(action)s.html' % tpl_subst, + tpl_root + '%(action)s.html' % tpl_subst, + ]) + + return (subject_template, text_template, html_template) + + def get_sender(self): + return 'Scott Gilbertson ' + @classmethod def get_default(cls): try: @@ -85,45 +124,6 @@ class NewsletterMailing(models.Model): self.date_created = timezone.now() super(NewsletterMailing, self).save() - def get_templates(self, action): - """ - Return a subject, text, HTML tuple with e-mail templates for - a particular action. Returns a tuple with subject, text and e-mail - template. - """ - - assert action in ACTIONS + ('message', ), 'Unknown action: %s' % action - - # Common substitutions for filenames - tpl_subst = { - 'action': action, - 'newsletter': self.slug - } - - # Common root path for all the templates - tpl_root = 'newsletter/message/' - - subject_template = select_template([ - tpl_root + '%(newsletter)s/%(action)s_subject.txt' % tpl_subst, - tpl_root + '%(action)s_subject.txt' % tpl_subst, - ]) - - text_template = select_template([ - tpl_root + '%(newsletter)s/%(action)s.txt' % tpl_subst, - tpl_root + '%(action)s.txt' % tpl_subst, - ]) - - if self.send_html: - html_template = select_template([ - tpl_root + '%(newsletter)s/%(action)s.html' % tpl_subst, - tpl_root + '%(action)s.html' % tpl_subst, - ]) - else: - # HTML templates are not required - html_template = None - - return (subject_template, text_template, html_template) - class Subscriber(models.Model): """ A model for Newletter Subscriber """ @@ -131,9 +131,7 @@ class Subscriber(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) date_created = models.DateTimeField(blank=True, auto_now_add=True, editable=False) date_updated = models.DateTimeField(blank=True, auto_now=True, editable=False) - ip = models.GenericIPAddressField(blank=True, null=True) newsletter = models.ForeignKey(Newsletter, on_delete=models.CASCADE) - create_date = models.DateTimeField(editable=False, default=datetime.datetime.now) activation_code = models.CharField(max_length=40, default=make_activation_code) subscribed = models.BooleanField(default=False, db_index=True) subscribe_date = models.DateTimeField(null=True, blank=True) @@ -278,25 +276,22 @@ class Subscriber(models.Model): message.send() def subscribe_activate_url(self): - return reverse('newsletter_update_activate', kwargs={ - 'newsletter_slug': self.newsletter.slug, - 'email': self.email, - 'action': 'subscribe', + return reverse('lttr:newsletter_activate', kwargs={ + 'slug': self.newsletter.slug, 'activation_code': self.activation_code }) def unsubscribe_activate_url(self): return reverse('newsletter_update_activate', kwargs={ - 'newsletter_slug': self.newsletter.slug, + 'slug': self.newsletter.slug, 'email': self.email, 'action': 'unsubscribe', 'activation_code': self.activation_code }) def update_activate_url(self): - return reverse('newsletter_update_activate', kwargs={ - 'newsletter_slug': self.newsletter.slug, - 'email': self.email, + return reverse('lttr:newsletter_update_activate', kwargs={ + 'slug': self.newsletter.slug, 'action': 'update', 'activation_code': self.activation_code }) diff --git a/app/lttr/urls.py b/app/lttr/urls.py index 84b0bca..e0166f1 100644 --- a/app/lttr/urls.py +++ b/app/lttr/urls.py @@ -1,4 +1,4 @@ -from django.urls import path +from django.urls import path, re_path from . import views @@ -11,10 +11,39 @@ urlpatterns = [ name="detail" ), path( - r'', + r'/', views.NewsletterListView.as_view(), name="list" ), + path( + '/activate//', + views.ConfirmSubscriptionView.as_view(), name='newsletter_activate' + ), + #path( + # '/subscribe/confirm/', + # views.SubscribeRequestView.as_view(confirm=True), + # name='newsletter_subscribe_confirm' + #), + #path( + # '^/update/$', + # views.UpdateRequestView.as_view(), + # name='newsletter_update_request' + #), + #path( + # '^/unsubscribe/$', + # views.UnsubscribeRequestView.as_view(), + # name='newsletter_unsubscribe_request' + #), + #path( + # '^/unsubscribe/confirm/$', + # views.UnsubscribeRequestView.as_view(confirm=True), + # name='newsletter_unsubscribe_confirm' + #), + path( + r'subscribed/', + views.NewsletterSubscribedView.as_view(), + name="subscribed" + ), path( r'', views.NewsletterListView.as_view(), diff --git a/app/lttr/views.py b/app/lttr/views.py index 786bc5c..0d3dbea 100644 --- a/app/lttr/views.py +++ b/app/lttr/views.py @@ -1,14 +1,22 @@ -from django.views.generic import ListView, CreateView +import socket +from django.views.generic import ListView, CreateView, TemplateView, FormView from django.views.generic.detail import DetailView from django.views.generic.dates import YearArchiveView, MonthArchiveView from django.contrib.syndication.views import Feed +from django.template.response import TemplateResponse +from django.contrib.auth import get_user_model from django.db.models import Q +from django.shortcuts import get_object_or_404, redirect from django.conf import settings +from django.urls import reverse, reverse_lazy from utils.views import PaginatedListView +from smtplib import SMTPException from .models import NewsletterMailing, Subscriber, Newsletter -from .forms import SubscribeRequestForm +from .forms import SubscribeRequestForm, UpdateForm + +ACTIONS = ('subscribe', 'unsubscribe', 'update') class NewsletterMailingDetail(DetailView): @@ -29,12 +37,17 @@ class NewsletterMailingDetail(DetailView): return context +class NewsletterSubscribedView(TemplateView): + template_name = "lttr/subscribed.html" + + class NewsletterListView(CreateView): - model = Subscriber - form_class = SubscribeRequestForm """ Return a subscribe form and list of Newsletter posts in reverse chronological order """ + model = Subscriber + form_class = SubscribeRequestForm + action = 'subscribe' def get_form_kwargs(self): kwargs = super(NewsletterListView, self).get_form_kwargs() @@ -46,9 +59,34 @@ class NewsletterListView(CreateView): context['mailings'] = NewsletterMailing.objects.filter(status=1) return context + def get_success_url(self): + return reverse_lazy('lttr:subscribed') + def form_valid(self, form, **kwargs): - form.instance.user = settings.AUTH_USER_MODEL.objects.get_or_create(email=form.instance.email) + form.instance.user, created = get_user_model().objects.get_or_create( + email=form.cleaned_data['email_field'], + username=form.cleaned_data['email_field'] + ) self.object = form.save() + try: + self.object.send_activation_email(action=self.action) + + except (SMTPException, socket.error) as e: + print(e) + self.error = True + + # Although form was valid there was error while sending email, + # so stay at the same url. + return super(NewsletterListView, self).form_invalid(form) return super(NewsletterListView, self).form_valid(form) - #super(NewsletterListView, self).form_valid() + +class ConfirmSubscriptionView(DetailView): + model = Subscriber + template_name = "lttr/confirm_activate.html" + + def get_object(self): + obj = Subscriber.objects.get(newsletter__slug=self.kwargs['slug'], activation_code=self.kwargs['activation_code']) + if obj.subscribed is False: + obj.update('subscribe') + return obj diff --git a/design/templates/lttr/confirm_activate.html b/design/templates/lttr/confirm_activate.html new file mode 100644 index 0000000..d67ee1b --- /dev/null +++ b/design/templates/lttr/confirm_activate.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} +{% load typogrify_tags %} + +{% block pagetitle %}Luxagraf | Friends of a Long Year {% endblock %} +{% block metadescription %}An infrequesnt mailing list about travel, photography, tools, walking, the natural world and other ephemera.{% endblock %} + +{% block primary %} +
+
+

You're confirmed, thanks for joining.

+

If you'd like you can visit the archives of past mailings.

+
+
+{%endblock%} diff --git a/design/templates/lttr/message/subscribe.html b/design/templates/lttr/message/subscribe.html new file mode 100644 index 0000000..89d8221 --- /dev/null +++ b/design/templates/lttr/message/subscribe.html @@ -0,0 +1,22 @@ +{% load i18n %} + + + + + {% blocktrans with title=newsletter.title %}Subscription to {{ title }}{% endblocktrans %} + + + +{% blocktrans with name=subscription.name title=newsletter.title domain=site.domain url=subscription.subscribe_activate_url %}Dear {{ name }}, + +hola- + +Someone, hopefully you, asked to subscribe to {{ title }}, a luxagraf.net letter. + +To confirm your subscription, please follow this activation link: +https://{{ domain }}{{ url }} +{% endblocktrans %} +cheers +Scott + + diff --git a/design/templates/lttr/message/subscribe.txt b/design/templates/lttr/message/subscribe.txt new file mode 100644 index 0000000..2af5378 --- /dev/null +++ b/design/templates/lttr/message/subscribe.txt @@ -0,0 +1,9 @@ +Hola- + +Someone, hopefully you, asked to subscribe to {{ newsletter.title }}, a luxagraf.net letter. + +If you would like to confirm your subscription, please follow this activation link: +https://{{ site.domain }}{{ subscription.subscribe_activate_url }} + +cheers +Scott diff --git a/design/templates/lttr/message/subscribe_subject.txt b/design/templates/lttr/message/subscribe_subject.txt new file mode 100644 index 0000000..f4660e0 --- /dev/null +++ b/design/templates/lttr/message/subscribe_subject.txt @@ -0,0 +1 @@ +Confirm Your Subscription to {{newsletter.title}} diff --git a/design/templates/lttr/message/unsubscribe.html b/design/templates/lttr/message/unsubscribe.html new file mode 100644 index 0000000..4b1a86b --- /dev/null +++ b/design/templates/lttr/message/unsubscribe.html @@ -0,0 +1,19 @@ +{% load i18n %} + + + + + {% blocktrans with title=newsletter.title %}Unsubscription from {{ title }}{% endblocktrans %} + + +{% blocktrans with name=subscription.name title=newsletter.title domain=site.domain url=subscription.unsubscribe_activate_url %}Dear {{ name }}, + +you, or someone in your name requested unsubscription from {{ title }}. + +If you would like to confirm your unsubscription, please follow this activation link: +http://{{ domain }}{{ url }} + +Kind regards,{% endblocktrans %} +{{ newsletter.sender }} + + diff --git a/design/templates/lttr/message/unsubscribe.txt b/design/templates/lttr/message/unsubscribe.txt new file mode 100644 index 0000000..ab31fa5 --- /dev/null +++ b/design/templates/lttr/message/unsubscribe.txt @@ -0,0 +1,9 @@ +{% load i18n %}{% blocktrans with name=subscription.name title=newsletter.title domain=site.domain url=subscription.unsubscribe_activate_url %}Dear {{ name }}, + +you, or someone in your name requested unsubscription from {{ title }}. + +If you would like to confirm your unsubscription, please follow this activation link: +http://{{ domain }}{{ url }} + +Kind regards,{% endblocktrans %} +{{ newsletter.sender }} diff --git a/design/templates/lttr/message/unsubscribe_subject.txt b/design/templates/lttr/message/unsubscribe_subject.txt new file mode 100644 index 0000000..49c68ef --- /dev/null +++ b/design/templates/lttr/message/unsubscribe_subject.txt @@ -0,0 +1 @@ +{% load i18n %}{{ newsletter.title }} - {% trans "Confirm unsubscription" %} diff --git a/design/templates/lttr/subscribed.html b/design/templates/lttr/subscribed.html new file mode 100644 index 0000000..18ad151 --- /dev/null +++ b/design/templates/lttr/subscribed.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} +{% load typogrify_tags %} + +{% block pagetitle %}Luxagraf | Friends of a Long Year {% endblock %} +{% block metadescription %}An infrequesnt mailing list about travel, photography, tools, walking, the natural world and other ephemera.{% endblock %} + +{% block primary %} +
+
+

Thanks for joining.

+

Check your email for a link to confirm your subscription

+
+
+{%endblock%} diff --git a/design/templates/lttr/subscriber_form.html b/design/templates/lttr/subscriber_form.html index ded2c5f..c495cfa 100644 --- a/design/templates/lttr/subscriber_form.html +++ b/design/templates/lttr/subscriber_form.html @@ -17,9 +17,10 @@ {{field.label_tag}} {{field}} - {%endfor%} - + {% if forloop.last %}{%endif%} + {% if field.errors %}{{field.errors}}{% endif %} + {%endfor%}

Join Friends of a Long Year, an infrequent mailing that will keep you up-to-date with luxagraf and offer some thoughts on topics like travel, photography, the natural world, tools, walking and other ephemera. It comes about twice a month, sometimes less, sometimes more. Unsubscribing is easy. It's all self-hosted, secure, and private.

The name comes from the great early 20th century explorer and desert rat, Mary Hunter Austin, whose collected essays, Lost Borders is dedicated to the "Friends of a Long Year". This somewhoat inscrutable dedication grabbed me, and seemed like the perfect name for this mailing list.

-- cgit v1.2.3