0001-sms-factorize-clean_number-call-42427.patch
passerelle/apps/choosit/models.py | ||
---|---|---|
7 | 7 |
from django.db import models |
8 | 8 | |
9 | 9 |
from passerelle.utils.jsonresponse import APIError |
10 | 10 |
from passerelle.base.models import SMSResource |
11 | 11 | |
12 | 12 | |
13 | 13 |
class ChoositSMSGateway(SMSResource): |
14 | 14 |
key = models.CharField(verbose_name=_('Key'), max_length=64) |
15 |
default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3, |
|
16 |
default=u'33') |
|
17 |
default_trunk_prefix = models.CharField(verbose_name=_('Default trunk prefix'), max_length=2, |
|
18 |
default=u'0') |
|
19 |
# FIXME: add regexp field, to check destination and from format |
|
20 | 15 | |
21 | 16 |
TEST_DEFAULTS = { |
22 | 17 |
'create_kwargs': { |
23 | 18 |
'key': '1234', |
24 | 19 |
}, |
25 | 20 |
'test_vectors': [ |
26 | 21 |
{ |
27 | 22 |
'response': '', |
... | ... | |
71 | 66 | |
72 | 67 |
@classmethod |
73 | 68 |
def get_verbose_name(cls): |
74 | 69 |
return cls._meta.verbose_name |
75 | 70 | |
76 | 71 |
def send_msg(self, text, sender, destinations, **kwargs): |
77 | 72 |
"""Send a SMS using the Choosit provider""" |
78 | 73 |
# from http://sms.choosit.com/documentation_technique.html |
79 |
# unfortunately it lacks a batch API... |
|
80 |
destinations = self.clean_numbers(destinations, |
|
81 |
self.default_country_code, |
|
82 |
self.default_trunk_prefix) |
|
74 | ||
83 | 75 |
results = [] |
84 | 76 |
for dest in destinations: |
85 | 77 |
params = { |
86 | 78 |
'key': self.key, |
87 | 79 |
'recipient': dest, |
88 | 80 |
'content': text[:160], |
89 | 81 |
} |
90 | 82 |
data = {'data': json.dumps(params)} |
passerelle/apps/mobyt/models.py | ||
---|---|---|
13 | 13 |
('l', _('sms direct')), |
14 | 14 |
('ll', _('sms low-cost')), |
15 | 15 |
('n', _('sms top')), |
16 | 16 |
) |
17 | 17 |
username = models.CharField(verbose_name=_('Username'), max_length=64) |
18 | 18 |
password = models.CharField(verbose_name=_('Password'), max_length=64) |
19 | 19 |
quality = models.CharField(max_length=4, choices=MESSAGES_QUALITIES, default='l', |
20 | 20 |
verbose_name=_('Message quality')) |
21 |
default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3, |
|
22 |
default=u'33') |
|
23 |
default_trunk_prefix = models.CharField(verbose_name=_('Default trunk prefix'), max_length=2, |
|
24 |
default=u'0') |
|
25 |
# FIXME: add regexp field, to check destination and from format |
|
26 | 21 | |
27 | 22 |
manager_view_template_name = 'passerelle/manage/messages_service_view.html' |
28 | 23 | |
29 | 24 |
TEST_DEFAULTS = { |
30 | 25 |
'create_kwargs': { |
31 | 26 |
'username': 'john', |
32 | 27 |
'password': 'doe', |
33 | 28 |
}, |
... | ... | |
45 | 40 |
} |
46 | 41 | |
47 | 42 |
class Meta: |
48 | 43 |
verbose_name = 'Mobyt' |
49 | 44 |
db_table = 'sms_mobyt' |
50 | 45 | |
51 | 46 |
def send_msg(self, text, sender, destinations, **kwargs): |
52 | 47 |
"""Send a SMS using the Mobyt provider""" |
53 |
# unfortunately it lacks a batch API... |
|
54 |
destinations = self.clean_numbers(destinations, |
|
55 |
self.default_country_code, |
|
56 |
self.default_trunk_prefix) |
|
48 | ||
57 | 49 |
rcpt = ','.join(destinations) |
58 | 50 |
params = { |
59 | 51 |
'user': self.username, |
60 | 52 |
'pass': self.password, |
61 | 53 |
'rcpt': rcpt, |
62 | 54 |
'data': text, |
63 | 55 |
'sender': sender, |
64 | 56 |
'qty': self.quality, |
passerelle/apps/orange/models.py | ||
---|---|---|
42 | 42 |
raise OrangeError('Orange returned Invalid JSON content: %s' % response.text) |
43 | 43 | |
44 | 44 | |
45 | 45 |
class OrangeSMSGateway(SMSResource): |
46 | 46 |
username = models.CharField(verbose_name=_('Identifier'), max_length=64) |
47 | 47 |
password = models.CharField(verbose_name=_('Password'), max_length=64) |
48 | 48 |
groupname = models.CharField(verbose_name=_('Group'), max_length=64) |
49 | 49 | |
50 |
default_country_code = models.CharField( |
|
51 |
verbose_name=_('Default country code'), max_length=3, default='33') |
|
52 |
default_trunk_prefix = models.CharField( |
|
53 |
verbose_name=_('Default trunk prefix'), max_length=2, default='0') |
|
54 | ||
55 | 50 |
manager_view_template_name = 'passerelle/manage/messages_service_view.html' |
56 | 51 | |
57 | 52 |
class Meta: |
58 | 53 |
verbose_name = _('Orange') |
59 | 54 |
db_table = 'sms_orange' |
60 | 55 | |
61 | 56 |
def get_access_token(self): |
62 | 57 |
headers = {'content-type': 'application/x-www-form-urlencoded'} |
... | ... | |
103 | 98 |
URL_DIFFUSION % group_id, json=payload, headers=headers) |
104 | 99 |
if response.status_code != 201: |
105 | 100 |
raise OrangeError('Orange fails to send SMS: %s, %s' % ( |
106 | 101 |
response.status_code, response.text)) |
107 | 102 |
return get_json(response) |
108 | 103 | |
109 | 104 |
def send_msg(self, text, sender, destinations, **kwargs): |
110 | 105 |
'''Send a SMS using the Orange provider''' |
111 |
destinations = self.clean_numbers( |
|
112 |
destinations, self.default_country_code, self.default_trunk_prefix) |
|
106 | ||
113 | 107 |
access_token = self.get_access_token() |
114 | 108 |
group_id = self.group_id_from_name(access_token) |
115 | 109 |
response = self.diffusion(access_token, group_id, destinations, text) |
116 | 110 |
return response |
passerelle/apps/ovh/models.py | ||
---|---|---|
23 | 23 |
) |
24 | 24 |
account = models.CharField(verbose_name=_('Account'), max_length=64) |
25 | 25 |
username = models.CharField(verbose_name=_('Username'), max_length=64) |
26 | 26 |
password = models.CharField(verbose_name=_('Password'), max_length=64) |
27 | 27 |
msg_class = models.IntegerField(choices=MESSAGES_CLASSES, default=1, |
28 | 28 |
verbose_name=_('Message class')) |
29 | 29 |
credit_threshold_alert = models.PositiveIntegerField(verbose_name=_('Credit alert threshold'), |
30 | 30 |
default=100) |
31 |
default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3, |
|
32 |
default=u'33') |
|
33 |
default_trunk_prefix = models.CharField(verbose_name=_('Default trunk prefix'), max_length=2, |
|
34 |
default=u'0') |
|
35 | 31 |
credit_left = models.PositiveIntegerField(verbose_name=_('Credit left'), default=0) |
36 |
# FIXME: add regexp field, to check destination and from format |
|
37 | 32 | |
38 | 33 |
manager_view_template_name = 'passerelle/manage/messages_service_view.html' |
39 | 34 | |
40 | 35 |
TEST_DEFAULTS = { |
41 | 36 |
'create_kwargs': { |
42 | 37 |
'account': '1234', |
43 | 38 |
'username': 'john', |
44 | 39 |
'password': 'doe', |
... | ... | |
77 | 72 |
} |
78 | 73 | |
79 | 74 |
class Meta: |
80 | 75 |
verbose_name = 'OVH' |
81 | 76 |
db_table = 'sms_ovh' |
82 | 77 | |
83 | 78 |
def send_msg(self, text, sender, destinations, **kwargs): |
84 | 79 |
"""Send a SMS using the OVH provider""" |
85 |
destinations = self.clean_numbers(destinations, |
|
86 |
self.default_country_code, |
|
87 |
self.default_trunk_prefix) |
|
88 | 80 | |
89 | 81 |
text = force_text(text).encode('utf-8') |
90 | 82 |
to = ','.join(destinations) |
91 | 83 |
params = { |
92 | 84 |
'account': self.account.encode('utf-8'), |
93 | 85 |
'login': self.username.encode('utf-8'), |
94 | 86 |
'password': self.password.encode('utf-8'), |
95 | 87 |
'from': sender.encode('utf-8'), |
passerelle/apps/oxyd/models.py | ||
---|---|---|
6 | 6 |
from passerelle.utils.jsonresponse import APIError |
7 | 7 |
from passerelle.base.models import SMSResource |
8 | 8 |
from django.utils.translation import ugettext_lazy as _ |
9 | 9 | |
10 | 10 | |
11 | 11 |
class OxydSMSGateway(SMSResource): |
12 | 12 |
username = models.CharField(verbose_name=_('Username'), max_length=64) |
13 | 13 |
password = models.CharField(verbose_name=_('Password'), max_length=64) |
14 |
default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3, |
|
15 |
default=u'33') |
|
16 |
default_trunk_prefix = models.CharField(verbose_name=_('Default trunk prefix'), max_length=2, |
|
17 |
default=u'0') |
|
18 |
# FIXME: add regexp field, to check destination and from format |
|
19 | 14 | |
20 | 15 |
manager_view_template_name = 'passerelle/manage/messages_service_view.html' |
21 | 16 | |
22 | 17 |
TEST_DEFAULTS = { |
23 | 18 |
'create_kwargs': { |
24 | 19 |
'username': 'john', |
25 | 20 |
'password': 'doe', |
26 | 21 |
}, |
... | ... | |
48 | 43 |
URL = 'http://sms.oxyd.fr/send.php' |
49 | 44 | |
50 | 45 |
class Meta: |
51 | 46 |
verbose_name = 'Oxyd' |
52 | 47 |
db_table = 'sms_oxyd' |
53 | 48 | |
54 | 49 |
def send_msg(self, text, sender, destinations, **kwargs): |
55 | 50 |
"""Send a SMS using the Oxyd provider""" |
56 |
# unfortunately it lacks a batch API... |
|
57 |
destinations = self.clean_numbers(destinations, |
|
58 |
self.default_country_code, |
|
59 |
self.default_trunk_prefix) |
|
51 | ||
60 | 52 |
results = [] |
61 | 53 |
for dest in destinations: |
62 | 54 |
params = { |
63 | 55 |
'id': self.username, |
64 | 56 |
'pass': self.password, |
65 | 57 |
'num': dest, |
66 | 58 |
'sms': text.encode('utf-8'), |
67 | 59 |
'flash': '0', |
passerelle/base/models.py | ||
---|---|---|
913 | 913 | |
914 | 914 | |
915 | 915 |
class SMSResource(BaseResource): |
916 | 916 |
category = _('SMS Providers') |
917 | 917 |
documentation_url = 'https://doc-publik.entrouvert.com/admin-fonctionnel/les-tutos/configuration-envoi-sms/' |
918 | 918 | |
919 | 919 |
_can_send_messages_description = _('Sending messages is limited to the following API users:') |
920 | 920 | |
921 |
default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3, |
|
922 |
default=u'33') |
|
923 |
default_trunk_prefix = models.CharField(verbose_name=_('Default trunk prefix'), max_length=2, |
|
924 |
default=u'0') |
|
921 | 925 |
max_message_length = models.IntegerField(_('Maximum message length'), default=160) |
922 | 926 | |
923 |
@classmethod |
|
924 |
def clean_numbers(cls, destinations, default_country_code='33', |
|
925 |
default_trunk_prefix='0'): # Yeah France first ! |
|
927 |
def clean_numbers(self, destinations): |
|
928 | ||
926 | 929 |
numbers = [] |
927 | 930 |
for dest in destinations: |
928 | 931 |
# most gateways needs the number prefixed by the country code, this is |
929 | 932 |
# really unfortunate. |
930 | 933 |
dest = dest.strip() |
931 | 934 |
number = ''.join(re.findall('[0-9]', dest)) |
932 | 935 |
if dest.startswith('+'): |
933 | 936 |
number = '00' + number |
934 | 937 |
elif number.startswith('00'): |
935 | 938 |
# assumes 00 is international access code, remove it |
936 | 939 |
pass |
937 |
elif number.startswith(default_trunk_prefix): |
|
938 |
number = '00' + default_country_code + number[len(default_trunk_prefix):]
|
|
940 |
elif number.startswith(self.default_trunk_prefix):
|
|
941 |
number = '00' + self.default_country_code + number[len(self.default_trunk_prefix):]
|
|
939 | 942 |
else: |
940 | 943 |
raise APIError('phone number %r is unsupported (no international prefix, ' |
941 | 944 |
'no local trunk prefix)' % number) |
942 | 945 |
numbers.append(number) |
943 | 946 |
return numbers |
944 | 947 | |
945 | 948 |
@endpoint(perm='can_send_messages', methods=['post']) |
946 | 949 |
def send(self, request, *args, **kwargs): |
... | ... | |
952 | 955 |
assert 'to' in data, 'missing "to" in JSON payload' |
953 | 956 |
assert isinstance(data['message'], six.text_type), 'message is not a string' |
954 | 957 |
assert isinstance(data['from'], six.text_type), 'from is not a string' |
955 | 958 |
assert all(map(lambda x: isinstance(x, six.text_type), data['to'])), \ |
956 | 959 |
'to is not a list of strings' |
957 | 960 |
except (ValueError, AssertionError) as e: |
958 | 961 |
raise APIError('Payload error: %s' % e) |
959 | 962 |
data['message'] = data['message'][:self.max_message_length] |
963 |
data['to'] = self.clean_numbers(data['to']) |
|
964 |
stop = not bool('nostop' in request.GET) |
|
960 | 965 |
logging.info('sending message %r to %r with sending number %r', |
961 | 966 |
data['message'], data['to'], data['from']) |
962 |
stop = not bool('nostop' in request.GET) |
|
963 | 967 |
result = {'data': self.send_msg(data['message'], data['from'], data['to'], stop=stop)} |
964 | 968 |
SMSLog.objects.create(appname=self.get_connector_slug(), slug=self.slug) |
965 | 969 |
return result |
966 | 970 | |
967 | 971 |
class Meta: |
968 | 972 |
abstract = True |
969 | 973 | |
970 | 974 |
tests/test_sms.py | ||
---|---|---|
1 | 1 |
import mock |
2 | 2 |
import pytest |
3 | 3 | |
4 | 4 |
from django.contrib.contenttypes.models import ContentType |
5 | 5 | |
6 | 6 |
from passerelle.apps.ovh.models import OVHSMSGateway |
7 | 7 |
from passerelle.base.models import ApiUser, AccessRight, SMSResource, SMSLog |
8 |
from passerelle.utils.jsonresponse import APIError |
|
8 | 9 | |
9 | 10 |
from test_manager import login, admin_user |
10 | 11 | |
11 | 12 |
import utils |
12 | 13 | |
13 | 14 |
pytestmark = pytest.mark.django_db |
14 | 15 | |
15 | 16 |
klasses = SMSResource.__subclasses__() |
16 | 17 | |
17 | 18 | |
18 | 19 |
def test_clean_numbers(): |
19 |
assert SMSResource.clean_numbers(['+ 33 12']) == ['003312'] |
|
20 |
assert SMSResource.clean_numbers(['0 0 33 12']) == ['003312'] |
|
21 |
assert SMSResource.clean_numbers(['0 12']) == ['003312'] |
|
22 |
assert SMSResource.clean_numbers(['+ 33 12'], '32', '1') == ['003312'] |
|
23 |
assert SMSResource.clean_numbers(['0 0 33 12'], '32', '1') == ['003312'] |
|
24 |
assert SMSResource.clean_numbers(['1 12'], '32', '1') == ['003212'] |
|
20 |
connector = OVHSMSGateway() |
|
21 |
assert connector.clean_numbers(['+ 33 12']) == ['003312'] |
|
22 |
assert connector.clean_numbers(['0 0 33 12']) == ['003312'] |
|
23 |
assert connector.clean_numbers(['0 12']) == ['003312'] |
|
24 |
connector.default_country_code = '32' |
|
25 |
connector.default_trunk_prefix = '1' |
|
26 |
connector.save() |
|
27 |
assert connector.clean_numbers(['+ 33 12']) == ['003312'] |
|
28 |
assert connector.clean_numbers(['0 0 33 12']) == ['003312'] |
|
29 |
assert connector.clean_numbers(['1 12']) == ['003212'] |
|
30 |
with pytest.raises(APIError, match='phone number %r is unsupported' % '0123'): |
|
31 |
connector.clean_numbers(['0123']) |
|
25 | 32 | |
26 | 33 | |
27 | 34 |
@pytest.fixture(params=klasses) |
28 | 35 |
def connector(request, db): |
29 | 36 |
klass = request.param |
30 | 37 |
kwargs = getattr(klass, 'TEST_DEFAULTS', {}).get('create_kwargs', {}) |
31 | 38 |
kwargs.update({ |
32 | 39 |
'title': klass.__name__, |
33 |
- |