add sms delivery (#12665)

This commit is contained in:
Serghei Mihai 2017-08-07 16:50:47 +02:00
parent f46f80e445
commit 9f83201165
5 changed files with 157 additions and 6 deletions

View File

@ -18,6 +18,7 @@ from . import utils
channel_choices = (
('mailto', _('Email')),
('sms', _('SMS')),
)
@ -150,16 +151,32 @@ class Broadcast(models.Model):
def __unicode__(self):
if self.deliver_time:
return u'announce {id} delivered via at {time}'.format(
return u'announce {id} delivered at {time}'.format(
id=self.announce.id, time=self.deliver_time)
return u'announce {id} to deliver'.format(id=self.announce.id)
def filter_destinations(self, destinations, prefix):
return [dest for dest in destinations if dest.startswith('%s:' % prefix)]
def send_sms(self, title, content, destinations, category_id):
return utils.send_sms(content, destinations)
def send_mailto(self, title, content, destinations, category_id):
return utils.send_email(title, content, destinations, category_id)
def send(self):
total_sent = 0
destinations = [s.identifier for s in self.announce.category.subscription_set.all() if s.identifier]
self.delivery_count = utils.send_email(self.announce.title, self.announce.text, destinations, category_id=self.announce.category.pk)
for channel_name, verbose_name in channel_choices:
action = getattr(self, 'send_' + channel_name)
filtered_destinations = self.filter_destinations(destinations, channel_name)
total_sent += action(self.announce.title, self.announce.text,
filtered_destinations, self.announce.category.id)
self.delivery_count = total_sent
self.deliver_time = timezone.now()
self.save()
class Meta:
verbose_name = _('sent')
ordering = ('-deliver_time',)

View File

@ -162,6 +162,12 @@ REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES'] = (
# default site
SITE_BASE_URL = 'http://localhost'
# default SMS Gateway
SMS_GATEWAY_URL = None
# sms expeditor
SMS_EXPEDITOR = 'Corbo'
local_settings_file = os.environ.get('CORBO_SETTINGS_FILE',
os.path.join(os.path.dirname(__file__), 'local_settings.py'))
if os.path.exists(local_settings_file):

View File

@ -16,6 +16,7 @@
import os
import logging
import requests
import urlparse
import hashlib
from html2text import HTML2Text
@ -72,3 +73,33 @@ def send_email(title, content, destinations, category_id):
logger.warning('Error occured while sending announce "%s" to %s.',
title, dest)
return total_sent
def send_sms(content, destinations):
from django.conf import settings
logger = logging.getLogger(__name__)
sent = 0
if not destinations:
return sent
if settings.SMS_GATEWAY_URL:
# remove all HTML formatting from content
html_content = etree.HTML(content)
# remove identifier prefix
destinations = [d.replace('sms:', '') for d in destinations]
data = {'to': destinations,
'message': etree.tostring(html_content, method='text'),
'from': settings.SMS_EXPEDITOR}
try:
response = requests.post(settings.SMS_GATEWAY_URL, json=data)
response.raise_for_status()
if not response.json()['err']:
# if no error returned by SMS gateway presume the that content
# was delivered to all destinations
sent = len(destinations)
else:
logger.warning('Error occured while sending sms: %s', response.json()['err_desc'])
except requests.RequestException as e:
logger.warning('Failed to reach SMS gateway: %s', e)
return sent
else:
logger.error('SMS send requested but no SMS gateway defined.')
return sent

View File

@ -59,7 +59,7 @@ def test_get_newsletters(app, categories, announces, user):
assert category['id'] in [slugify(c) for c in CATEGORIES]
assert category['text'] in CATEGORIES
assert 'transports' in category
assert category['transports'] == [{'id': 'mailto', 'text': 'Email'}]
assert category['transports'] == [{'id': 'mailto', 'text': 'Email'}, {'id': 'sms', 'text': 'SMS'}]
def test_get_subscriptions_by_email(app, categories, announces, user):

View File

@ -3,13 +3,17 @@ from uuid import uuid4
import os
import re
import urllib
import logging
import mock
import random
import requests
from django.core.urlresolvers import reverse
from django.core import mail, signing
from django.utils import timezone
from django.core.files.storage import DefaultStorage
from django.utils.text import slugify
from django.test import override_settings
from corbo.models import Category, Announce, Subscription, Broadcast
from corbo.models import channel_choices
@ -18,6 +22,12 @@ pytestmark = pytest.mark.django_db
CATEGORIES = (u'Alerts', u'News')
def get_random_number():
number_generator = range(10)
random.shuffle(number_generator)
number = ''.join(map(str, number_generator))
return number
@pytest.fixture
def categories():
@ -57,7 +67,7 @@ def test_send_email(app, categories, announces, mailoutbox):
for category in categories:
uuid = uuid4()
Subscription.objects.create(category=category,
identifier='%s@example.net' % uuid, uuid=uuid)
identifier='mailto:%s@example.net' % uuid, uuid=uuid)
for i, announce in enumerate(announces):
broadcast = Broadcast.objects.get(announce=announce)
broadcast.send()
@ -72,7 +82,7 @@ def test_check_inline_css(app, categories, announces, mailoutbox):
announce.save()
uuid = uuid4()
Subscription.objects.create(category=announce.category,
identifier='%s@example.net' % uuid, uuid=uuid)
identifier='mailto:%s@example.net' % uuid, uuid=uuid)
broadcast = Broadcast.objects.get(announce=announce)
broadcast.send()
assert broadcast.delivery_count
@ -94,7 +104,7 @@ def test_check_inline_images(mocked_get, app, categories, announces, mailoutbox)
announce.save()
uuid = uuid4()
Subscription.objects.create(category=announce.category,
identifier='%s@example.net' % uuid, uuid=uuid)
identifier='mailto:%s@example.net' % uuid, uuid=uuid)
broadcast = Broadcast.objects.get(announce=announce)
mocked_get.return_value = mock.Mock(status_code=200,
headers={'content-type': 'image/png'},
@ -145,3 +155,90 @@ def test_unsubscription_link(app, categories, announces, custom_mailoutbox):
# make sure the uri schema is not in the page
resp = app.get(unsubscription_link)
assert scheme not in resp.content
def test_send_sms_with_no_gateway_defined(app, categories, announces, caplog):
for category in categories:
uuid = uuid4()
Subscription.objects.create(category=category,
identifier='sms:%s' % get_random_number(), uuid=uuid)
for i, announce in enumerate(announces):
broadcast = Broadcast.objects.get(announce=announce)
broadcast.send()
assert broadcast.delivery_count == 0
records = caplog.records()
for record in records:
assert record.name == 'corbo.utils'
assert record.levelno == logging.ERROR
assert record.getMessage() == 'SMS send requested but no SMS gateway defined.'
@mock.patch('corbo.utils.requests.post')
def test_send_sms_with_gateway_api_error(mocked_post, app, categories, announces, caplog):
for category in categories:
for i in range(3):
uuid = uuid4()
Subscription.objects.create(category=category,
identifier='sms:%s' % get_random_number(), uuid=uuid)
for i, announce in enumerate(announces):
broadcast = Broadcast.objects.get(announce=announce)
with override_settings(SMS_GATEWAY_URL='http://sms.gateway'):
mocked_response = mock.Mock()
mocked_response.json.return_value = {'err': 1, 'data': None,
'err_desc': 'Payload error: missing "message" in JSON payload'}
mocked_post.return_value = mocked_response
broadcast.send()
assert broadcast.delivery_count == 0
records = caplog.records()
assert len(records) == 1 + i
for record in records:
assert record.name == 'corbo.utils'
assert record.levelno == logging.WARNING
assert record.getMessage() == 'Error occured while sending sms: Payload error: missing "message" in JSON payload'
@mock.patch('corbo.utils.requests.post')
def test_send_sms_with_gateway_connection_error(mocked_post, app, categories, announces, caplog):
for category in categories:
for i in range(3):
uuid = uuid4()
Subscription.objects.create(category=category,
identifier='sms:%s' % get_random_number(), uuid=uuid)
for i, announce in enumerate(announces):
broadcast = Broadcast.objects.get(announce=announce)
with override_settings(SMS_GATEWAY_URL='http://sms.gateway'):
mocked_response = mock.Mock()
def mocked_requests_connection_error(*args, **kwargs):
raise requests.ConnectionError('unreachable')
mocked_post.side_effect = mocked_requests_connection_error
mocked_post.return_value = mocked_response
broadcast.send()
assert broadcast.delivery_count == 0
records = caplog.records()
assert len(records) == 1 + i
for record in records:
assert record.name == 'corbo.utils'
assert record.levelno == logging.WARNING
assert record.getMessage() == 'Failed to reach SMS gateway: unreachable'
@mock.patch('corbo.utils.requests.post')
def test_send_sms(mocked_post, app, categories, announces):
for category in categories:
for i in range(3):
uuid = uuid4()
Subscription.objects.create(category=category,
identifier='sms:%s' % get_random_number(), uuid=uuid)
for announce in announces:
broadcast = Broadcast.objects.get(announce=announce)
with override_settings(SMS_GATEWAY_URL='http://sms.gateway'):
mocked_response = mock.Mock()
mocked_response.json.return_value = {'err': 0, 'err_desc': None, 'data': 'gateway response'}
for announce in announces:
broadcast = Broadcast.objects.get(announce=announce)
with override_settings(SMS_GATEWAY_URL='http://sms.gateway'):
mocked_response = mock.Mock()
mocked_response.json.return_value = {'err': 0, 'err_desc': None, 'data': 'gateway response'}
mocked_post.return_value = mocked_response
broadcast.send()
assert mocked_post.call_args[0][0] == 'http://sms.gateway'
assert mocked_post.call_args[1]['json']['from'] == 'Corbo'
assert isinstance(mocked_post.call_args[1]['json']['to'], list)
assert broadcast.delivery_count == 3