api: newsletters retrieval endpoint (#10794)
This commit is contained in:
parent
0296e37098
commit
e93ea1421d
|
@ -0,0 +1,23 @@
|
|||
# corbo - Announces Manager
|
||||
# Copyright (C) 2016 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.conf.urls import patterns, include, url
|
||||
|
||||
from .api_views import NewslettersView
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^newsletters/', NewslettersView.as_view(), name='newsletters'),
|
||||
)
|
|
@ -0,0 +1,32 @@
|
|||
# corbo - Announces Manager
|
||||
# Copyright (C) 2016 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
|
||||
from .models import Category, Subscription, channel_choices
|
||||
|
||||
|
||||
class NewslettersView(APIView):
|
||||
|
||||
def get(self, request):
|
||||
newsletters = []
|
||||
transports = [{'id': identifier, 'text': name} for identifier, name in channel_choices]
|
||||
for c in Category.objects.all():
|
||||
newsletter = {'id': str(c.pk), 'text': c.name,
|
||||
'transports': transports}
|
||||
newsletters.append(newsletter)
|
||||
return Response({'data': newsletters})
|
|
@ -1,36 +0,0 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
def get_channel_choices(include=[], exclude=[]):
|
||||
for channel in HomepageChannel, SMSChannel, EmailChannel:
|
||||
if include and channel.identifier not in include:
|
||||
continue
|
||||
if exclude and channel.identifier in exclude:
|
||||
continue
|
||||
for identifier, display_name in channel.get_choices():
|
||||
yield (identifier, display_name)
|
||||
|
||||
class HomepageChannel(object):
|
||||
identifier = 'homepage'
|
||||
|
||||
@classmethod
|
||||
def get_choices(self):
|
||||
return (('homepage', _('Homepage')),)
|
||||
|
||||
class SMSChannel(object):
|
||||
|
||||
@classmethod
|
||||
def get_choices(self):
|
||||
return (('sms', _('SMS')),)
|
||||
|
||||
def send(self, announce):
|
||||
pass
|
||||
|
||||
class EmailChannel(object):
|
||||
identifier = 'email'
|
||||
|
||||
@classmethod
|
||||
def get_choices(self):
|
||||
return (('email', _('Email')),)
|
||||
|
||||
def send(self, announce):
|
||||
pass
|
|
@ -1,12 +1,12 @@
|
|||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .models import Announce, Category, Broadcast
|
||||
from .channels import get_channel_choices
|
||||
from .models import Announce, Category, Broadcast, channel_choices
|
||||
|
||||
|
||||
class AnnounceForm(forms.ModelForm):
|
||||
transport_channel = forms.MultipleChoiceField(required=False,
|
||||
choices=get_channel_choices(),
|
||||
choices=channel_choices,
|
||||
widget=forms.CheckboxSelectMultiple())
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -5,7 +5,10 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from ckeditor.fields import RichTextField
|
||||
|
||||
import channels
|
||||
channel_choices = (
|
||||
('mailto', _('Email')),
|
||||
('homepage', _('Homepage'))
|
||||
)
|
||||
|
||||
class Category(models.Model):
|
||||
name = models.CharField(max_length=64, blank=False, null=False)
|
||||
|
@ -51,7 +54,7 @@ class Announce(models.Model):
|
|||
class Broadcast(models.Model):
|
||||
announce = models.ForeignKey(Announce, verbose_name=_('announce'))
|
||||
channel = models.CharField(_('channel'), max_length=32,
|
||||
choices=channels.get_channel_choices(), blank=False)
|
||||
choices=channel_choices, blank=False)
|
||||
time = models.DateTimeField(_('sent time'), auto_now_add=True)
|
||||
result = models.TextField(_('result'), blank=True)
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ INSTALLED_APPS = (
|
|||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'rest_framework',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
|
|
|
@ -1,239 +0,0 @@
|
|||
import logging
|
||||
import smtplib
|
||||
import re
|
||||
try:
|
||||
import simplejson as json
|
||||
except:
|
||||
import json
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
def get_transport_choices(include=[], exclude=[]):
|
||||
for transport in get_transports():
|
||||
if include and transport.identifier not in include:
|
||||
continue
|
||||
if exclude and transport.identifier in exclude:
|
||||
continue
|
||||
for identifier, display_name in transport.get_choices():
|
||||
yield (identifier, display_name)
|
||||
|
||||
|
||||
def get_transport(identifier):
|
||||
transports = get_transports()
|
||||
for transport in transports:
|
||||
if identifier == transport.identifier:
|
||||
return transport
|
||||
return None
|
||||
|
||||
|
||||
__TRANSPORTS = None
|
||||
|
||||
|
||||
def get_transports():
|
||||
global __TRANSPORTS
|
||||
|
||||
if __TRANSPORTS is None:
|
||||
transports = []
|
||||
for class_path in app_settings.transport_modes:
|
||||
if not isinstance(class_path, basestring):
|
||||
class_path, kwargs = class_path
|
||||
else:
|
||||
kwargs = {}
|
||||
module_path, class_name = class_path.rsplit('.', 1)
|
||||
try:
|
||||
module = import_module(module_path)
|
||||
transports.append(getattr(module, class_name)(**kwargs))
|
||||
except (ImportError, AttributeError), e:
|
||||
raise ImportError('Unable to load transport class %s' % class_path, e)
|
||||
__TRANSPORTS = transports
|
||||
return __TRANSPORTS
|
||||
|
||||
|
||||
def get_template_list(template_list, **kwargs):
|
||||
'''Customize a template list given an announce category'''
|
||||
for template in template_list:
|
||||
yield template.format(**kwargs)
|
||||
|
||||
|
||||
def get_template(template_list, **kwargs):
|
||||
template_list = get_template_list(template_list, **kwargs)
|
||||
return select_template(template_list)
|
||||
|
||||
|
||||
class HomepageTransport(object):
|
||||
identifier = 'homepage'
|
||||
|
||||
def get_choices(self):
|
||||
return (('homepage', _('Homepage')),)
|
||||
|
||||
def get_identifier_from_subscription(self, subscription):
|
||||
return u'homepage'
|
||||
|
||||
|
||||
class SMSTransport(object):
|
||||
body_template_list = [
|
||||
'portail_citoyen_announces/{identifier}/body_{category}.txt',
|
||||
'portail_citoyen_announces/{identifier}/body.txt',
|
||||
'portail_citoyen_announces/body_{category}.txt',
|
||||
'portail_citoyen_announces/body.txt',
|
||||
]
|
||||
mobile_re = re.compile('^0[67][0-9]{8}$')
|
||||
|
||||
def __init__(self, url, from_mobile, login=None, password=None, identifier='sms', name=_('SMS')):
|
||||
self.url = url
|
||||
self.from_mobile = from_mobile
|
||||
self.login = login
|
||||
self.password = password
|
||||
self.identifier = identifier
|
||||
self.name = name
|
||||
|
||||
def get_choices(self):
|
||||
return ((self.identifier, self.name),)
|
||||
|
||||
def get_subscriptions(self, category):
|
||||
return models.Subscription.objects.filter(category=category,
|
||||
transport=self.identifier)
|
||||
|
||||
def get_sms(self, category):
|
||||
qs = self.get_subscriptions(category)
|
||||
for subscription in qs:
|
||||
sms = ''
|
||||
if subscription.identifier:
|
||||
sms = subscription.identifier
|
||||
elif subscription.user:
|
||||
sms = subscription.user.mobile
|
||||
if self.mobile_re.match(sms):
|
||||
yield sms
|
||||
|
||||
def send(self, announce):
|
||||
category = announce.category
|
||||
site = category.site
|
||||
body_template = get_template(self.body_template_list,
|
||||
category=category.identifier, identifier=self.identifier)
|
||||
ctx = Context({ 'announce': announce, 'site': site, 'category': category })
|
||||
body = body_template.render(ctx)
|
||||
sms = list(self.get_sms(category))
|
||||
logger.info(u'sending announce %(announce)s through %(mode)s to %(count)s emails',
|
||||
dict(announce=announce, mode=self.identifier, count=len(sms)))
|
||||
try:
|
||||
payload = {
|
||||
'message': body,
|
||||
'from': self.from_mobile,
|
||||
'to': list(sms),
|
||||
}
|
||||
response = requests.post(self.url, data=json.dumps(payload))
|
||||
json_response = response.json()
|
||||
if json_response['err'] != 0:
|
||||
msg = u'unable to send announce "%s" on site "%s": %s' % (announce,
|
||||
site, json_response)
|
||||
logger.error(msg)
|
||||
else:
|
||||
logger.info('announce %(announce)s sent succesfully',
|
||||
dict(announce=announce))
|
||||
msg = u'ok'
|
||||
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)
|
||||
models.Sent.objects.create(
|
||||
announce=announce,
|
||||
transport=self.identifier,
|
||||
result=msg)
|
||||
|
||||
def get_identifier_from_subscription(self, subscription):
|
||||
if subscription.user:
|
||||
return subscription.user.mobile
|
||||
return subscription.identifier
|
||||
|
||||
class EmailTransport(object):
|
||||
identifier = 'email'
|
||||
|
||||
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',
|
||||
]
|
||||
|
||||
def get_choices(self):
|
||||
return (('email', _('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)
|
||||
for subscription in qs:
|
||||
email = ''
|
||||
if subscription.identifier:
|
||||
email = subscription.identifier
|
||||
elif subscription.user:
|
||||
email = subscription.user.email
|
||||
yield email
|
||||
|
||||
def send(self, announce):
|
||||
category = announce.category
|
||||
site = category.site
|
||||
subject_template = get_template(self.subject_template_list,
|
||||
category=category.identifier, identifier=self.identifier)
|
||||
body_template = get_template(self.body_template_list,
|
||||
category=category.identifier, identifier=self.identifier)
|
||||
ctx = Context({ 'announce': announce, 'site': site, 'category': category })
|
||||
subject = subject_template.render(ctx).replace('\r', '').replace('\n', '')
|
||||
body = body_template.render(ctx)
|
||||
emails = list(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)
|
||||
|
||||
def get_identifier_from_subscription(self, subscription):
|
||||
if subscription.user:
|
||||
return subscription.user.email
|
||||
return subscription.identifier
|
|
@ -8,6 +8,7 @@ from .urls_utils import decorated_includes, manager_required
|
|||
from .views import homepage, atom
|
||||
|
||||
from manage_urls import urlpatterns as manage_urls
|
||||
from api_urls import urlpatterns as api_urls
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', homepage, name='home'),
|
||||
|
@ -15,7 +16,8 @@ urlpatterns = patterns('',
|
|||
url(r'^manage/', decorated_includes(manager_required,
|
||||
include(manage_urls))),
|
||||
url(r'^ckeditor/', include('ckeditor.urls')),
|
||||
url(r'^admin/', include(admin.site.urls))
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^api/', include(api_urls))
|
||||
)
|
||||
|
||||
if 'mellon' in settings.INSTALLED_APPS:
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
rm -f coverage.xml
|
||||
rm -f test_results.xml
|
||||
|
||||
pip install --upgrade tox
|
||||
pip install --upgrade pylint pylint-django
|
||||
tox -r
|
||||
test -f pylint.out && cp pylint.out pylint.out.prev
|
||||
(pylint -f parseable --rcfile /var/lib/jenkins/pylint.django.rc corbo/ | tee pylint.out) || /bin/true
|
||||
test -f pylint.out.prev && (diff pylint.out.prev pylint.out | grep '^[><]' | grep .py) || /bin/true
|
|
@ -1,3 +1,4 @@
|
|||
Django>=1.7, <1.8
|
||||
django-ckeditor<4.5.3
|
||||
djangorestframework
|
||||
-e git+http://repos.entrouvert.org/gadjo.git/#egg=gadjo
|
||||
|
|
3
setup.py
3
setup.py
|
@ -94,7 +94,8 @@ setup(
|
|||
'Programming Language :: Python :: 2',
|
||||
],
|
||||
install_requires=['django>=1.7, <1.8',
|
||||
'django-ckeditor<4.5.3'
|
||||
'django-ckeditor<4.5.3',
|
||||
'djangorestframework',
|
||||
'gadjo'
|
||||
],
|
||||
zip_safe=False,
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import pytest
|
||||
import django_webtest
|
||||
|
||||
@pytest.fixture
|
||||
def app(request):
|
||||
wtm = django_webtest.WebTestMixin()
|
||||
wtm._patch_settings()
|
||||
request.addfinalizer(wtm._unpatch_settings)
|
||||
return django_webtest.DjangoTestApp()
|
|
@ -0,0 +1,46 @@
|
|||
import pytest
|
||||
import json
|
||||
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from corbo.models import Category, Announce, Broadcast
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
CATEGORIES = ('Alerts', 'News')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def categories():
|
||||
categories = []
|
||||
for category in CATEGORIES:
|
||||
c, created = Category.objects.get_or_create(name=category)
|
||||
categories.append(c)
|
||||
return categories
|
||||
|
||||
@pytest.fixture
|
||||
def announces():
|
||||
announces = []
|
||||
for category in Category.objects.all():
|
||||
a = Announce.objects.create(category=category, title='By email')
|
||||
Broadcast.objects.create(announce=a, channel='mailto')
|
||||
announces.append(a)
|
||||
a = Announce.objects.create(category=category, title='On homepage')
|
||||
Broadcast.objects.create(announce=a, channel='homepage')
|
||||
announces.append(a)
|
||||
return announces
|
||||
|
||||
|
||||
def test_get_newsletters(app, categories, announces):
|
||||
resp = app.get(reverse('newsletters'), status=200)
|
||||
data = resp.json
|
||||
assert data['data']
|
||||
for category in data['data']:
|
||||
assert 'id' in category
|
||||
assert 'text' in category
|
||||
assert category['text'] in CATEGORIES
|
||||
assert 'transports' in category
|
||||
assert category['transports'] == [{'id': 'mailto', 'text': 'Email'},
|
||||
{'id': 'homepage', 'text': 'Homepage'}
|
||||
]
|
|
@ -0,0 +1,23 @@
|
|||
[tox]
|
||||
envlist = coverage-{django17,django18}
|
||||
|
||||
[testenv]
|
||||
usedevelop =
|
||||
coverage: True
|
||||
setenv =
|
||||
DJANGO_SETTINGS_MODULE=corbo.settings
|
||||
coverage: COVERAGE=--junitxml=test_results.xml --cov-report xml --cov=corbo/ --cov-config .coveragerc
|
||||
deps =
|
||||
django17: django>1.7,<1.8
|
||||
django18: django>=1.8,<1.9
|
||||
pytest-cov
|
||||
pytest-django
|
||||
pytest
|
||||
pytest-capturelog
|
||||
django-webtest
|
||||
django-ckeditor<4.5.3
|
||||
djangorestframework
|
||||
pylint==1.4.0
|
||||
astroid==1.3.2
|
||||
commands =
|
||||
py.test {env:COVERAGE:} {posargs:tests/}
|
Reference in New Issue