Projet

Général

Profil

0001-sfr_dmc-implemented-SFR-DMC-sms-connector-20973.patch

Corentin Séchet, 17 février 2022 14:31

Télécharger (12,1 ko)

Voir les différences:

Subject: [PATCH] sfr_dmc: implemented SFR DMC sms connector (#20973)

 passerelle/apps/sfr_dmc/__init__.py           |   0
 .../apps/sfr_dmc/migrations/0001_initial.py   |  87 +++++++++
 .../apps/sfr_dmc/migrations/__init__.py       |   0
 passerelle/apps/sfr_dmc/models.py             | 165 ++++++++++++++++++
 passerelle/settings.py                        |   3 +-
 5 files changed, 254 insertions(+), 1 deletion(-)
 create mode 100644 passerelle/apps/sfr_dmc/__init__.py
 create mode 100644 passerelle/apps/sfr_dmc/migrations/0001_initial.py
 create mode 100644 passerelle/apps/sfr_dmc/migrations/__init__.py
 create mode 100644 passerelle/apps/sfr_dmc/models.py
passerelle/apps/sfr_dmc/migrations/0001_initial.py
1
# Generated by Django 2.2.24 on 2022-02-09 11:10
2

  
3
import django.contrib.postgres.fields
4
from django.db import migrations, models
5

  
6
import passerelle.sms.models
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    initial = True
12

  
13
    dependencies = [
14
        ('base', '0029_auto_20210202_1627'),
15
    ]
16

  
17
    operations = [
18
        migrations.CreateModel(
19
            name='SfrDmcGateway',
20
            fields=[
21
                (
22
                    'id',
23
                    models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
24
                ),
25
                ('title', models.CharField(max_length=50, verbose_name='Title')),
26
                ('slug', models.SlugField(unique=True, verbose_name='Identifier')),
27
                ('description', models.TextField(verbose_name='Description')),
28
                (
29
                    'default_country_code',
30
                    models.CharField(default='33', max_length=3, verbose_name='Default country code'),
31
                ),
32
                (
33
                    'default_trunk_prefix',
34
                    models.CharField(default='0', max_length=2, verbose_name='Default trunk prefix'),
35
                ),
36
                (
37
                    'max_message_length',
38
                    models.IntegerField(
39
                        default=2000,
40
                        help_text='Messages over this limit will be truncated.',
41
                        verbose_name='Maximum message length',
42
                    ),
43
                ),
44
                (
45
                    'authorized',
46
                    django.contrib.postgres.fields.ArrayField(
47
                        base_field=models.CharField(
48
                            choices=[
49
                                ('fr-metro', 'France mainland (+33 [67])'),
50
                                ('fr-domtom', 'France DOM/TOM (+262, etc.)'),
51
                                ('be', 'Belgian (+32 4[5-9]) '),
52
                                ('all', 'All'),
53
                            ],
54
                            max_length=32,
55
                            null=True,
56
                        ),
57
                        default=passerelle.sms.models.authorized_default,
58
                        size=None,
59
                        verbose_name='Authorized Countries',
60
                    ),
61
                ),
62
                (
63
                    'allow_premium_rate',
64
                    models.BooleanField(
65
                        default=False,
66
                        help_text='This option is only applyed to France mainland',
67
                        verbose_name='Allow premium rate numbers',
68
                    ),
69
                ),
70
                ('service_id', models.CharField(max_length=15, verbose_name='Service ID')),
71
                ('service_password', models.CharField(max_length=15, verbose_name='Service Password')),
72
                ('space_id', models.IntegerField(verbose_name='Space ID')),
73
                (
74
                    'users',
75
                    models.ManyToManyField(
76
                        blank=True,
77
                        related_name='_sfrdmcgateway_users_+',
78
                        related_query_name='+',
79
                        to='base.ApiUser',
80
                    ),
81
                ),
82
            ],
83
            options={
84
                'verbose_name': 'SFR DMC',
85
            },
86
        ),
87
    ]
passerelle/apps/sfr_dmc/models.py
1
# passerelle - uniform access to multiple data sources and services
2
# Copyright (C) 2022  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 json
17

  
18
from django.db import models
19
from django.utils.translation import ugettext_lazy as _
20

  
21
from passerelle.sms.models import SMSResource
22
from passerelle.utils.jsonresponse import APIError
23

  
24

  
25
class SfrDmcGateway(SMSResource):
26
    service_id = models.CharField(verbose_name=_('Service ID'), max_length=15)
27
    service_password = models.CharField(verbose_name=_('Service Password'), max_length=15)
28
    space_id = models.IntegerField(verbose_name=_('Space ID'))
29

  
30
    TEST_DEFAULTS = {
31
        'create_kwargs': {
32
            'service_id': '1234',
33
            'service_password': 'krascuky',
34
            'space_id': '1234',
35
        },
36
        'test_vectors': [
37
            {
38
                'response': '',
39
                'result': {
40
                    'err': 1,
41
                    'err_desc': 'SFR DMC error: some destinations failed',
42
                    'data': [
43
                        ['0033688888888', 'SFR DMC error: Bad JSON response'],
44
                        ['0033677777777', 'SFR DMC error: Bad JSON response'],
45
                    ],
46
                },
47
            },
48
            {
49
                'response': {'bad_json': True},
50
                'result': {
51
                    'err': 1,
52
                    'err_desc': 'SFR DMC error: some destinations failed',
53
                    'data': [
54
                        ['0033688888888', 'SFR DMC error: Bad JSON response'],
55
                        ['0033677777777', 'SFR DMC error: Bad JSON response'],
56
                    ],
57
                },
58
            },
59
            {
60
                'response': {'success': False, 'no_error_detail_field': 'bad_json'},
61
                'result': {
62
                    'err': 1,
63
                    'err_desc': 'SFR DMC error: some destinations failed',
64
                    'data': [
65
                        ['0033688888888', 'SFR DMC error: Bad JSON response'],
66
                        ['0033677777777', 'SFR DMC error: Bad JSON response'],
67
                    ],
68
                },
69
            },
70
            {
71
                'response': {'success': False, 'errorDetail': 'sfr error description'},
72
                'result': {
73
                    'err': 1,
74
                    'err_desc': 'SFR DMC error: some destinations failed',
75
                    'data': [
76
                        [
77
                            '0033688888888',
78
                            {'result': 'SFR DMC error: sfr error description', 'sms_id': 1234},
79
                        ],
80
                        [
81
                            '0033677777777',
82
                            {'result': 'SFR DMC error: sfr error description', 'sms_id': 1234},
83
                        ],
84
                    ],
85
                },
86
            },
87
            {
88
                'response': {'success': True, 'response': ['1234', '5678']},
89
                'result': {
90
                    'err': 0,
91
                    'data': [
92
                        ['0033688888888', {'result': 'Envoi terminé', 'sms_id': 1234}],
93
                        ['0033677777777', {'result': 'Envoi terminé', 'sms_id': 1234}],
94
                    ],
95
                },
96
            },
97
        ],
98
    }
99
    URL = 'https://www.dmc.sfr-sh.fr/DmcWS/1.5.7'
100

  
101
    class Meta:
102
        verbose_name = _('SFR DMC')
103

  
104
    def send_msg(self, text, sender, destinations, **kwargs):
105
        """Send a SMS using the SFR DMC provider"""
106
        errors = []
107

  
108
        # SFR API limitation, we slice destinations in chunks of 20 items
109
        batch_size = 20
110
        batches = [destinations[i : i + batch_size] for i in range(0, len(destinations), batch_size)]
111

  
112
        for batch in batches:
113

  
114
            def add_error(message, destination=None):
115
                if destination is None:
116
                    for dest in batch:
117
                        errors.append((dest, f'SFR DMC Error : {message}'))
118
                else:
119
                    errors.append((destination, f'SFR DMC Error : {message}'))
120

  
121
            local_destinations = []
122
            # The API seems to only accept number without country codes.
123
            for dest in destinations:
124
                if dest[0:4] != '0033':
125
                    add_error('SFR DMC API doesn\'t support country code in phone number.', dest)
126
                    continue
127
                local_destinations.append(f'0{dest[4:]}')
128

  
129
            response = self.requests.get(
130
                f'{self.URL}/JsonService/MessagesUnitairesWS/batchSingleCall',
131
                params={
132
                    'authenticate': json.dumps(
133
                        {
134
                            'serviceId': self.service_id,
135
                            'servicePassword': self.service_password,
136
                            'spaceId': self.space_id,
137
                        }
138
                    ),
139
                    'messageUnitaires': json.dumps(
140
                        [
141
                            {'media': 'SMS', 'textMsg': text, 'from': sender, 'to': dest}
142
                            for dest in local_destinations
143
                        ]
144
                    ),
145
                },
146
            )
147

  
148
            try:
149
                json_data = response.json()
150
            except ValueError:
151
                add_error('Bad JSON response')
152
                continue
153

  
154
            if 'success' not in json_data:
155
                add_error('Bad JSON response')
156
            elif not json_data['success']:
157
                if not 'errorDetail' in json_data:
158
                    add_error('Bad JSON response')
159
                else:
160
                    add_error(json_data['errorDetail'])
161

  
162
        if errors:
163
            raise APIError('SFR DMC error: some destinations failed', data=errors)
164

  
165
        return None
passerelle/settings.py
147 147
    'passerelle.apps.gdc',
148 148
    'passerelle.apps.gesbac',
149 149
    'passerelle.apps.jsondatastore',
150
    'passerelle.apps.sp_fr',
151 150
    'passerelle.apps.maelis',
152 151
    'passerelle.apps.mdel',
153 152
    'passerelle.apps.mdel_ddpacs',
......
162 161
    'passerelle.apps.photon',
163 162
    'passerelle.apps.plone_restapi',
164 163
    'passerelle.apps.sector',
164
    'passerelle.apps.sfr_dmc',
165 165
    'passerelle.apps.solis',
166
    'passerelle.apps.sp_fr',
166 167
    'passerelle.apps.twilio',
167 168
    'passerelle.apps.vivaticket',
168 169
    # backoffice templates and static
169
-