0001-sms-add-a-twilio-connector-19663.patch
passerelle/apps/twilio/migrations/0001_initial.py | ||
---|---|---|
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Generated by Django 1.11.18 on 2020-04-09 18:07 |
|
3 |
from __future__ import unicode_literals |
|
4 | ||
5 |
from django.db import migrations, models |
|
6 | ||
7 | ||
8 |
class Migration(migrations.Migration): |
|
9 | ||
10 |
initial = True |
|
11 | ||
12 |
dependencies = [ |
|
13 |
('base', '0018_smslog'), |
|
14 |
] |
|
15 | ||
16 |
operations = [ |
|
17 |
migrations.CreateModel( |
|
18 |
name='TwilioSMSGateway', |
|
19 |
fields=[ |
|
20 |
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|
21 |
('title', models.CharField(max_length=50, verbose_name='Title')), |
|
22 |
('slug', models.SlugField(unique=True, verbose_name='Identifier')), |
|
23 |
('description', models.TextField(verbose_name='Description')), |
|
24 |
('max_message_length', models.IntegerField(default=160, verbose_name='Maximum message length')), |
|
25 |
('account_sid', models.CharField(max_length=64, verbose_name='Account Sid')), |
|
26 |
('auth_token', models.CharField(max_length=64, verbose_name='Auth Token')), |
|
27 |
('users', models.ManyToManyField(blank=True, related_name='_twiliosmsgateway_users_+', related_query_name='+', to='base.ApiUser')), |
|
28 |
], |
|
29 |
options={ |
|
30 |
'verbose_name': 'Twilio', |
|
31 |
'db_table': 'sms_twilio', |
|
32 |
}, |
|
33 |
), |
|
34 |
] |
passerelle/apps/twilio/models.py | ||
---|---|---|
1 |
# passerelle - uniform access to multiple data sources and services |
|
2 |
# Copyright (C) 2020 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 |
import requests |
|
17 |
from requests.auth import HTTPBasicAuth |
|
18 | ||
19 |
from django.db import models |
|
20 |
from django.utils.translation import ugettext_lazy as _ |
|
21 | ||
22 |
from passerelle.utils.jsonresponse import APIError |
|
23 |
from passerelle.base.models import SMSResource |
|
24 | ||
25 | ||
26 |
class TwilioSMSGateway(SMSResource): |
|
27 |
account_sid = models.CharField(verbose_name=_('Account Sid'), max_length=64) |
|
28 |
auth_token = models.CharField(verbose_name=_('Auth Token'), max_length=64) |
|
29 | ||
30 |
manager_view_template_name = 'passerelle/manage/messages_service_view.html' |
|
31 | ||
32 |
class Meta: |
|
33 |
verbose_name = 'Twilio' |
|
34 |
db_table = 'sms_twilio' |
|
35 | ||
36 |
TEST_DEFAULTS = { |
|
37 |
'create_kwargs': { |
|
38 |
'account_sid': 'ACxxx', |
|
39 |
'auth_token': 'yyy', |
|
40 |
}, |
|
41 |
'test_vectors': [ |
|
42 |
{ |
|
43 |
'status_code': 400, |
|
44 |
'response': 'my error message', |
|
45 |
'result': { |
|
46 |
'err': 1, |
|
47 |
'err_desc': 'Twilio error: some destinations failed', |
|
48 |
'data': [ |
|
49 |
['+33688888888', "Twilio error: my error message"], |
|
50 |
['+33677777777', "Twilio error: my error message"], |
|
51 |
], |
|
52 |
} |
|
53 |
}, |
|
54 |
{ |
|
55 |
'status_code': 201, |
|
56 |
'result': { |
|
57 |
'err': 0, |
|
58 |
'data': None, |
|
59 |
} |
|
60 |
} |
|
61 |
], |
|
62 |
} |
|
63 |
URL = 'https://api.twilio.com/2010-04-01/Accounts' |
|
64 | ||
65 |
def send_msg(self, text, sender, destinations, **kwargs): |
|
66 |
"""Send a SMS using the Twilio provider""" |
|
67 | ||
68 |
url = '%s/%s/Messages.json' % (TwilioSMSGateway.URL, self.account_sid) |
|
69 |
auth = HTTPBasicAuth(self.account_sid, self.auth_token) |
|
70 |
results = [] |
|
71 |
for dest in destinations: |
|
72 |
params = { |
|
73 |
'Body': text, |
|
74 |
'From': sender, |
|
75 |
'To': dest |
|
76 |
} |
|
77 |
try: |
|
78 |
resp = self.requests.post(url, params, auth=auth) |
|
79 |
except requests.RequestException as e: |
|
80 |
results.append('Twilio error: POST failed, %s' % e) |
|
81 |
else: |
|
82 |
if resp.status_code != 201: |
|
83 |
results.append('Twilio error: %s' % resp.text) |
|
84 |
else: |
|
85 |
results.append(0) |
|
86 |
if any(results): |
|
87 |
raise APIError( |
|
88 |
'Twilio error: some destinations failed', |
|
89 |
data=list(zip(destinations, results))) |
|
90 |
return None |
passerelle/settings.py | ||
---|---|---|
149 | 149 |
'passerelle.apps.opengis', |
150 | 150 |
'passerelle.apps.orange', |
151 | 151 |
'passerelle.apps.ovh', |
152 | 152 |
'passerelle.apps.oxyd', |
153 | 153 |
'passerelle.apps.pastell', |
154 | 154 |
'passerelle.apps.phonecalls', |
155 | 155 |
'passerelle.apps.solis', |
156 | 156 |
'passerelle.apps.vivaticket', |
157 |
'passerelle.apps.twilio', |
|
157 | 158 |
# backoffice templates and static |
158 | 159 |
'gadjo', |
159 | 160 |
) |
160 | 161 | |
161 | 162 |
# disable some applications for now |
162 | 163 |
PASSERELLE_APP_BDP_ENABLED = False |
163 | 164 |
PASSERELLE_APP_GDC_ENABLED = False |
164 | 165 |
PASSERELLE_APP_PASTELL_ENABLED = False |
tests/test_sms.py | ||
---|---|---|
51 | 51 |
assert result.json['err_desc'].startswith('Payload error: ') |
52 | 52 | |
53 | 53 |
payload = { |
54 | 54 |
'message': 'hello', |
55 | 55 |
'from': '+33699999999', |
56 | 56 |
'to': ['+33688888888', '+33677777777'], |
57 | 57 |
} |
58 | 58 |
for test_vector in getattr(connector, 'TEST_DEFAULTS', {}).get('test_vectors', []): |
59 |
with utils.mock_url(connector.URL, test_vector['response']): |
|
59 |
with utils.mock_url( |
|
60 |
connector.URL, |
|
61 |
test_vector.get('response', ''), |
|
62 |
test_vector.get('status_code', 200)): |
|
63 | ||
60 | 64 |
result = app.post_json(path, params=payload) |
61 | 65 |
for key, value in test_vector['result'].items(): |
62 | 66 |
assert key in result.json |
63 | 67 |
assert result.json[key] == value |
64 | 68 | |
65 | 69 | |
66 | 70 |
def test_manage_views(admin_user, app, connector): |
67 | 71 |
url = '/%s/%s/' % (connector.get_connector_slug(), connector.slug) |
68 |
- |