add sms delivery (#12665)
This commit is contained in:
parent
f46f80e445
commit
9f83201165
|
@ -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',)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
Reference in New Issue