Projet

Général

Profil

0001-add-sms-delivery-12665.patch

Serghei Mihai, 02 novembre 2017 01:28

Télécharger (12,9 ko)

Voir les différences:

Subject: [PATCH] add sms delivery (12665)

 corbo/models.py                                  |  13 ++-
 corbo/settings.py                                |   6 ++
 corbo/utils.py                                   |  27 ++++++
 corbo/views.py                                   |   2 +-
 requirements.txt                                 |   1 +
 tests/test_api.py                                |   2 +-
 tests/{test_emailing.py => test_broadcasting.py} | 100 ++++++++++++++++++++++-
 7 files changed, 144 insertions(+), 7 deletions(-)
 rename tests/{test_emailing.py => test_broadcasting.py} (55%)
corbo/models.py
18 18

  
19 19
channel_choices = (
20 20
    ('mailto', _('Email')),
21
    ('sms', _('SMS')),
21 22
)
22 23

  
23 24

  
......
156 157

  
157 158
    def send(self):
158 159
        destinations = [s.identifier for s in self.announce.category.subscription_set.all() if s.identifier]
159
        self.delivery_count = utils.send_email(self.announce.title, self.announce.text, destinations, category_id=self.announce.category.pk)
160
        emails = []
161
        mobile_numbers = []
162
        for destination in destinations:
163
            if destination.startswith('mailto:'):
164
                emails.append(destination.replace('mailto:', ''))
165
            elif destination.startswith('sms:'):
166
                mobile_numbers.append(destination.replace('sms:', ''))
167
        self.delivery_count += utils.send_email(self.announce.title, self.announce.text,
168
                                    emails, category_id=self.announce.category.pk)
169

  
170
        self.delivery_count += utils.send_sms(self.announce.text, mobile_numbers)
160 171
        self.deliver_time = timezone.now()
161 172
        self.save()
162 173

  
corbo/settings.py
162 162
# default site
163 163
SITE_BASE_URL = 'http://localhost'
164 164

  
165
# default SMS Gateway
166
SMS_GATEWAY_URL = None
167

  
168
# sms expeditor
169
SMS_EXPEDITOR = 'Corbo'
170

  
165 171
local_settings_file = os.environ.get('CORBO_SETTINGS_FILE',
166 172
        os.path.join(os.path.dirname(__file__), 'local_settings.py'))
167 173
if os.path.exists(local_settings_file):
corbo/utils.py
16 16

  
17 17
import os
18 18
import logging
19
import requests
19 20
import urlparse
20 21
import hashlib
21 22
from html2text import HTML2Text
......
72 73
            logger.warning('Error occured while sending announce "%s" to %s.',
73 74
                           title, dest)
74 75
    return total_sent
76

  
77
def send_sms(content, destinations):
78
    from django.conf import settings
79
    logger = logging.getLogger(__name__)
80
    sent = 0
81
    if not destinations:
82
        return sent
83
    if settings.SMS_GATEWAY_URL:
84
        # remove all HTML formatting from content
85
        html_content = etree.HTML(content)
86
        data = {'to': destinations,
87
                'message': etree.tostring(html_content, method='text'),
88
                'from': settings.SMS_EXPEDITOR}
89
        try:
90
            response = requests.post(settings.SMS_GATEWAY_URL, json=data)
91
            response.raise_for_status()
92
            if not response.json()['err']:
93
                # if no error returned by SMS gateway presume the that content
94
                # was delivered to all destinations
95
                sent = len(destinations)
96
            else:
97
                logger.warning('Error occured while sending sms: %s', response.json()['err_desc'])
98
        except requests.RequestException as e:
99
            logger.warning('Failed to reach SMS gateway: %s', e)
100
            return sent
101
    return sent
corbo/views.py
165 165
        data = signing.loads(self.kwargs['unsubscription_token'])
166 166
        try:
167 167
            return models.Subscription.objects.get(category__pk=data['category'],
168
                                                   identifier=data['identifier'])
168
                                                   identifier='mailto:%(identifier)s' % data)
169 169
        except models.Subscription.DoesNotExist:
170 170
            raise Http404
171 171

  
requirements.txt
6 6
feedparser
7 7
requests
8 8
lxml
9
nltk
9 10
-e git+http://repos.entrouvert.org/gadjo.git/#egg=gadjo
tests/test_api.py
59 59
        assert category['id'] in [slugify(c) for c in CATEGORIES]
60 60
        assert category['text'] in CATEGORIES
61 61
        assert 'transports' in category
62
        assert category['transports'] == [{'id': 'mailto', 'text': 'Email'}]
62
        assert category['transports'] == [{'id': 'mailto', 'text': 'Email'}, {'id': 'sms', 'text': 'SMS'}]
63 63

  
64 64

  
65 65
def test_get_subscriptions_by_email(app, categories, announces, user):
tests/test_emailing.py → tests/test_broadcasting.py
3 3
import os
4 4
import re
5 5
import urllib
6
import logging
6 7
import mock
8
import random
9
import requests
7 10

  
8 11
from django.core.urlresolvers import reverse
9 12
from django.core import mail, signing
10 13
from django.utils import timezone
11 14
from django.core.files.storage import DefaultStorage
12 15
from django.utils.text import slugify
16
from django.test import override_settings
13 17

  
14 18
from corbo.models import Category, Announce, Subscription, Broadcast
15 19
from corbo.models import channel_choices
......
18 22

  
19 23
CATEGORIES = (u'Alerts', u'News')
20 24

  
25
def get_random_number():
26
    number_generator = range(10)
27
    random.shuffle(number_generator)
28
    number = ''.join(map(str, number_generator))
29
    return number
30

  
21 31

  
22 32
@pytest.fixture
23 33
def categories():
......
57 67
    for category in categories:
58 68
        uuid = uuid4()
59 69
        Subscription.objects.create(category=category,
60
                                    identifier='%s@example.net' % uuid, uuid=uuid)
70
                                    identifier='mailto:%s@example.net' % uuid, uuid=uuid)
61 71
    for i, announce in enumerate(announces):
62 72
        broadcast = Broadcast.objects.get(announce=announce)
63 73
        broadcast.send()
......
72 82
        announce.save()
73 83
        uuid = uuid4()
74 84
        Subscription.objects.create(category=announce.category,
75
                                    identifier='%s@example.net' % uuid, uuid=uuid)
85
                                    identifier='mailto:%s@example.net' % uuid, uuid=uuid)
76 86
        broadcast = Broadcast.objects.get(announce=announce)
77 87
        broadcast.send()
78 88
        assert broadcast.delivery_count
......
94 104
        announce.save()
95 105
        uuid = uuid4()
96 106
        Subscription.objects.create(category=announce.category,
97
                                    identifier='%s@example.net' % uuid, uuid=uuid)
107
                                    identifier='mailto:%s@example.net' % uuid, uuid=uuid)
98 108
        broadcast = Broadcast.objects.get(announce=announce)
99 109
        mocked_get.return_value = mock.Mock(status_code=200,
100 110
                                            headers={'content-type': 'image/png'},
......
139 149
                assert unsubscription_link in mail.outbox[index].text
140 150
                assert unsubscription_link_sentinel != unsubscription_link
141 151
                assert signing.loads(signature) == {
142
                    'category': announce.category.pk, 'identifier': destination.identifier}
152
                    'category': announce.category.pk, 'identifier': destination.identifier.replace(scheme, '')}
143 153
                unsubscription_link_sentinel = unsubscription_link
144 154

  
145 155
                # make sure the uri schema is not in the page
146 156
                resp = app.get(unsubscription_link)
147 157
                assert scheme not in resp.content
158

  
159
def test_send_sms_with_no_gateway_defined(app, categories, announces):
160
    for category in categories:
161
        uuid = uuid4()
162
        Subscription.objects.create(category=category,
163
                                    identifier='sms:%s' % get_random_number(), uuid=uuid)
164
    for i, announce in enumerate(announces):
165
        broadcast = Broadcast.objects.get(announce=announce)
166
        broadcast.send()
167
        assert broadcast.delivery_count == 0
168

  
169
@mock.patch('corbo.utils.requests.post')
170
def test_send_sms_with_gateway_api_error(mocked_post, app, categories, announces, caplog):
171
    for category in categories:
172
        for i in range(3):
173
            uuid = uuid4()
174
            Subscription.objects.create(category=category,
175
                            identifier='sms:%s' % get_random_number(), uuid=uuid)
176
    for i, announce in enumerate(announces):
177
        broadcast = Broadcast.objects.get(announce=announce)
178
        with override_settings(SMS_GATEWAY_URL='http://sms.gateway'):
179
            mocked_response = mock.Mock()
180
            mocked_response.json.return_value = {'err': 1, 'data': None,
181
                            'err_desc': 'Payload error: missing "message" in JSON payload'}
182
            mocked_post.return_value = mocked_response
183
            broadcast.send()
184
            assert broadcast.delivery_count == 0
185
            records = caplog.records()
186
            assert len(records) == 1 + i
187
            for record in records:
188
                assert record.name == 'corbo.utils'
189
                assert record.levelno == logging.WARNING
190
                assert record.getMessage() == 'Error occured while sending sms: Payload error: missing "message" in JSON payload'
191

  
192
@mock.patch('corbo.utils.requests.post')
193
def test_send_sms_with_gateway_connection_error(mocked_post, app, categories, announces, caplog):
194
    for category in categories:
195
        for i in range(3):
196
            uuid = uuid4()
197
            Subscription.objects.create(category=category,
198
                            identifier='sms:%s' % get_random_number(), uuid=uuid)
199
    for i, announce in enumerate(announces):
200
        broadcast = Broadcast.objects.get(announce=announce)
201
        with override_settings(SMS_GATEWAY_URL='http://sms.gateway'):
202
            mocked_response = mock.Mock()
203
            def mocked_requests_connection_error(*args, **kwargs):
204
                raise requests.ConnectionError('unreachable')
205
            mocked_post.side_effect = mocked_requests_connection_error
206
            mocked_post.return_value = mocked_response
207
            broadcast.send()
208
            assert broadcast.delivery_count == 0
209
            records = caplog.records()
210
            assert len(records) == 1 + i
211
            for record in records:
212
                assert record.name == 'corbo.utils'
213
                assert record.levelno == logging.WARNING
214
                assert record.getMessage() == 'Failed to reach SMS gateway: unreachable'
215

  
216
@mock.patch('corbo.utils.requests.post')
217
def test_send_sms(mocked_post, app, categories, announces):
218
    for category in categories:
219
        for i in range(3):
220
            uuid = uuid4()
221
            Subscription.objects.create(category=category,
222
                            identifier='sms:%s' % get_random_number(), uuid=uuid)
223
    for announce in announces:
224
        broadcast = Broadcast.objects.get(announce=announce)
225
        with override_settings(SMS_GATEWAY_URL='http://sms.gateway'):
226
            mocked_response = mock.Mock()
227
            mocked_response.json.return_value = {'err': 0, 'err_desc': None, 'data': 'gateway response'}
228

  
229
    for announce in announces:
230
        broadcast = Broadcast.objects.get(announce=announce)
231
        with override_settings(SMS_GATEWAY_URL='http://sms.gateway'):
232
            mocked_response = mock.Mock()
233
            mocked_response.json.return_value = {'err': 0, 'err_desc': None, 'data': 'gateway response'}
234
            mocked_post.return_value = mocked_response
235
            broadcast.send()
236
            assert mocked_post.call_args[0][0] == 'http://sms.gateway'
237
            assert mocked_post.call_args[1]['json']['from'] == 'Corbo'
238
            assert isinstance(mocked_post.call_args[1]['json']['to'], list)
239
            assert broadcast.delivery_count == 3
148
-