Project

General

Profile

0001-unsubscribe-link-10795.patch

Serghei Mihai, 28 June 2016 12:08 PM

Download (10.5 KB)

View differences:

Subject: [PATCH] unsubscribe link (#10795)

 corbo/models.py                                    | 22 +++++++++++++------
 corbo/static/css/corbo.css                         | 17 +++++++++++++++
 corbo/templates/corbo/announce.html                |  9 ++++++++
 .../corbo/subscription_confirm_delete.html         | 18 ++++++++++++++++
 corbo/templates/corbo/unsubscription.html          | 14 ++++++++++++
 corbo/templates/corbo/unsubscription_done.html     | 10 +++++++++
 corbo/urls.py                                      |  8 +++++--
 corbo/views.py                                     | 25 ++++++++++++++++++++++
 tests/test_emailing.py                             | 25 +++++++++++++++++++++-
 9 files changed, 139 insertions(+), 9 deletions(-)
 create mode 100644 corbo/templates/corbo/announce.html
 create mode 100644 corbo/templates/corbo/subscription_confirm_delete.html
 create mode 100644 corbo/templates/corbo/unsubscription.html
 create mode 100644 corbo/templates/corbo/unsubscription_done.html
corbo/models.py
9 9
from django.db import models
10 10
from django.core.files.storage import DefaultStorage
11 11
from django.utils.translation import ugettext_lazy as _
12
from django.core import signing
13
from django.template import loader, Context
14
from django.core.urlresolvers import reverse
12 15

  
13 16
from ckeditor.fields import RichTextField
14 17

  
......
75 78
        subscriptions = self.announce.category.subscription_set.all()
76 79
        total_sent = 0
77 80
        handler = HTML2Text()
78
        m = Message(html=self.announce.text, subject=self.announce.title,
79
                    text=handler.handle(self.announce.text),
80
                    mail_from=settings.DEFAULT_FROM_EMAIL)
81
        template = loader.get_template('corbo/announce.html')
82
        m = Message(subject=self.announce.title, mail_from=settings.DEFAULT_FROM_EMAIL)
81 83
        html_tree = HTMLTree(self.announce.text)
82 84
        storage = DefaultStorage()
83 85
        for img in html_tree.xpath('//img/@src'):
84 86
            img_path = img.lstrip(storage.base_url)
85 87
            m.attach(filename=img, data=storage.open(img_path))
86 88
            m.attachments[img].is_inline = True
87
        m.transformer.synchronize_inline_images()
88
        m.transformer.load_and_transform()
89
        m.transformer.save()
90 89
        for s in subscriptions:
91 90
            if not s.identifier:
92 91
                continue
92
            unsubscribe_token = signing.dumps({'category': self.announce.category.pk,
93
                                               'identifier': s.identifier})
94
            unsubscribe_link = reverse('unsubscribe', kwargs={'unsubscription_token': unsubscribe_token})
95
            message = template.render(Context({'unsubscribe_link': unsubscribe_link,
96
                                               'content': self.announce.text}))
97
            m.html = message
98
            m.transformer.load_and_transform()
99
            m.transformer.synchronize_inline_images()
100
            m.transformer.save()
101
            handler.body_width = 0
102
            m.text = handler.handle(message)
93 103
            sent = m.send(to=s.identifier)
94 104
            if sent:
95 105
                total_sent += 1
corbo/static/css/corbo.css
280 280

  
281 281
#id_transport_channel li, #id_transport_channel label {
282 282
    display: inline;
283
}
284

  
285
.content {
286
    width: 50%;
287
    margin: auto;
288
}
289

  
290
.unsubscription {
291
    background: #e6db74;
292
    border: 1px solid #aaa;
293
    padding: 5px;
294
}
295

  
296
.info {
297
    background: #a6e22e;
298
    border: 1px solid #aaa;
299
    padding: 5px;
283 300
}
corbo/templates/corbo/announce.html
1
{% load i18n %}
2
<div class="content">
3
  {{ content|safe }}
4
</div>
5
<div class="footer">
6
  {% blocktrans %}
7
  Click <a href='{{ unsubscribe_link}}'>here</a> to unsubscribe from this newsletter.
8
  {% endblocktrans %}
9
</div>
corbo/templates/corbo/subscription_confirm_delete.html
1
{% extends "corbo/unsubscription.html" %}
2
{% load i18n %}
3

  
4
{% block title %}{% blocktrans with object.category as category %}
5
Unsubscription from {{ category }}
6
{% endblocktrans %}{% endblock %}
7

  
8
{% block content %}
9
<div class="unsubscription">
10
{% blocktrans with object.category as category and object.identifier as identifier %}
11
Are you sure you want to unsubscribe {{ identifier }} from "{{ category }}"?
12
{% endblocktrans %}
13
<form method='POST'>
14
  {% csrf_token %}
15
  <button>{% trans "Unsubscribe" %}</button>
16
</form>
17
</div>
18
{% endblock %}
corbo/templates/corbo/unsubscription.html
1
{% load static %}
2
<!DOCTYPE html>
3
<html>
4
  <head>
5
    <title>{% block title %}{% endblock %}</title>
6
    <link rel='stylesheet' type='text/css' href='{% static "css/corbo.css" %}' />
7
  </head>
8
  <body>
9
    <div class='content'>
10
    {% block content %}
11
    {% endblock %}
12
    </div>
13
  </body>
14
</html>
corbo/templates/corbo/unsubscription_done.html
1
{% extends "corbo/unsubscription.html" %}
2
{% load i18n %}
3

  
4
{% block title %}
5
{% trans "Successfully unsubscription" %}
6
{% endblock %}
7

  
8
{% block content %}
9
<div class="info">{% trans "You were sucessfully unsubcribed." %}</div>
10
{% endblock %}
corbo/urls.py
5 5
from django.contrib import admin
6 6

  
7 7
from .urls_utils import decorated_includes, manager_required
8
from .views import homepage, atom
8
from .views import homepage, atom, unsubscribe, unsubscription_done
9 9

  
10 10
from manage_urls import urlpatterns as manage_urls
11 11
from api_urls import urlpatterns as api_urls
......
17 17
                    include(manage_urls))),
18 18
    url(r'^ckeditor/', include('ckeditor.urls')),
19 19
    url(r'^admin/', include(admin.site.urls)),
20
    url(r'^api/', include(api_urls))
20
    url(r'^api/', include(api_urls)),
21
    url(r'^unsubscribe/done/$', unsubscription_done,
22
        name='unsubscription_done'),
23
    url(r'^unsubscribe/(?P<unsubscription_token>[\w:-]+)$', unsubscribe,
24
        name='unsubscribe'),
21 25
)
22 26

  
23 27
if 'mellon' in settings.INSTALLED_APPS:
corbo/views.py
1 1
from datetime import datetime
2 2

  
3 3
from django.conf import settings
4
from django.core import signing
4 5
from django.core.urlresolvers import reverse
5 6
from django.views.generic import CreateView, UpdateView, DeleteView, \
6 7
                                 ListView, TemplateView
7 8
from django.contrib.syndication.views import Feed
8 9
from django.utils.feedgenerator import Atom1Feed
9 10
from django.utils.http import urlencode
11
from django.http import Http404
10 12

  
11 13
import models
12 14
from .forms import AnnounceForm, CategoryForm
......
79 81
delete_category = CategoryDeleteView.as_view()
80 82

  
81 83

  
84
class UnsubscribeView(DeleteView):
85
    model = models.Subscription
86

  
87
    def get_object(self, queryset=None):
88
        data = signing.loads(self.kwargs['unsubscription_token'])
89
        try:
90
            return models.Subscription.objects.get(category__pk=data['category'],
91
                                identifier=data['identifier'])
92
        except models.Subscription.DoesNotExist:
93
            raise Http404
94

  
95
    def get_success_url(self):
96
        return reverse('unsubscription_done')
97

  
98
unsubscribe = UnsubscribeView.as_view()
99

  
100

  
101
class UnsubscriptionDoneView(TemplateView):
102
    template_name='corbo/unsubscription_done.html'
103

  
104
unsubscription_done = UnsubscriptionDoneView.as_view()
105

  
106

  
82 107
class ManageView(ListView):
83 108
    paginate_by = settings.ANNOUNCES_PER_PAGE
84 109
    template_name = 'corbo/manage.html'
tests/test_emailing.py
5 5

  
6 6
from django.core.urlresolvers import reverse
7 7
from django.utils.http import urlencode
8
from django.core import mail
8
from django.core import mail, signing
9 9
from django.utils import timezone
10 10
from django.core.files.storage import DefaultStorage
11
from django.core.urlresolvers import reverse
11 12

  
12 13
from corbo.models import Category, Announce, Subscription, Broadcast
13 14
from corbo.models import channel_choices
......
91 92
        assert storage.url(image_name) in mail.outbox[0].attachments.keys()
92 93
        mail.outbox = []
93 94
    storage.delete(image_name)
95

  
96
def test_unsubscription_link(app, categories, announces):
97
    for category in categories:
98
        uuid = uuid4()
99
        email = '%s@example.net' % uuid
100
        s = Subscription.objects.create(category=category,
101
                                identifier=email,
102
                                uuid=str(uuid))
103
        for announce in announces:
104
            if announce.category != category:
105
                continue
106
            broadcast= Broadcast.objects.get(announce=announce)
107
            broadcast.send()
108
            assert broadcast.result
109
            assert mail.outbox
110
            signature = signing.dumps({'category': announce.category.pk,
111
                                       'identifier': email})
112
            unsubscription_link = reverse('unsubscribe', kwargs={'unsubscription_token': signature})
113
            assert mail.outbox[0].subject == announce.title
114
            assert unsubscription_link in mail.outbox[0].html
115
            assert unsubscription_link in mail.outbox[0].text
116
            mail.outbox = []
94
-