add all functionnalities for sending announces to users
This commit is contained in:
parent
8c43aeab60
commit
3047160255
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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',)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{{ announce.text }}
|
|
@ -0,0 +1 @@
|
|||
{{ announce.title }}
|
|
@ -0,0 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{{ form }}
|
||||
<button type="submit">{% trans "Modify" %}</button>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -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)
|
|
@ -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()
|
||||
|
|
Reference in New Issue