Projet

Général

Profil

Télécharger (7,91 ko) Statistiques
| Branche: | Tag: | Révision:

root / corbo / models.py @ 02ed310e

1
import os
2
import io
3
import hashlib
4
from time import mktime
5
from datetime import datetime
6
import logging
7
import urlparse
8
from html2text import HTML2Text
9
from emails.django import Message
10
from lxml import etree
11
import requests
12
import feedparser
13

    
14
from django.utils import timezone
15
from django.conf import settings
16
from django.db import models
17
from django.core.files.storage import DefaultStorage
18
from django.utils.translation import ugettext_lazy as _
19
from django.core import signing
20
from django.template import loader, Context
21
from django.core.urlresolvers import reverse
22
from django.utils.translation import activate
23

    
24
from ckeditor.fields import RichTextField
25

    
26
channel_choices = (
27
    ('mailto', _('Email')),
28
)
29

    
30
UNSUBSCRIBE_LINK_PLACEHOLDER = '##UNSUBSCRIBE_LINK_PLACEHOLDER##'
31

    
32
logger = logging.getLogger(__name__)
33

    
34

    
35
def transform_image_src(src, **kwargs):
36
    basename = os.path.basename(src)
37
    if basename == src:
38
        return src
39
    name, ext = os.path.splitext(src)
40
    hash = hashlib.sha256(name)
41
    return '%s_%s%s' % (os.path.basename(name), hash.hexdigest()[:8], ext)
42

    
43

    
44
class Category(models.Model):
45
    name = models.CharField(_('Name'), max_length=64, blank=False, null=False)
46
    rss_feed_url = models.URLField(_('Feed URL'), blank=True, null=True,
47
                help_text=_('if defined, announces will be automatically created from rss items'))
48
    ctime = models.DateTimeField(auto_now_add=True)
49

    
50
    def __unicode__(self):
51
        return self.name
52

    
53
    def get_announces_count(self):
54
        return self.announce_set.all().count()
55

    
56
    def get_subscriptions_count(self):
57
        return self.subscription_set.all().count()
58

    
59
    def save(self, *args, **kwargs):
60
        super(Category, self).save(*args, **kwargs)
61
        if not self.rss_feed_url:
62
            return
63
        feed_response = requests.get(self.rss_feed_url)
64
        if feed_response.ok:
65
            content = feedparser.parse(feed_response.content)
66
            for entry in content.get('entries', []):
67
                published = datetime.fromtimestamp(mktime(entry.published_parsed))
68
                html_tree = etree.HTML(entry['summary'])
69
                storage = DefaultStorage()
70
                for img in html_tree.xpath('//img'):
71
                    image_name = os.path.basename(img.attrib['src'])
72
                    r = requests.get(img.attrib['src'])
73
                    new_content = r.content
74
                    if storage.exists(image_name):
75
                        old_content = storage.open(image_name).read()
76
                        old_hash = hashlib.md5(old_content).hexdigest()
77
                        new_hash = hashlib.md5(new_content).hexdigest()
78
                        img.attrib['src'] = storage.url(image_name)
79
                        if new_hash == old_hash:
80
                            continue
81
                    new_image_name = storage.save(image_name, io.BytesIO(new_content))
82
                    img.attrib['src'] = storage.url(new_image_name)
83

    
84
                announce, created = Announce.objects.get_or_create(identifier=entry['id'],
85
                                                                   category=self)
86
                announce.title = entry['title']
87
                announce.text = etree.tostring(html_tree)
88
                announce.publication_time = published
89
                announce.save()
90

    
91
                if created:
92
                    Broadcast.objects.get_or_create(announce=announce)
93

    
94

    
95
class Announce(models.Model):
96
    category = models.ForeignKey('Category', verbose_name=_('category'))
97
    title = models.CharField(_('title'), max_length=256,
98
                             help_text=_('maximum 256 characters'))
99
    identifier = models.CharField(max_length=256, null=True, blank=True)
100
    text = RichTextField(_('Content'))
101
    publication_time = models.DateTimeField(_('publication time'), blank=True,
102
                                            null=True)
103
    expiration_time = models.DateTimeField(_('Expires on'), blank=True,
104
                                           null=True)
105
    ctime = models.DateTimeField(_('creation time'), auto_now_add=True)
106
    mtime = models.DateTimeField(_('modification time'), auto_now=True)
107

    
108
    def __unicode__(self):
109
        return u'{title} ({id}) at {mtime}'.format(
110
            title=self.title, id=self.id, mtime=self.mtime)
111

    
112
    def is_expired(self):
113
        if self.expiration_time:
114
            return self.expiration_time < timezone.now()
115
        return False
116

    
117
    def is_published(self):
118
        if self.publication_time:
119
            return self.publication_time <= timezone.now()
120
        return False
121

    
122
    class Meta:
123
        verbose_name = _('announce')
124
        ordering = ('-mtime',)
125

    
126

    
127
class Broadcast(models.Model):
128
    announce = models.ForeignKey(Announce, verbose_name=_('announce'))
129
    deliver_time = models.DateTimeField(_('Deliver time'), null=True)
130
    result = models.TextField(_('result'), blank=True)
131

    
132
    def __unicode__(self):
133
        if self.deliver_time:
134
            return u'announce {id} delivered via at {time}'.format(
135
                id=self.announce.id, time=self.deliver_time)
136
        return u'announce {id} to deliver'.format(id=self.announce.id)
137

    
138
    def send(self):
139
        subscriptions = self.announce.category.subscription_set.all()
140
        total_sent = 0
141
        handler = HTML2Text()
142
        activate(settings.LANGUAGE_CODE)
143
        template = loader.get_template('corbo/announce.html')
144
        message = Message(subject=self.announce.title, mail_from=settings.DEFAULT_FROM_EMAIL,
145
                          html=template.render(
146
                              Context({'content': self.announce.text,
147
                                      'unsubscribe_link_placeholder': UNSUBSCRIBE_LINK_PLACEHOLDER})))
148
        html_tree = etree.HTML(self.announce.text)
149
        storage = DefaultStorage()
150
        for img in html_tree.xpath('//img/@src'):
151
            img_path = img.lstrip(storage.base_url)
152
            message.attach(filename=transform_image_src(img), data=storage.open(img_path))
153

    
154
        message.transformer.apply_to_images(func=transform_image_src)
155
        # perform transformations in message html, like inline css parsing
156
        message.transformer.load_and_transform()
157
        # mark all attached images as inline
158
        message.transformer.make_all_images_inline()
159
        message.transformer.save()
160
        for s in subscriptions:
161
            if not s.identifier:
162
                continue
163
            unsubscribe_token = signing.dumps({'category': self.announce.category.pk,
164
                                               'identifier': s.identifier})
165
            unsubscribe_link = urlparse.urljoin(settings.SITE_BASE_URL, reverse(
166
                'unsubscribe', kwargs={'unsubscription_token': unsubscribe_token}))
167
            message.html = message.html.replace(UNSUBSCRIBE_LINK_PLACEHOLDER, unsubscribe_link)
168
            handler.body_width = 0
169
            message.text = handler.handle(message.html)
170
            sent = message.send(to=s.identifier)
171
            if sent:
172
                total_sent += 1
173
                logger.info('Announce "%s" sent to %s', self.announce.title, s.identifier)
174
            else:
175
                logger.warning('Error occured while sending announce "%s" to %s.',
176
                               self.announce.title, s.identifier)
177
        self.result = total_sent
178
        self.deliver_time = timezone.now()
179
        self.save()
180

    
181
    class Meta:
182
        verbose_name = _('sent')
183
        ordering = ('-deliver_time',)
184

    
185

    
186
class Subscription(models.Model):
187
    category = models.ForeignKey('Category', verbose_name=_('Category'))
188
    uuid = models.CharField(_('User identifier'), max_length=128, blank=True)
189
    identifier = models.CharField(_('identifier'), max_length=128, blank=True,
190
                                  help_text=_('ex.: mailto, ...'))
191

    
192
    def get_identifier_display(self):
193
        try:
194
            scheme, identifier = self.identifier.split(':')
195
            return identifier
196
        except ValueError:
197
            return self.identifier
198

    
199
    class Meta:
200
        unique_together = ('category', 'identifier', 'uuid')
(6-6/11)