diff options
author | luxagraf <sng@luxagraf.net> | 2020-11-11 14:54:09 -0500 |
---|---|---|
committer | luxagraf <sng@luxagraf.net> | 2020-11-11 14:54:09 -0500 |
commit | 52c80b30dc84d85cbad2fe317d1058e6a0c58803 (patch) | |
tree | 1b9a56cd06879763bbe9f724d46a46ff54b28c8b /app/lttr | |
parent | 1cfbf44615d5053e6e200972ff74ade38e7d7c31 (diff) |
finished up newsletter formatting and delivery. Just need to clean up
the rough edges
Diffstat (limited to 'app/lttr')
-rw-r--r-- | app/lttr/admin.py | 12 | ||||
-rw-r--r-- | app/lttr/mailer.py | 86 | ||||
-rw-r--r-- | app/lttr/migrations/0011_auto_20201106_2050.py | 21 | ||||
-rw-r--r-- | app/lttr/migrations/0012_auto_20201106_2109.py | 18 | ||||
-rw-r--r-- | app/lttr/migrations/0013_auto_20201110_1730.py | 23 | ||||
-rw-r--r-- | app/lttr/models.py | 77 | ||||
-rw-r--r-- | app/lttr/templates/lttr/friends_base.html | 252 | ||||
-rw-r--r-- | app/lttr/templates/lttr/friends_base_left.html | 176 | ||||
-rw-r--r-- | app/lttr/templates/lttr/unsubscribe.html | 17 | ||||
-rw-r--r-- | app/lttr/urls.py | 10 | ||||
-rw-r--r-- | app/lttr/views.py | 9 |
11 files changed, 664 insertions, 37 deletions
diff --git a/app/lttr/admin.py b/app/lttr/admin.py index 13e2607..d7d68bc 100644 --- a/app/lttr/admin.py +++ b/app/lttr/admin.py @@ -2,7 +2,7 @@ from django.contrib import admin from utils.widgets import AdminImageWidget, LGEntryForm -from .models import NewsletterMailing, Subscriber +from .models import NewsletterMailing, Subscriber, Newsletter, MailingStatus @admin.register(Subscriber) class SubscriberAdmin(admin.ModelAdmin): @@ -13,6 +13,9 @@ class SubscriberAdmin(admin.ModelAdmin): class Media: js = ('next-prev-links.js',) +@admin.register(Newsletter) +class NewsletterAdmin(admin.ModelAdmin): + pass @admin.register(NewsletterMailing) class NewsletterMailingAdmin(admin.ModelAdmin): @@ -24,6 +27,8 @@ class NewsletterMailingAdmin(admin.ModelAdmin): ('title', "newsletter", "issue"), 'subtitle', 'body_markdown', + 'body_html', + 'body_email_html', ('pub_date', 'status'), 'slug', 'featured_image', @@ -42,3 +47,8 @@ class NewsletterMailingAdmin(admin.ModelAdmin): css = { "all": ("my_styles.css",) } + +@admin.register(MailingStatus) +class MailingStatusAdmin(admin.ModelAdmin): + list_display = ('newsletter_mailing', 'subscriber', 'status', 'creation_date') + list_filter = ('status', 'creation_date') diff --git a/app/lttr/mailer.py b/app/lttr/mailer.py new file mode 100644 index 0000000..b5b875e --- /dev/null +++ b/app/lttr/mailer.py @@ -0,0 +1,86 @@ +from time import sleep +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText + +from django.core.mail import EmailMultiAlternatives +from django.template.loader import render_to_string +from django.utils.encoding import smart_str + +from .models import Subscriber, MailingStatus + + + +class SendShit(): + + def __init__(self, newsletter, mailing, verbose=0): + self.verbose = verbose + self.newsletter = newsletter + self.mailing = mailing + all_subscribers = Subscriber.objects.filter(newsletter=self.newsletter,subscribed=True,unsubscribed=False).values('email_field') + already_sent = MailingStatus.objects.filter(newsletter_mailing=self.mailing,status=1).values('subscriber__email_field') + self.subscribers = all_subscribers.difference(already_sent) + + + def send_mailings(self): + mailings = len(self.subscribers) + print("mailing newsletter to %s subscribers"%mailings) + i = 1 + for s in self.subscribers: + subscriber = Subscriber.objects.get(newsletter=self.newsletter,email_field=s['email_field'],subscribed=True,unsubscribed=False) + status = None + if self.verbose == 1: + print("mailing newsletter %s of %s to %s" %(i,mailings,subscriber)) + status, created = MailingStatus.objects.get_or_create( + newsletter_mailing=self.mailing, + subscriber=subscriber, + ) + # New instance, try sending + if created: + try: + email = self.build_message(subscriber) + status.status=1 + if self.verbose==1: + print("successfully sent %s the newsletter mailing %s"%(subscriber, self.mailing)) + except: + status.status=2 + if self.verbose == 1: + print("failed to send %s to %s"%(self.mailing, subscriber)) + status.save() + else: + # not new, check if error and resend or just continue + if status.status == 2: + if self.verbose==1: + print("retrying error") + try: + email = self.build_message(subscriber) + status.status=1 + if self.verbose==1: + print("successfully sent %s the newsletter mailing %s"%(subscriber, self.mailing)) + except: + status.status=2 + if self.verbose == 1: + print("failed to send %s to %s"%(self.mailing, subscriber)) + status.save() + i=i+1 + sleep(2) + + + def build_message(self, subscriber): + """ + Build the email as plain text with a + a multipart alternative for HTML + """ + subject = smart_str("%s: %s — %s" %(self.mailing.newsletter.title, self.mailing.get_issue_str(), self.mailing.title)) + from_email, to = 'Scott Gilbertson <sng@luxagraf.net>', subscriber.get_email() + text_content = self.mailing.email_encode() + html_content = render_to_string('lttr/friends_base.html', {'object': self.mailing, 'subscriber':subscriber}) + msg = EmailMultiAlternatives(subject, text_content, from_email, [to]) + msg.attach_alternative(html_content, "text/html") + msg.send() + + ''' + for header, value in self.newsletter.server.custom_headers.items(): + message[header] = value + ''' + return msg + diff --git a/app/lttr/migrations/0011_auto_20201106_2050.py b/app/lttr/migrations/0011_auto_20201106_2050.py new file mode 100644 index 0000000..bc3a488 --- /dev/null +++ b/app/lttr/migrations/0011_auto_20201106_2050.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1 on 2020-11-06 20:50 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('lttr', '0010_newslettermailing_books'), + ] + + operations = [ + migrations.AlterField( + model_name='subscriber', + name='user', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/app/lttr/migrations/0012_auto_20201106_2109.py b/app/lttr/migrations/0012_auto_20201106_2109.py new file mode 100644 index 0000000..a56410b --- /dev/null +++ b/app/lttr/migrations/0012_auto_20201106_2109.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1 on 2020-11-06 21:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('lttr', '0011_auto_20201106_2050'), + ] + + operations = [ + migrations.AlterField( + model_name='mailingstatus', + name='status', + field=models.IntegerField(choices=[(-1, 'sent in test'), (0, 'sent'), (1, 'error'), (2, 'invalid email'), (4, 'opened'), (5, 'opened on site'), (6, 'link opened'), (7, 'unsubscription')], null=True, verbose_name='status'), + ), + ] diff --git a/app/lttr/migrations/0013_auto_20201110_1730.py b/app/lttr/migrations/0013_auto_20201110_1730.py new file mode 100644 index 0000000..8acf4c2 --- /dev/null +++ b/app/lttr/migrations/0013_auto_20201110_1730.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1 on 2020-11-10 17:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('lttr', '0012_auto_20201106_2109'), + ] + + operations = [ + migrations.AddField( + model_name='newslettermailing', + name='body_email_html', + field=models.TextField(blank=True), + ), + migrations.AlterField( + model_name='mailingstatus', + name='status', + field=models.IntegerField(choices=[(0, 'Initialized'), (1, 'Sent'), (2, 'Error')], null=True), + ), + ] diff --git a/app/lttr/models.py b/app/lttr/models.py index 91d5e22..5f80321 100644 --- a/app/lttr/models.py +++ b/app/lttr/models.py @@ -1,8 +1,8 @@ import datetime + from django.contrib.gis.db import models from django.contrib.sites.models import Site from django.template.loader import select_template -from django.core.mail import EmailMultiAlternatives from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.utils.text import slugify @@ -10,6 +10,10 @@ from django.urls import reverse from django.conf import settings from django.utils.crypto import get_random_string +from django.template import Context, Template + +from bs4 import BeautifulSoup + from taggit.managers import TaggableManager from utils.util import render_images, parse_video, markdown_to_html @@ -22,6 +26,18 @@ from books.models import Book ACTIONS = ('subscribe', 'unsubscribe', 'update') +def markdown_to_emailhtml(base_html): + soup = BeautifulSoup(base_html, "lxml") + for p in soup.find_all('p'): + p.attrs['style']="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-weight:normal;margin-bottom:1.4em;font-size:17px;line-height:1.5;hyphens:auto;color:#222222;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important;" + for i in soup.find_all('img'): + i.attrs['width']="720" + i.attrs['style']="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;display:inline;margin-bottom:0;width:100% !important;max-width:100% !important;height:auto !important;max-height:auto !important;" + for h in soup.find_all('hr'): + h.attrs['style']="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;width:20%;margin-top:40px;margin-bottom:40px;margin-right:0px;margin-left:0px;border-width:0;border-top-width:1px;border-top-style:solid;border-top-color:#ddd;" + return str(soup)[12:-14] + + def make_activation_code(): """ Generate a unique activation code. """ @@ -108,6 +124,7 @@ class NewsletterMailing(models.Model): subtitle = models.CharField(max_length=250, null=True, blank=True) slug = models.SlugField(unique_for_date='pub_date', blank=True) body_html = models.TextField(blank=True) + body_email_html = models.TextField(blank=True) body_markdown = models.TextField() pub_date = models.DateTimeField() featured_image = models.ForeignKey(LuxImage, on_delete=models.CASCADE, null=True, blank=True) @@ -137,6 +154,10 @@ class NewsletterMailing(models.Model): if self.issue < 10: issue = "00%s" % self.issue return issue + + def email_encode(self): + + return self.body_markdown @property def get_previous_published(self): @@ -164,13 +185,14 @@ class NewsletterMailing(models.Model): if not created: md = render_images(self.body_markdown) self.body_html = markdown_to_html(md) + self.body_email_html = markdown_to_emailhtml(self.body_html) self.date_created = timezone.now() if created and not self.featured_image: self.featured_image = LuxImage.objects.latest() old = type(self).objects.get(pk=self.pk) if self.pk else None if old and old.featured_image != self.featured_image: # Field has changed s = LuxImageSize.objects.get(name="navigation_thumb") - ss = LuxImageSize.objects.get(name="picwide-med") + ss = LuxImageSize.objects.get(name="picwide-sm") self.featured_image.sizes.add(s) self.featured_image.sizes.add(ss) self.featured_image.save() @@ -180,7 +202,7 @@ class NewsletterMailing(models.Model): class Subscriber(models.Model): """ A model for Newletter Subscriber """ email_field = models.EmailField(db_column='email', db_index=True, blank=True, null=True) - user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True) date_created = models.DateTimeField(blank=True, auto_now_add=True, editable=False) date_updated = models.DateTimeField(blank=True, auto_now=True, editable=False) newsletter = models.ForeignKey(Newsletter, on_delete=models.CASCADE) @@ -191,7 +213,9 @@ class Subscriber(models.Model): unsubscribe_date = models.DateTimeField(null=True, blank=True) def __str__(self): - return self.user.username + if self.user: + return self.user.username + return self.email_field def get_name(self): if self.user: @@ -334,10 +358,8 @@ class Subscriber(models.Model): }) def unsubscribe_activate_url(self): - return reverse('newsletter_update_activate', kwargs={ + return reverse('lttr:newsletter_unsubscribe', kwargs={ 'slug': self.newsletter.slug, - 'email': self.email, - 'action': 'unsubscribe', 'activation_code': self.activation_code }) @@ -356,38 +378,33 @@ def get_address(name, email): return u'%s' % email -class MailingStatus(models.Model): - """Status of the reception""" - SENT_TEST = -1 - SENT = 0 - ERROR = 1 - INVALID = 2 - OPENED = 4 - OPENED_ON_SITE = 5 - LINK_OPENED = 6 - UNSUBSCRIPTION = 7 - - STATUS_CHOICES = ((SENT_TEST, _('sent in test')), - (SENT, _('sent')), - (ERROR, _('error')), - (INVALID, _('invalid email')), - (OPENED, _('opened')), - (OPENED_ON_SITE, _('opened on site')), - (LINK_OPENED, _('link opened')), - (UNSUBSCRIPTION, _('unsubscription')), - ) +class StatusType(models.IntegerChoices): + INIT = 0, ('Initialized') + SENT = 1, ('Sent') + ERROR = 2, ('Error') + +class MailingStatus(models.Model): newsletter_mailing = models.ForeignKey(NewsletterMailing, on_delete=models.CASCADE, verbose_name=_('newsletter')) subscriber = models.ForeignKey(Subscriber, on_delete=models.CASCADE, verbose_name=_('subscriber')) - status = models.IntegerField(_('status'), choices=STATUS_CHOICES) + status = models.IntegerField(choices=StatusType.choices, null=True) creation_date = models.DateTimeField(_('creation date'), auto_now_add=True) def __str__(self): - return '%s : %s : %s' % (self.newsletter.__str__(), - self.contact.__str__(), + return '%s : %s : %s' % (self.newsletter_mailing, + self.subscriber, self.get_status_display()) class Meta: ordering = ('-creation_date',) verbose_name = _('subscriber mailing status') verbose_name_plural = _('subscriber mailing statuses') + + +''' +from lttr.mailer import SendShit +mailing = NewsletterMailing.objects.get(pk=1) +newsletter = Newsletter.objects.get(pk=3) +n = SendShit(newsletter, mailing, 1) +n.send_mailings() +''' diff --git a/app/lttr/templates/lttr/friends_base.html b/app/lttr/templates/lttr/friends_base.html new file mode 100644 index 0000000..42e7795 --- /dev/null +++ b/app/lttr/templates/lttr/friends_base.html @@ -0,0 +1,252 @@ +{% load typogrify_tags %} +<!DOCTYPE html> +<html lang="en" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" > +<head style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" > + <meta name="viewport" content="width=device-width" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + <meta name=”robot” content=”noindex” style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" > + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + <title style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >{{object.title}}</title> + + <style type="text/css" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" > +@font-face { + font-family: 'mffnweb'; + src: url('https://luxagraf.net/media/fonts/ffmn.woff2') format('woff2'); + src: url('https://luxagraf.net/media/fonts/ffmn.woff') format('woff'); + font-weight: 400; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: 'mffnbweb'; + src: url('https://luxagraf.net/media/fonts/ffmn.woff2') format('woff2'); + src: url('https://luxagraf.net/media/fonts/ffmn.woff') format('woff'); + font-weight: 700; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: 'mffweb'; + src: url('https://luxagraf.net/media/fonts/ffmpb.woff2') format('woff2'); + src: url('https://luxagraf.net/media/fonts/ffmpb.woff') format('woff'); + font-weight: 400; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: 'mffweb'; + src: url('https://luxagraf.net/media/fonts/ffmbi.woff2') format('woff2'); + src: url('https://luxagraf.net/media/fonts/ffmbi.woff') format('woff'); + font-weight: 400; + font-style: italic; + font-display: swap; +} + +.tk-ff-meta-web-pro { font-family: mffnbweb,sans-serif; } +.tk-mffnweb { font-family: mffnweb,serif; } +* { + margin:0; + padding:0; +} +* { } +sup, sub { + vertical-align: baseline; + position: relative; + top: -0.4em; +} +sub { + top: 0.4em; +} +img { + max-width: 100%; +} +img.fullbleed { display: inline; border-radius: 3px; margin-bottom: 1.5em; width: 100% !important; max-width: 100% !important; height: auto !important; max-height: auto !important; } +p img.fullbleed { margin-bottom: 0px; } +.collapse { + margin:0; + padding:0; +} +body { + -webkit-font-smoothing:antialiased; + -webkit-text-size-adjust:none; + width: 100%!important; + background-color: #ffffff; + height: 100%; + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif; +} +a { color: #000; font-weight: 600; text-decoration: none; border-bottom: 1px solid #ddd; } +.btn { + text-decoration:none; + color: #FFF; + background-color: #666; + padding:10px 16px; + font-weight:bold; + margin-right:10px; + text-align:center; + cursor:pointer; + display: inline-block; +} +p.callout { + padding:15px; + background-color:#ECF8FF; + margin-bottom: 15px; +} +.callout a { + font-weight:bold; + color: #2BA6CB; +} +.highlight { background-color: #ffffb2;} +figure { border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; padding-top: 20px; padding-bottom: 20px; margin-bottom: 30px; } +figcaption { text-align: center; font-size: .8em; } +.sp { font-size: .85em; text-transform: uppercase; font-weight: bold; letter-spacing: 1px; } +table.head-wrap { width: 100%;} +table.body-wrap { width: 100%;} +table.footer-wrap { + width: 100%; + clear:both!important; + color: #999; + font-family: helvetica !important; + font-size: 10px !important; +} +.footer-wrap .container .content p { + font-size: 14px; +} +.footer-wrap .container .content a { color: #333; text-decoration: none; } +.footnotes ol li { font-size: .8em; } +.footnotes ol li p { font-size: .8em; } +.footnotes hr { display: none; } +h1,h2,h3,h4,h5,h6 { +font-family: mffnweb, 'Lora', 'Lucida Serif', Lucida, Georgia, serif; +line-height: 1; +margin-bottom:15px; +color:#000; +text-align: center; +} +h1 small, h2 small, h3 small, h4 small, h5 small, h6 small { font-size: 60%; color: #6f6f6f; line-height: 0; text-transform: none; } +h1 { font-weight:400; font-size: 44px;} +h2 { font-weight:400; font-size: 30px;} +h4 { font-weight:500; font-size: 23px;} +h5 { font-weight:500; font-size: 23px; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif; text-align: left; } +h3, h6 { font-weight:400; font-size: 32px; font-style: italic; margin-top: 40px; text-transform: none; color:#000; text-align: left;} +h2 { font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif; } +h2 a { font-weight: normal; } +.collapse { margin:0!important;} +p, ul, ol { + margin-bottom: 1.4em; + font-weight: 400; + + font-size:17px; + line-height:1.5; + hyphens: auto; +} +hr { width: 50%; margin: 40px auto; border: 0; border-top: 1px solid #ddd; } +p.quote { padding-left: 10px; border-left: 2px solid #ddd; } +blockquote { border-left: 4px solid #efefef; padding-left: 15px; font-style: italic; } +p.lead { font-size:17px; } +p.last { margin-bottom:0px; } +ul li, ol li { + margin-left: 35px; + list-style-position: outside; +} +.container { + display:block!important; + max-width:720px!important; + margin:0 auto!important; + clear:both!important; +} +.content { + padding:17px; + max-width:720px; + margin:0 auto; + display:block; +} +.content table { width: 100%; } +.clear { display: block; clear: both; } +@media only screen and (max-width: 700px) { + + a[class="btn"] { display:block!important; margin-bottom:10px!important; background-image:none!important; margin-right:0!important;} + img.fullbleed { margin-bottom: 1.5em; width: 100%; height: auto !important; } + p { font-size: 16px;} + h1 { font-size: 36px; } + div[class="column"] { width: auto!important; float:none!important;} + + table.social div[class="column"] { + width:auto!important; + } +} + +</style> + +</head> +<body style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:none;width:100%!important;background-color:#ffffff;height:100%;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif;" > + <table class="body-wrap" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;width:100%;" > + <tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" > + <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" ></td> + <td class="container" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;display:block!important;max-width:720px!important;margin-top:0 !important;margin-bottom:0 !important;margin-right:auto !important;margin-left:auto !important;clear:both!important;" > + <div class="content" style="padding-top:15px;padding-bottom:15px;padding-right:15px;padding-left:15px;max-width:720px;margin-top:0;margin-bottom:0;margin-right:auto;margin-left:auto;display:block;" > + <table style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;width:100%;" > + <tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" > + <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" > + <h2 style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;line-height:1;margin-bottom:15px;color:#000;text-align:center;font-weight:400;font-size:30px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif;" ><br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + <span style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-size:.5em;line-height:2em;" ><singleline style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" ><a href="https://luxagraf.net/newsletter/" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;color:#000;text-decoration:none;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#ddd;font-weight:normal;" >Friends of a Long Year</a> — {{object.get_issue_str}} — {{object.pub_date|date:"F"}} <span>{{object.pub_date|date:"j, Y"}}</span></singleline></span></h2> + <h1 style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:mffnweb, 'Lora', 'Lucida Serif', Lucida, Georgia, serif;line-height:1;margin-bottom:15px;color:#000;text-align:center;font-weight:400;font-size:44px;" ><singleline style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >{{object.title|safe|smartypants}}</singleline></h1> + + <hr style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;width:50%;margin-top:40px;margin-bottom:40px;margin-right:auto;margin-left:auto;border-width:0;border-top-width:1px;border-top-style:solid;border-top-color:#ddd;" /> + <a href="https://luxagraf.net{{object.get_absolute_url}}" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;border-width:0;color:#000;font-weight:600;text-decoration:none;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#ddd;" > + {% include "lib/friends_featured_img.html" with image=object.featured_image %} + </a> + <br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + <br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + + {{object.body_email_html|safe|smartypants}} + + <hr style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;width:50%;margin-top:40px;margin-bottom:40px;margin-right:auto;margin-left:auto;border-width:0;border-top-width:1px;border-top-style:solid;border-top-color:#ddd;" /> + </td> + </tr> + </table> + </div> +</td> +<td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" ></td> +</tr> +</table> +<table class="footer-wrap" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;width:100%;clear:both!important;color:#999;font-family:helvetica !important;font-size:10px !important;" > + <tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" > + <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" ></td> + <td class="container" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;display:block!important;max-width:720px!important;margin-top:0 !important;margin-bottom:0 !important;margin-right:auto !important;margin-left:auto !important;clear:both!important;" > + + + <div class="content" style="padding-top:15px;padding-bottom:15px;padding-right:15px;padding-left:15px;max-width:720px;margin-top:0;margin-bottom:0;margin-right:auto;margin-left:auto;display:block;" > + <table style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;width:100%;" > + <tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" > + <td align="center" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" > + + {%comment%}<p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;margin-bottom:1.4em;font-weight:400;line-height:1.5;hyphens:auto;font-size:14px;" >This newsletter is made possible by members of <a href="" class="sp" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#ddd;font-size:.85em;text-transform:uppercase;font-weight:bold;letter-spacing:1px;color:#333;text-decoration:none;" >SPECIAL PROJECTS</a>.<br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + If you enjoy <em style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >Ridgeline</em>, consider joining. Thanks. </p>{%endcomment%} + <p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;margin-bottom:1.4em;font-weight:400;line-height:1.5;hyphens:auto;font-size:14px;" >New subscriber?<br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + + Browse the <a href="https://luxagraf.net/newsletter/friends/" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-weight:600;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#ddd;color:#333;text-decoration:none;" >online archives</a> here.</p> + <p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;margin-bottom:1.4em;font-weight:400;line-height:1.5;hyphens:auto;font-size:14px;" >⫹⫺</p> + <p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;margin-bottom:1.4em;font-weight:400;line-height:1.5;hyphens:auto;font-size:14px;" ><em style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >Friends</em>?<br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + + A monthly letter from <a href="https://luxagraf.net/" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-weight:600;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#ddd;color:#333;text-decoration:none;" >Scott Gilberson</a>, <br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" />also known as luxagraf..<br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /></p> + + <p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;margin-bottom:1.4em;font-weight:400;line-height:1.5;hyphens:auto;font-size:14px;" >⫹⫺</p> +<p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;margin-bottom:1.4em;font-weight:400;line-height:1.5;hyphens:auto;font-size:14px;" > + Shipped from points unknown, USA.<br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + Explained <a href="https://luxagraf.net/" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-weight:600;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#ddd;color:#333;text-decoration:none;" >here</a>.</p> + <br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + <p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;margin-bottom:1.4em;font-weight:400;line-height:1.5;hyphens:auto;font-size:14px;" >If you enjoy this, <br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + please consider forwarding it to a friend. <br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" />We are after all, friends of a long year here.</p> + <p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;margin-bottom:1.4em;font-weight:400;line-height:1.5;hyphens:auto;font-size:14px;" >⫹⫺</p> + <p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-weight:normal;margin-bottom:1.4em;line-height:1.5;hyphens:auto;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important;color:#999;font-size:14px;" >You can always: <a href="https://luxagraf.net{{subscriber.unsubscribe_activate_url}}" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-weight:600;text-decoration:underline;color:#999 !important;" >Unsubscribe</a> instantly.</p> + </td> + </tr> + </table> + </div> + + </td> + <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" ></td> + </tr> +</table> +</html> + + diff --git a/app/lttr/templates/lttr/friends_base_left.html b/app/lttr/templates/lttr/friends_base_left.html new file mode 100644 index 0000000..f2b91e6 --- /dev/null +++ b/app/lttr/templates/lttr/friends_base_left.html @@ -0,0 +1,176 @@ +{% load typogrify_tags %} +<!DOCTYPE html> +<html lang="en" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;color:#222222;background-color:#ffffff;" > +<head style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" > + <meta name="viewport" content="width=device-width" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + <meta name="”robot”" content="”noindex”" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + <title style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >Friends of a Long Year</title> + <style type="text/css" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" > +* { + margin:0; + padding:0; +} +sup, sub { + vertical-align: baseline; + position: relative; + top: -0.4em; +} +sub { + top: 0.4em; +} +img { + max-width: 100%; +} +img.fullbleed { display: inline; border-radius: 3px; margin-bottom: 0; width: 100% !important; max-width: 100% !important; height: auto !important; max-height: auto !important; } +p img.fullbleed { margin-bottom: 0px; } +.collapse { + margin:0; + padding:0; +} +html { color: #222222; background-color: #ffffff; } +body { + width: 100%!important; + background-color: #ffffff; + height: 100%; + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important; + font-weight: normal; + margin-bottom: 1.4em; + font-size:15px; + line-height:1.5; +} +body, td, input, textarea, select { font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important; } +.slowtweets { background-color: #f8f8f8; padding: 20px; border-radius: 5px; } + .slowtweets h4 { font-size: 1em; } + .slowtweets li { list-style: none; margin-bottom: 1em; padding-bottom: 1em; border-bottom: 1px solid #f1f1f1; font-size: .9em; margin-left: 0px;} + .slowtweets p { font-size: .8em; font-weight: bold; } +a { color: #222222; font-weight: 600; text-decoration: underline; } +.nounderline { border: 0; } +.nounderline a { text-decoration: none; } +table, tr, td { background-color: #ffffff !important; } +table.head-wrap { width: 100%;} +table.body-wrap { width: 100%;} +table.footer-wrap { + width: 100%; + clear:both!important; + color: #999; + font-family: helvetica !important; + font-size: 10px !important; +} +.footer-wrap .container .content p { + font-size: 14px; +} +.footer-wrap .container .content a { color: #333; } +h1,h2,h3,h4,h5,h6 { +font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif; +line-height: 1; +margin-bottom:15px; +color:#000; +text-align: left; +} +h1 { font-weight:400; font-size: 15px; text-transform: uppercase; letter-spacing: 8px; font-weight: bold; } + h1 span { letter-spacing: 0; } +h2 { font-weight:400; font-size: 15px; } +h4 { font-weight:500; font-size: 23px;} +h5 { font-weight:900; font-size: 17px;} +h3, h6 { font-weight:400; font-size: 32px; font-style: italic; margin-top: 40px; text-transform: none; color:#000; text-align: left;} +h2 { font-family: ff-meta-web-pro, 'Open Sans', sans-serif; } +h2 a { font-weight: normal; } +.collapse { margin:0!important;} +p, ul, ol { + font-weight: normal; + margin-bottom: 1.4em; + font-size:15px; + line-height:1.5; + hyphens: auto; + color: #222222; + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important; +} +hr { width: 20%; margin: 40px 0px; border: 0; border-top: 1px solid #ddd; } +p.quote { padding-left: 10px; border-left: 2px solid #ddd; } +blockquote { border-left: 4px solid #efefef; padding-left: 15px; font-style: italic; } +ul li, ol li { + margin-left: 35px; + list-style-position: outside; +} +.sp { text-transform: uppercase; font-weight: bold; letter-spacing: 1px; font-size: .85em; } +footer p { color: #999; } +footer a { color: #999 !important; } +.container { + display:block!important; + max-width:650px!important; + margin:0 10px!important; + clear:both!important; +} +.content { + padding:15px; + max-width:650px; + margin:0 0px; + display:block; +} +.content table { width: 100%; } +@media only screen and (max-width: 700px) { + + img.fullbleed { margin-bottom: 1.5em; width: 100%; height: auto !important; } + p { font-size: 16px;} + h1 { font-size: 16px; } +} +</style> +</head> +<body style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;width:100%!important;background-color:#ffffff;height:100%;font-weight:normal;margin-bottom:1.4em;font-size:15px;line-height:1.5;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important;" > + <table class="body-wrap" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;background-color:#ffffff !important;width:100%;" > + <tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;background-color:#ffffff !important;" > + <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important;background-color:#ffffff !important;" ></td> + <td class="container" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important;background-color:#ffffff !important;display:block!important;max-width:650px!important;margin-top:0 !important;margin-bottom:0 !important;margin-right:10px !important;margin-left:10px !important;clear:both!important;" > + <div class="content" style="padding-top:15px;padding-bottom:15px;padding-right:15px;padding-left:15px;max-width:650px;margin-top:0;margin-bottom:0;margin-right:0px;margin-left:0px;display:block;" > + <table style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;background-color:#ffffff !important;width:100%;" > + <tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;background-color:#ffffff !important;" > + <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important;background-color:#ffffff !important;" > + <h1 style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif;line-height:1;margin-bottom:15px;color:#000;text-align:left;font-weight:bold;font-size:15px;text-transform:uppercase;letter-spacing:8px;" > + </h1> + + <p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;color:#888;font-size:.9em;font-weight:normal;margin-bottom:1.4em;line-height:1.5;hyphens:auto;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important;" > + {{object.pub_date|date:"F"}} <span>{{object.pub_date|date:"j, Y"}}</span><br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /></p> + {{object.body_email_html|safe|smartypants}} + </td> + </tr> + </table> + </div> + </td> + <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important;background-color:#ffffff !important;" ></td> + </tr> +</table> +<table class="footer-wrap" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;background-color:#ffffff !important;width:100%;clear:both!important;color:#999;font-family:helvetica !important;font-size:10px !important;" > + <tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;background-color:#ffffff !important;" > + <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important;background-color:#ffffff !important;" ></td> + <td class="container" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important;background-color:#ffffff !important;display:block!important;max-width:650px!important;margin-top:0 !important;margin-bottom:0 !important;margin-right:10px !important;margin-left:10px !important;clear:both!important;" > + + + <div class="content" style="padding-top:15px;padding-bottom:15px;padding-right:15px;padding-left:15px;max-width:650px;margin-top:0;margin-bottom:0;margin-right:0px;margin-left:0px;display:block;" > + <hr style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;width:20%;margin-top:40px;margin-bottom:40px;margin-right:0px;margin-left:0px;border-width:0;border-top-width:1px;border-top-style:solid;border-top-color:#ddd;" /> + <table style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;background-color:#ffffff !important;width:100%;" > + <tr style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;background-color:#ffffff !important;" > + <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important;background-color:#ffffff !important;" > + + + <footer style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" > + <p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-weight:normal;margin-bottom:1.4em;line-height:1.5;hyphens:auto;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important;color:#999;font-size:14px;" >You're getting this email<br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + because you signed up for<br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> + + <a href="https://luxagraf.net/" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-weight:600;text-decoration:underline;color:#999 !important;" >Scott Gilbertson's</a> <a href="https://luxagraf.net/newsletter/" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-weight:600;text-decoration:underline;color:#999 !important;" ><em style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >Friends of a Long Year</em></a> + <br style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" /> +newsletter.</p> + <p style="margin-top:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-weight:normal;margin-bottom:1.4em;line-height:1.5;hyphens:auto;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important;color:#999;font-size:14px;" >You can always: <a href="https://luxagraf.net{{subscriber.unsubscribe_activate_url}}" style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-weight:600;text-decoration:underline;color:#999 !important;" >Unsubscribe</a> instantly.</p> + </footer> + + </td> + </tr> + </table> + </div> + + </td> + <td style="margin-top:0;margin-bottom:0;margin-right:0;margin-left:0;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif !important;background-color:#ffffff !important;" ></td> + </tr> +</table> +</body> +</html> diff --git a/app/lttr/templates/lttr/unsubscribe.html b/app/lttr/templates/lttr/unsubscribe.html new file mode 100644 index 0000000..d3f17e0 --- /dev/null +++ b/app/lttr/templates/lttr/unsubscribe.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 %}<ul class="bl" id="breadcrumbs" itemscope itemtype="http://data-vocabulary.org/Breadcrumb"> + <li><a href="/" title="luxagraf homepage" itemprop="url"><span itemprop="title">Home</span></a> → </li> + <li>Lttr</li> + </ul> + <main role="main" id="essay-archive" class="essay-archive archive-list"> + <div class="essay-intro"> + <h2>You're unsubscribed, so long friend</h2> + <p>If you clicked by mistake you can always <a href="{% url 'lttr:newsletter_activate' slug=newsletter activation_code=subscriber.activation_code %}">rejoin our merry band</a>.</p> + </div> + </main> +{%endblock%} diff --git a/app/lttr/urls.py b/app/lttr/urls.py index 05febe3..f103cec 100644 --- a/app/lttr/urls.py +++ b/app/lttr/urls.py @@ -6,6 +6,11 @@ app_name = "lttr" urlpatterns = [ path( + '<str:slug>/unsubscribe/<str:activation_code>', + views.UnsubscribeRequestView.as_view(), + name='newsletter_unsubscribe' + ), + path( r'<str:slug>/<int:issue>/<str:mailing>', views.NewsletterMailingDetail.as_view(), name="detail" @@ -19,11 +24,6 @@ urlpatterns = [ '<str:slug>/activate/<str:activation_code>/', views.ConfirmSubscriptionView.as_view(), name='newsletter_activate' ), - path( - '<str:slug>/unsubscribe/', - views.UnsubscribeRequestView.as_view(), - name='newsletter_unsubscribe_request' - ), #path( # '/subscribe/confirm/', # views.SubscribeRequestView.as_view(confirm=True), diff --git a/app/lttr/views.py b/app/lttr/views.py index e12d5a7..bf3df4c 100644 --- a/app/lttr/views.py +++ b/app/lttr/views.py @@ -93,7 +93,14 @@ class UnsubscribeRequestView(DetailView): template_name = "lttr/unsubscribe.html" def get_object(self): - obj = Subscriber.objects.get(newsletter__slug=self.kwargs['slug']) + obj = Subscriber.objects.get(newsletter__slug=self.kwargs['slug'],activation_code=self.kwargs['activation_code']) if obj.subscribed is True: obj.update('unsubscribe') return obj + + + def get_context_data(self, **kwargs): + context = super(UnsubscribeRequestView, self).get_context_data(**kwargs) + context['subscriber'] = self.get_object() + context['newsletter'] = self.kwargs['slug'] + return context |