Project

General

Profile

0001-unsubscribe-link-10795.patch

Serghei Mihai, 27 June 2016 03:31 PM

Download (10.3 KB)

View differences:

Subject: [PATCH] unsubscribe link (#10795)

 corbo/models.py                                    | 20 ++++++++++++----
 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                             | 27 ++++++++++++++++++++--
 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(settings.MEDIA_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.save()
89 89
        for s in subscriptions:
90 90
            if not s.identifier:
91 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.synchronize_inline_images()
99
            m.transformer.save()
100
            m.text = handler.handle(message)
101

  
92 102
            sent = m.send(to=s.identifier)
93 103
            if sent:
94 104
                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
2 2
import json
3 3
from uuid import uuid4
4 4

  
5

  
6 5
from django.core.urlresolvers import reverse
7 6
from django.utils.http import urlencode
8
from django.core import mail
7
from django.core import mail, signing
9 8
from django.utils import timezone
9
from django.core.urlresolvers import reverse
10 10

  
11 11
from corbo.models import Category, Announce, Subscription, Broadcast
12 12
from corbo.models import channel_choices
......
57 57
        broadcast.send()
58 58
        assert broadcast.result
59 59
        assert mail.outbox
60

  
61
def test_unsubscription_link(app, categories, announces):
62
    for category in categories:
63
        uuid = uuid4()
64
        email = '%s@example.net' % uuid
65
        s = Subscription.objects.create(category=category,
66
                                identifier=email,
67
                                uuid=str(uuid))
68
        for announce in announces:
69
            if announce.category != category:
70
                continue
71
            broadcast= Broadcast.objects.get(announce=announce)
72
            broadcast.send()
73
            assert broadcast.result
74
            assert mail.outbox
75
            for out in mail.outbox:
76
                signature = signing.dumps({'category': announce.category.pk,
77
                                           'identifier': email})
78
                unsubscription_link = reverse('unsubscribe', kwargs={'unsubscription_token': signature})
79
                assert out.subject == announce.title
80
                assert unsubscription_link in out.html
81
                assert unsubscription_link in out.text
82
            mail.outbox = []
60
-