Project

General

Profile

Download (7.6 KB) Statistics
| Branch: | Tag: | Revision:

root / corbo / models.py @ d0bd7a5a

1
import os
2
import io
3
import hashlib
4
from time import mktime
5
from datetime import datetime
6
from lxml import etree
7
import requests
8
import feedparser
9

    
10
from django.utils import timezone
11
from django.conf import settings
12
from django.db import models
13
from django.core.files.storage import DefaultStorage
14
from django.utils.translation import ugettext_lazy as _
15

    
16
from ckeditor.fields import RichTextField
17

    
18
from . import utils
19

    
20
channel_choices = (
21
    ('mailto', _('Email')),
22
    ('sms', _('SMS')),
23
)
24

    
25

    
26
class Category(models.Model):
27
    name = models.CharField(_('Name'), max_length=64, blank=False, null=False)
28
    slug = models.SlugField(_('Slug'), unique=True)
29
    rss_feed_url = models.URLField(_('Feed URL'), blank=True, null=True,
30
                help_text=_('if defined, announces will be automatically created from rss items'))
31
    ctime = models.DateTimeField(auto_now_add=True)
32

    
33
    def __unicode__(self):
34
        return self.name
35

    
36
    def get_announces_count(self):
37
        return self.announce_set.all().count()
38

    
39
    def get_subscriptions_count(self):
40
        return self.subscription_set.all().count()
41

    
42
    def save(self, *args, **kwargs):
43
        super(Category, self).save(*args, **kwargs)
44
        if not self.rss_feed_url:
45
            return
46
        feed_response = requests.get(self.rss_feed_url, proxies=settings.REQUESTS_PROXIES)
47
        if feed_response.ok:
48
            content = feedparser.parse(feed_response.content)
49
            for entry in content.get('entries', []):
50
                published = datetime.fromtimestamp(mktime(entry.published_parsed))
51

    
52
                announce, created = Announce.objects.get_or_create(identifier=entry['id'],
53
                                                                   category=self)
54
                announce.title = entry['title']
55
                announce.text = entry['summary']
56
                announce.publication_time = published
57
                announce.save()
58

    
59
                if created:
60
                    Broadcast.objects.get_or_create(announce=announce)
61

    
62

    
63
class Announce(models.Model):
64
    category = models.ForeignKey('Category', verbose_name=_('category'))
65
    title = models.CharField(_('title'), max_length=256,
66
                             help_text=_('maximum 256 characters'))
67
    identifier = models.CharField(max_length=256, null=True, blank=True)
68
    text = RichTextField(_('Content'))
69
    publication_time = models.DateTimeField(_('Publication date'), blank=True,
70
                                            null=True)
71
    expiration_time = models.DateTimeField(_('Expiration date'), blank=True,
72
                                           null=True)
73
    ctime = models.DateTimeField(_('creation time'), auto_now_add=True)
74
    mtime = models.DateTimeField(_('modification time'), auto_now=True)
75

    
76
    def save(self, *args, **kwargs):
77
        if self.text:
78
            html_tree = etree.HTML(self.text)
79
            storage = DefaultStorage()
80
            file_counter = 1
81
            for img in html_tree.xpath('//img'):
82
                if img.attrib['src'].startswith('/'):
83
                    continue
84
                image_name = os.path.basename(img.attrib['src'])
85
                r = requests.get(img.attrib['src'], proxies=settings.REQUESTS_PROXIES)
86
                if not r.ok:
87
                    continue
88
                new_content = r.content
89
                # get announce images list
90
                dirs, files = storage.listdir(self.images_path)
91
                existing_file = None
92

    
93
                # compute next filename
94
                files.sort()
95
                for f in files:
96
                    if image_name == f.split('_', 1)[-1]:
97
                        existing_file = f
98
                        try:
99
                            file_counter = int(f.split('_', 1)[0])
100
                            file_counter += 1
101
                        except ValueError:
102
                            file_counter = 1
103

    
104
                if existing_file:
105
                    existing_file_path = os.path.join(self.images_path, existing_file)
106
                    old_content = storage.open(existing_file_path).read()
107
                    old_hash = hashlib.md5(old_content).hexdigest()
108
                    new_hash = hashlib.md5(new_content).hexdigest()
109
                    img.attrib['src'] = storage.url(existing_file_path)
110
                    if new_hash == old_hash:
111
                        continue
112
                file_counter = str(file_counter).zfill(2)
113
                image_name = '%s_%s' % (file_counter, image_name)
114
                image_name = os.path.join(self.images_path, image_name)
115
                storage.save(image_name, io.BytesIO(new_content))
116
                img.attrib['src'] = storage.url(image_name)
117

    
118
            self.text = etree.tostring(html_tree)
119
        super(Announce, self).save(*args, **kwargs)
120

    
121
    @property
122
    def images_path(self):
123
        path = os.path.join('images', str(self.id))
124
        storage = DefaultStorage()
125
        if not storage.exists(path):
126
            os.makedirs(storage.path(path))
127
        return path
128

    
129
    def __unicode__(self):
130
        return u'{title} ({id}) at {mtime}'.format(
131
            title=self.title, id=self.id, mtime=self.mtime)
132

    
133
    def is_expired(self):
134
        if self.expiration_time:
135
            return self.expiration_time < timezone.now()
136
        return False
137

    
138
    def is_published(self):
139
        if self.publication_time:
140
            return self.publication_time <= timezone.now()
141
        return False
142

    
143
    class Meta:
144
        verbose_name = _('announce')
145
        ordering = ('-mtime',)
146

    
147

    
148
class Broadcast(models.Model):
149
    announce = models.ForeignKey(Announce, verbose_name=_('announce'))
150
    deliver_time = models.DateTimeField(_('Deliver time'), null=True)
151
    delivery_count = models.IntegerField(_('Delivery count'), default=0)
152

    
153
    def __unicode__(self):
154
        if self.deliver_time:
155
            return u'announce {id} delivered at {time}'.format(
156
                id=self.announce.id, time=self.deliver_time)
157
        return u'announce {id} to deliver'.format(id=self.announce.id)
158

    
159
    def filter_destinations(self, destinations, prefix):
160
        return [dest for dest in destinations if dest.startswith('%s:' % prefix)]
161

    
162
    def send_sms(self, title, content, destinations, category_id):
163
        return utils.send_sms(content, destinations)
164

    
165
    def send_mailto(self, title, content, destinations, category_id):
166
        return utils.send_email(title, content, destinations, category_id)
167

    
168
    def send(self):
169
        total_sent = 0
170
        destinations = [s.identifier for s in self.announce.category.subscription_set.all() if s.identifier]
171
        for channel_name, verbose_name in channel_choices:
172
            action = getattr(self, 'send_' + channel_name)
173
            filtered_destinations = self.filter_destinations(destinations, channel_name)
174
            total_sent += action(self.announce.title, self.announce.text,
175
                                 filtered_destinations, self.announce.category.id)
176
        self.delivery_count = total_sent
177
        self.deliver_time = timezone.now()
178
        self.save()
179

    
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/13)