0001-sms-turn-mixin-into-abstract-class-39654.patch
passerelle/apps/choosit/migrations/0001_initial.py | ||
---|---|---|
56 | 56 |
'db_table': 'sms_choosit', |
57 | 57 |
'verbose_name': 'Choosit', |
58 | 58 |
}, |
59 |
bases=(models.Model, passerelle.sms.SMSGatewayMixin),
|
|
59 |
bases=(models.Model,), |
|
60 | 60 |
), |
61 | 61 |
migrations.AddField( |
62 | 62 |
model_name='choositregistergateway', |
passerelle/apps/choosit/models.py | ||
---|---|---|
7 | 7 |
from django.db import models |
8 | 8 | |
9 | 9 |
from passerelle.utils.jsonresponse import APIError |
10 |
from passerelle.base.models import BaseResource |
|
11 |
from passerelle.sms import SMSGatewayMixin |
|
10 |
from passerelle.base.models import SMSResource |
|
12 | 11 | |
13 | 12 | |
14 |
class ChoositSMSGateway(BaseResource, SMSGatewayMixin):
|
|
13 |
class ChoositSMSGateway(SMSResource):
|
|
15 | 14 |
key = models.CharField(verbose_name=_('Key'), max_length=64) |
16 | 15 |
default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3, |
17 | 16 |
default=u'33') |
passerelle/apps/mobyt/migrations/0001_initial.py | ||
---|---|---|
29 | 29 |
'db_table': 'sms_mobyt', |
30 | 30 |
'verbose_name': 'Mobyt', |
31 | 31 |
}, |
32 |
bases=(models.Model, passerelle.sms.SMSGatewayMixin),
|
|
32 |
bases=(models.Model,), |
|
33 | 33 |
), |
34 | 34 |
] |
passerelle/apps/mobyt/models.py | ||
---|---|---|
4 | 4 |
import requests |
5 | 5 | |
6 | 6 |
from passerelle.utils.jsonresponse import APIError |
7 |
from passerelle.base.models import BaseResource |
|
8 |
from passerelle.sms import SMSGatewayMixin |
|
7 |
from passerelle.base.models import SMSResource |
|
9 | 8 | |
10 | 9 | |
11 |
class MobytSMSGateway(BaseResource, SMSGatewayMixin):
|
|
10 |
class MobytSMSGateway(SMSResource):
|
|
12 | 11 |
URL = 'http://multilevel.mobyt.fr/sms/batch.php' |
13 | 12 |
MESSAGES_QUALITIES = ( |
14 | 13 |
('l', _('sms direct')), |
passerelle/apps/orange/migrations/0001_initial.py | ||
---|---|---|
26 | 26 |
'db_table': 'sms_orange', |
27 | 27 |
'verbose_name': 'Orange', |
28 | 28 |
}, |
29 |
bases=(models.Model, passerelle.sms.SMSGatewayMixin),
|
|
29 |
bases=(models.Model,), |
|
30 | 30 |
), |
31 | 31 |
] |
passerelle/apps/orange/models.py | ||
---|---|---|
2 | 2 |
from django.utils.translation import ugettext_lazy as _ |
3 | 3 |
from django.db import models |
4 | 4 | |
5 |
from passerelle.base.models import BaseResource |
|
6 |
from passerelle.sms import SMSGatewayMixin |
|
5 |
from passerelle.base.models import SMSResource |
|
7 | 6 | |
8 | 7 |
from . import soap |
9 | 8 | |
10 | 9 | |
11 |
class OrangeSMSGateway(BaseResource, SMSGatewayMixin):
|
|
10 |
class OrangeSMSGateway(SMSResource):
|
|
12 | 11 |
keystore = models.FileField(upload_to='orange', blank=True, null=True, |
13 | 12 |
verbose_name=_('Keystore'), |
14 | 13 |
help_text=_('Certificate and private key in PEM format')) |
passerelle/apps/ovh/migrations/0001_initial.py | ||
---|---|---|
32 | 32 |
'db_table': 'sms_ovh', |
33 | 33 |
'verbose_name': 'OVH', |
34 | 34 |
}, |
35 |
bases=(models.Model, passerelle.sms.SMSGatewayMixin),
|
|
35 |
bases=(models.Model,), |
|
36 | 36 |
), |
37 | 37 |
] |
passerelle/apps/ovh/models.py | ||
---|---|---|
5 | 5 |
from django.utils.encoding import force_text |
6 | 6 | |
7 | 7 |
from passerelle.utils.jsonresponse import APIError |
8 |
from passerelle.base.models import BaseResource |
|
9 |
from passerelle.sms import SMSGatewayMixin |
|
8 |
from passerelle.base.models import SMSResource |
|
10 | 9 | |
11 | 10 | |
12 |
class OVHSMSGateway(BaseResource, SMSGatewayMixin):
|
|
11 |
class OVHSMSGateway(SMSResource):
|
|
13 | 12 |
URL = 'https://www.ovh.com/cgi-bin/sms/http2sms.cgi' |
14 | 13 |
MESSAGES_CLASSES = ( |
15 | 14 |
(0, _('Message are directly shown to users on phone screen ' |
passerelle/apps/oxyd/migrations/0001_initial.py | ||
---|---|---|
28 | 28 |
'db_table': 'sms_oxyd', |
29 | 29 |
'verbose_name': 'Oxyd', |
30 | 30 |
}, |
31 |
bases=(models.Model, passerelle.sms.SMSGatewayMixin),
|
|
31 |
bases=(models.Model,), |
|
32 | 32 |
), |
33 | 33 |
] |
passerelle/apps/oxyd/models.py | ||
---|---|---|
4 | 4 |
from django.utils.encoding import force_text |
5 | 5 | |
6 | 6 |
from passerelle.utils.jsonresponse import APIError |
7 |
from passerelle.base.models import BaseResource |
|
8 |
from passerelle.sms import SMSGatewayMixin |
|
7 |
from passerelle.base.models import SMSResource |
|
9 | 8 |
from django.utils.translation import ugettext_lazy as _ |
10 | 9 | |
11 | 10 | |
12 |
class OxydSMSGateway(BaseResource, SMSGatewayMixin):
|
|
11 |
class OxydSMSGateway(SMSResource):
|
|
13 | 12 |
username = models.CharField(verbose_name=_('Username'), max_length=64) |
14 | 13 |
password = models.CharField(verbose_name=_('Password'), max_length=64) |
15 | 14 |
default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3, |
passerelle/base/models.py | ||
---|---|---|
37 | 37 | |
38 | 38 |
import passerelle |
39 | 39 |
import requests |
40 |
from passerelle.compat import json_loads |
|
40 | 41 |
from passerelle.utils.api import endpoint |
41 | 42 |
from passerelle.utils.jsonresponse import APIError |
42 | 43 | |
... | ... | |
899 | 900 | |
900 | 901 |
class Meta: |
901 | 902 |
abstract = True |
903 | ||
904 | ||
905 |
class SMSResource(BaseResource): |
|
906 |
category = _('SMS Providers') |
|
907 |
documentation_url = 'https://doc-publik.entrouvert.com/admin-fonctionnel/les-tutos/configuration-envoi-sms/' |
|
908 | ||
909 |
_can_send_messages_description = _('Sending messages is limited to the following API users:') |
|
910 | ||
911 |
@classmethod |
|
912 |
def clean_numbers(cls, destinations, default_country_code='33', |
|
913 |
default_trunk_prefix='0'): # Yeah France first ! |
|
914 |
numbers = [] |
|
915 |
for dest in destinations: |
|
916 |
# most gateways needs the number prefixed by the country code, this is |
|
917 |
# really unfortunate. |
|
918 |
dest = dest.strip() |
|
919 |
number = ''.join(re.findall('[0-9]', dest)) |
|
920 |
if dest.startswith('+'): |
|
921 |
number = '00' + number |
|
922 |
elif number.startswith('00'): |
|
923 |
# assumes 00 is international access code, remove it |
|
924 |
pass |
|
925 |
elif number.startswith(default_trunk_prefix): |
|
926 |
number = '00' + default_country_code + number[len(default_trunk_prefix):] |
|
927 |
else: |
|
928 |
raise NotImplementedError('phone number %r is unsupported (no ' |
|
929 |
'international prefix, no local ' |
|
930 |
'trunk prefix)' % number) |
|
931 |
numbers.append(number) |
|
932 |
return numbers |
|
933 | ||
934 |
@endpoint(perm='can_send_messages', methods=['post']) |
|
935 |
def send(self, request, *args, **kwargs): |
|
936 |
try: |
|
937 |
data = json_loads(request.body) |
|
938 |
assert isinstance(data, dict), 'JSON payload is not a dict' |
|
939 |
assert 'message' in data, 'missing "message" in JSON payload' |
|
940 |
assert 'from' in data, 'missing "from" in JSON payload' |
|
941 |
assert 'to' in data, 'missing "to" in JSON payload' |
|
942 |
assert isinstance(data['message'], six.text_type), 'message is not a string' |
|
943 |
assert isinstance(data['from'], six.text_type), 'from is not a string' |
|
944 |
assert all(map(lambda x: isinstance(x, six.text_type), data['to'])), \ |
|
945 |
'to is not a list of strings' |
|
946 |
except (ValueError, AssertionError) as e: |
|
947 |
raise APIError('Payload error: %s' % e) |
|
948 |
logging.info('sending message %r to %r with sending number %r', |
|
949 |
data['message'], data['to'], data['from']) |
|
950 |
if 'nostop' in request.GET: |
|
951 |
return {'data': self.send_msg(data['message'], data['from'], data['to'], stop=False)} |
|
952 |
return {'data': self.send_msg(data['message'], data['from'], data['to'], stop=True)} |
|
953 | ||
954 |
class Meta: |
|
955 |
abstract = True |
passerelle/sms/__init__.py | ||
---|---|---|
1 |
import logging |
|
2 |
import re |
|
3 | ||
4 |
from django.utils import six |
|
5 |
from django.utils.translation import ugettext_lazy as _ |
|
6 |
from passerelle.compat import json_loads |
|
7 |
from passerelle.utils.api import endpoint |
|
8 |
from passerelle.utils.jsonresponse import APIError |
|
9 | ||
10 | ||
11 |
class SMSGatewayMixin(object): |
|
12 |
category = _('SMS Providers') |
|
13 |
documentation_url = 'https://doc-publik.entrouvert.com/admin-fonctionnel/les-tutos/configuration-envoi-sms/' |
|
14 | ||
15 |
_can_send_messages_description = _('Sending messages is limited to the following API users:') |
|
16 | ||
17 |
@classmethod |
|
18 |
def clean_numbers(cls, destinations, default_country_code='33', |
|
19 |
default_trunk_prefix='0'): # Yeah France first ! |
|
20 |
numbers = [] |
|
21 |
for dest in destinations: |
|
22 |
# most gateways needs the number prefixed by the country code, this is |
|
23 |
# really unfortunate. |
|
24 |
dest = dest.strip() |
|
25 |
number = ''.join(re.findall('[0-9]', dest)) |
|
26 |
if dest.startswith('+'): |
|
27 |
number = '00' + number |
|
28 |
elif number.startswith('00'): |
|
29 |
# assumes 00 is international access code, remove it |
|
30 |
pass |
|
31 |
elif number.startswith(default_trunk_prefix): |
|
32 |
number = '00' + default_country_code + number[len(default_trunk_prefix):] |
|
33 |
else: |
|
34 |
raise NotImplementedError('phone number %r is unsupported (no ' |
|
35 |
'international prefix, no local ' |
|
36 |
'trunk prefix)' % number) |
|
37 |
numbers.append(number) |
|
38 |
return numbers |
|
39 | ||
40 |
@endpoint(perm='can_send_messages', methods=['post']) |
|
41 |
def send(self, request, *args, **kwargs): |
|
42 |
try: |
|
43 |
data = json_loads(request.body) |
|
44 |
assert isinstance(data, dict), 'JSON payload is not a dict' |
|
45 |
assert 'message' in data, 'missing "message" in JSON payload' |
|
46 |
assert 'from' in data, 'missing "from" in JSON payload' |
|
47 |
assert 'to' in data, 'missing "to" in JSON payload' |
|
48 |
assert isinstance(data['message'], six.text_type), 'message is not a string' |
|
49 |
assert isinstance(data['from'], six.text_type), 'from is not a string' |
|
50 |
assert all(map(lambda x: isinstance(x, six.text_type), data['to'])), \ |
|
51 |
'to is not a list of strings' |
|
52 |
except (ValueError, AssertionError) as e: |
|
53 |
raise APIError('Payload error: %s' % e) |
|
54 |
logging.info('sending message %r to %r with sending number %r', |
|
55 |
data['message'], data['to'], data['from']) |
|
56 |
if 'nostop' in request.GET: |
|
57 |
return {'data': self.send_msg(data['message'], data['from'], data['to'], stop=False)} |
|
58 |
return {'data': self.send_msg(data['message'], data['from'], data['to'], stop=True)} |
tests/test_sms.py | ||
---|---|---|
2 | 2 | |
3 | 3 |
from django.contrib.contenttypes.models import ContentType |
4 | 4 | |
5 |
from passerelle.base.models import ApiUser, AccessRight |
|
6 |
from passerelle.sms import SMSGatewayMixin |
|
5 |
from passerelle.base.models import ApiUser, AccessRight, SMSResource |
|
7 | 6 | |
8 | 7 |
from test_manager import login, admin_user |
9 | 8 | |
... | ... | |
11 | 10 | |
12 | 11 |
pytestmark = pytest.mark.django_db |
13 | 12 | |
14 |
klasses = SMSGatewayMixin.__subclasses__()
|
|
13 |
klasses = SMSResource.__subclasses__()
|
|
15 | 14 | |
16 | 15 | |
17 | 16 |
def test_clean_numbers(): |
18 |
assert SMSGatewayMixin.clean_numbers(['+ 33 12']) == ['003312']
|
|
19 |
assert SMSGatewayMixin.clean_numbers(['0 0 33 12']) == ['003312']
|
|
20 |
assert SMSGatewayMixin.clean_numbers(['0 12']) == ['003312']
|
|
21 |
assert SMSGatewayMixin.clean_numbers(['+ 33 12'], '32', '1') == ['003312']
|
|
22 |
assert SMSGatewayMixin.clean_numbers(['0 0 33 12'], '32', '1') == ['003312']
|
|
23 |
assert SMSGatewayMixin.clean_numbers(['1 12'], '32', '1') == ['003212']
|
|
17 |
assert SMSResource.clean_numbers(['+ 33 12']) == ['003312']
|
|
18 |
assert SMSResource.clean_numbers(['0 0 33 12']) == ['003312']
|
|
19 |
assert SMSResource.clean_numbers(['0 12']) == ['003312']
|
|
20 |
assert SMSResource.clean_numbers(['+ 33 12'], '32', '1') == ['003312']
|
|
21 |
assert SMSResource.clean_numbers(['0 0 33 12'], '32', '1') == ['003312']
|
|
22 |
assert SMSResource.clean_numbers(['1 12'], '32', '1') == ['003212']
|
|
24 | 23 | |
25 | 24 | |
26 | 25 |
@pytest.fixture(params=klasses) |
27 |
- |