Projet

Général

Profil

0001-sms-factorize-clean_number-call-42427.patch

Nicolas Roche, 06 mai 2020 16:44

Télécharger (15,8 ko)

Voir les différences:

Subject: [PATCH] sms: factorize clean_number call (#42427)

 passerelle/apps/choosit/models.py | 10 +---------
 passerelle/apps/mobyt/models.py   | 10 +---------
 passerelle/apps/orange/models.py  |  8 +-------
 passerelle/apps/ovh/models.py     |  8 --------
 passerelle/apps/oxyd/models.py    | 10 +---------
 passerelle/base/models.py         | 20 ++++++++++++++------
 tests/test_sms.py                 | 19 +++++++++++++------
 7 files changed, 31 insertions(+), 54 deletions(-)
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')  # Yeah France first !
925
    # FIXME: add regexp field, to check destination and from format
926

  
921 927
    max_message_length = models.IntegerField(_('Maximum message length'), default=160)
922 928

  
923
    @classmethod
924
    def clean_numbers(cls, destinations, default_country_code='33',
925
                      default_trunk_prefix='0'):  # Yeah France first !
929
    def clean_numbers(self, destinations):
930

  
926 931
        numbers = []
927 932
        for dest in destinations:
928 933
            # most gateways needs the number prefixed by the country code, this is
929 934
            # really unfortunate.
930 935
            dest = dest.strip()
931 936
            number = ''.join(re.findall('[0-9]', dest))
932 937
            if dest.startswith('+'):
933 938
                number = '00' + number
934 939
            elif number.startswith('00'):
935 940
                # assumes 00 is international access code, remove it
936 941
                pass
937
            elif number.startswith(default_trunk_prefix):
938
                number = '00' + default_country_code + number[len(default_trunk_prefix):]
942
            elif number.startswith(self.default_trunk_prefix):
943
                number = '00' + self.default_country_code + number[len(self.default_trunk_prefix):]
939 944
            else:
940 945
                raise APIError('phone number %r is unsupported (no international prefix, '
941 946
                               'no local trunk prefix)' % number)
942 947
            numbers.append(number)
943 948
        return numbers
944 949

  
945 950
    @endpoint(perm='can_send_messages', methods=['post'])
946 951
    def send(self, request, *args, **kwargs):
......
952 957
            assert 'to' in data, 'missing "to" in JSON payload'
953 958
            assert isinstance(data['message'], six.text_type), 'message is not a string'
954 959
            assert isinstance(data['from'], six.text_type), 'from is not a string'
955 960
            assert all(map(lambda x: isinstance(x, six.text_type), data['to'])), \
956 961
                'to is not a list of strings'
957 962
        except (ValueError, AssertionError) as e:
958 963
            raise APIError('Payload error: %s' % e)
959 964
        data['message'] = data['message'][:self.max_message_length]
965
        data['to'] = self.clean_numbers(data['to'])
966
        stop = not bool('nostop' in request.GET)
960 967
        logging.info('sending message %r to %r with sending number %r',
961 968
                     data['message'], data['to'], data['from'])
962
        stop = not bool('nostop' in request.GET)
969

  
970
        # unfortunately it lacks a batch API...
963 971
        result = {'data': self.send_msg(data['message'], data['from'], data['to'], stop=stop)}
964 972
        SMSLog.objects.create(appname=self.get_connector_slug(), slug=self.slug)
965 973
        return result
966 974

  
967 975
    class Meta:
968 976
        abstract = True
969 977

  
970 978

  
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
-