add all functionnalities for sending announces to users

This commit is contained in:
Benjamin Dauvergne 2013-05-29 18:39:29 +02:00
parent 8c43aeab60
commit 3047160255
10 changed files with 398 additions and 2 deletions

View File

@ -0,0 +1,78 @@
import os
from django.contrib import admin
from django.contrib.sites.models import get_current_site
import models
import transports
import app_settings
from django.utils.translation import ugettext_lazy as _
class SendingAction(object):
__name__ = 'sending action'
def __init__(self, mode):
self.mode = mode
@property
def short_description(self):
transport = transports.get_transport(self.mode)
display_name = getattr(transport, 'display_name', transport.identifier)
return _('Send by {0}').format(display_name)
def __call__(self, modeladmin, request, queryset):
pid = os.fork()
if pid != 0:
return
transport = transports.get_transport(self.mode)
for announce in queryset.select_related():
transport.send(announce)
def transport_actions():
return [SendingAction(mode) for mode in app_settings.transport_modes]
class SentInlineAdmin(admin.TabularInline):
model = models.Sent
extra = 0
can_delete = False
fields = [ 'time', 'transport', 'result' ]
readonly_fields = [ 'time', 'transport', 'result' ]
def has_add_permission(self, request):
return False
class AnnounceAdmin(admin.ModelAdmin):
actions = transport_actions()
list_display = ['title', 'category', 'extract', 'publication_time',
'expiration_time', 'hidden', 'sent' ]
inlines = ( SentInlineAdmin, )
def extract(self, instance):
return instance.text[:60]
extract.short_description = _('extract')
def sent(self, instance):
return u', '.join(instance.sent_set.values_list('transport', flat=True).distinct())
sent.short_description = _('sent')
class CategoryAdmin(admin.ModelAdmin):
list_display = [ 'name', 'identifier' ]
def get_queryset(self, request):
qs = super(CategoryAdmin, self).get_queryset(request)
qs.filter(site=get_current_site(request))
return qs
class SubscriptionAdmin(admin.ModelAdmin):
list_filter = [ 'category' ]
list_display = [ 'category', 'transport', 'identifier', 'user', 'created' ]
admin.site.register(models.Announce, AnnounceAdmin)
admin.site.register(models.Category, CategoryAdmin)
admin.site.register(models.Subscription, SubscriptionAdmin)

View File

@ -0,0 +1,10 @@
from django.conf import settings
default_transport_modes = {
'email': 'portail_citoyen_announces.transports.EmailTransport',
}
transport_modes = getattr(settings, 'ANNOUNCES_TRANSPORTS',
default_transport_modes)
default_from = getattr(settings, 'ANNOUNCES_DEFAULT_FROM_EMAIL', None)

View File

@ -0,0 +1,47 @@
from collections import defaultdict
from django import forms
import models
import transports
class SubscriptionForm(forms.Form):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
self.site = kwargs.pop('site')
self.categories = models.Category.objects.filter(site=self.site)
self.subscriptions = models.Subscription.objects.filter(user=self.user)
sub_by_category = defaultdict(lambda: [])
for sub in self.subscriptions:
sub_by_category[sub].append(sub.transport)
super(SubscriptionForm, self).__init__(*args, **kwargs)
choices = transports.get_transport_choices()
for category in self.categories:
field = forms.MultipleChoiceField(
label=category.name, choices=choices,
widget=forms.CheckboxSelectMultiple,
initial=sub_by_category[category])
self.fields['category_%s' % category.identifier] = field
def save(self):
cleaned_data = self.cleaned_data()
wanted_subscriptions = set()
for category in self.categories:
field_id = 'category_%s' % category.identifier
transports = cleaned_data.get(field_id, [])
for transport in transports:
wanted_subscriptions.add((category, transport))
# delete dead subscriptions
for subscription in self.subscriptions:
pair = (subscription.category, subscription.transport)
if pair not in wanted_subscriptions:
subscription.delete()
# create or update new ones
for category, transport in wanted_subscriptions:
models.SubscriptionForm.objects.get_or_create(
user=self.user,
category=category,
transport=transport)

View File

@ -0,0 +1,26 @@
from model_utils.managers import PassThroughManager
from django.db.models.query import QuerySet
from django.db.models import Q
try:
from django.utils.timezone import now as now
except ImportError:
import datetime
now = datetime.now
class AnnounceQueryset(QuerySet):
def published(self):
qs = self.filter(hidden=False)
qs = qs.filter(
(Q(publication_time__lte=now) | Q(publication_time__isnull=True)) &
(Q(expiration_time__gte=now) | Q(expiration_time__isnull=True)))
return qs
def unpublished(self):
qs = self.exclude(hidden=False)
qs = qs.exclude(
(Q(publication_time__lte=now) | Q(publication_time__isnull=True)) &
(Q(expiration_time__gte=now) | Q(expiration_time__isnull=True)))
return qs
AnnounceManager = PassThroughManager.for_queryset_class(QuerySet)

View File

@ -1,3 +1,98 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.core.validators import RegexValidator
# Create your models here.
from model_utils.models import TimeStampedModel
import managers
import transports
class Category(TimeStampedModel):
site = models.ForeignKey('sites.Site', verbose_name=_('site'))
name = models.CharField(_('name'), max_length=64)
identifier = models.CharField(_('identifier'),
unique = True,
max_length=64,
validators=[RegexValidator(r'^[a-z0-9_]+$')],
help_text=_('the identifier is used to find '
'templates for sending announces; it can '
'only contain lowercase letter, digits or underscores'))
def __unicode__(self):
return self.name
class Meta:
verbose_name = _('category')
ordering = ('name',)
unique_together = (('site', 'name'), ('site', 'identifier'))
class Announce(models.Model):
objects = managers.AnnounceManager()
title = models.CharField(_('title'), max_length=256,
help_text=_('maximum 256 characters'))
text = models.TextField(_('text'))
hidden = models.BooleanField(_('hidden'), blank=True, default=False)
publication_time = models.DateTimeField(_('publication time'), blank=True,
null=True)
expiration_time = models.DateTimeField(_('expiration time'), blank=True,
null=True)
creation_time = models.DateTimeField(_('creation time'), auto_now_add=True)
modification_time = models.DateTimeField(_('modification time'), auto_now=True)
category = models.ForeignKey('Category', verbose_name=_('category'), blank=True)
def __unicode__(self):
return u'{title} ({id}) at {modification_time}'.format(title=self.title,
id=self.id, modification_time=self.modification_time)
class Meta:
verbose_name = _('announce')
ordering = ('-modification_time',)
class Sent(models.Model):
announce = models.ForeignKey('Announce', verbose_name=_('announce'))
# subscription = models.ForeignKey('Subscription', verbose_name=_('subscription'))
transport = models.CharField(_('transport'), max_length=32,
choices=transports.get_transport_choices(), blank=False)
time = models.DateTimeField(_('sent time'), auto_now_add=True)
result = models.TextField(_('result'), blank=True)
def __unicode__(self):
return u'announce {id} sent via {transport} at {time}'.format(
id=self.announce.id, transport=self.transport, time=self.time)
class Meta:
verbose_name = _('sent')
ordering = ('-time',)
class Subscription(TimeStampedModel):
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'),
blank=True, null=True)
category = models.ForeignKey('Category', verbose_name=_('category'))
identifier = models.CharField(_('identifier'), max_length=128, blank=False,
help_text=_('ex.: email, mobile phone number, jabber id'))
transport = models.CharField(_('transport'),
max_length=32, choices=transports.get_transport_choices(),
blank=False)
def __unicode__(self):
return u'subscription of {user} to category {category} with ' \
'transport {transport} and identifier {identifier}'.format(
user=self.user,
category=self.category,
transport=self.transport,
identifier=self.identifier)
class Meta:
verbose_name = _('subscription')
ordering = ('-created',)

View File

@ -0,0 +1 @@
{{ announce.text }}

View File

@ -0,0 +1 @@
{{ announce.title }}

View File

@ -0,0 +1,9 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<form method="post">
{{ form }}
<button type="submit">{% trans "Modify" %}</button>
</form>
{% endblock %}

View File

@ -0,0 +1,113 @@
import logging
import smtplib
from django.utils.importlib import import_module
from django.core.mail import EmailMessage
from django.template.loader import select_template
from django.template import Context
from django.utils.translation import ugettext_lazy as _
import app_settings
import models
logger = logging.getLogger()
__transport_choices = None
def get_transport_choices():
global __transport_choices
if __transport_choices is None:
__transport_choices = []
for mode in app_settings.transport_modes:
transport = get_transport(mode)
identifier = transport.identifier
display_name = getattr(transport, 'display_name', identifier)
__transport_choices.append((identifier, display_name))
return __transport_choices
def get_transport(mode):
if mode not in app_settings.transport_modes:
return None
module_path, class_name = app_settings.transport_modes[mode].rsplit('.', 1)
try:
module = import_module(module_path)
return getattr(module, class_name)()
except (ImportError, AttributeError):
logger.error('unable to load transport class %s.%s',
module_path, class_name)
return None
def get_template_list(template_list, category):
'''Customize a template list given an announce category'''
for template in template_list:
yield template.format(category=category.identifier)
def get_template(template_list, category):
template_list = get_template_list(template_list, category)
return select_template(template_list)
class EmailTransport(object):
subject_template_list = [
'portail_citoyen_announces/email/subject_{category}.txt',
'portail_citoyen_announces/email/subject.txt',
'portail_citoyen_announces/subject_{category}.txt',
'portail_citoyen_announces/subject.txt',
]
body_template_list = [
'portail_citoyen_announces/email/body_{category}.txt',
'portail_citoyen_announces/email/body.txt',
'portail_citoyen_announces/body_{category}.txt',
'portail_citoyen_announces/body.txt',
]
identifier = 'email'
display_name = _('email')
def get_subscriptions(self, category):
return models.Subscription.objects.filter(category=category,
transport=self.identifier)
def get_emails(self, category):
qs = self.get_subscriptions(category)
return qs.values_list('identifier', flat=True).distinct()
def send(self, announce):
category = announce.category
site = category.site
subject_template = get_template(self.subject_template_list, category)
body_template = get_template(self.body_template_list, category)
ctx = Context({ 'announce': announce, 'site': site, 'category': category })
subject = subject_template.render(ctx).replace('\r', '').replace('\n', '')
body = body_template.render(ctx)
emails = self.get_emails(category)
logger.info(u'sending announce %(announce)s through %(mode)s to %(count)s emails',
dict(announce=announce, mode=self.identifier, count=len(emails)))
try:
message = EmailMessage(subject=subject,
body=body,
from_email=app_settings.default_from,
bcc=emails)
message.send()
except smtplib.SMTPException, e:
msg = u'unable to send announce "%s" on site "%s": %s' % (announce,
site, e)
logger.error(msg)
except Exception, e:
msg = u'unable to send announce "%s" on site "%s": %s' % (announce,
site, e)
logger.exception(msg)
else:
logger.info('announce %(announce)s sent succesfully',
dict(announce=announce))
msg = u'ok'
models.Sent.objects.create(
announce=announce,
transport=self.identifier,
result=msg)

View File

@ -1 +1,17 @@
# Create your views here.
from django.views.generic.edit import FormView
import forms
class SubscriptionView(FormView):
template_name = 'portail_citoyen_announces/subscription_edit.html'
form_class = forms.SubscriptionForm
success_url = '..'
def form_valid(self, form):
form.save()
return super(SubscriptionView, self).form_valid(self, form)
subscription_view = SubscriptionView.as_view()