Projet

Général

Profil

0001-sms-move-sms-code-into-a-dedicated-directory-42426.patch

Nicolas Roche, 07 mai 2020 14:43

Télécharger (14 ko)

Voir les différences:

Subject: [PATCH] sms: move sms code into a dedicated directory (#42426)

 passerelle/apps/choosit/models.py         |  4 +-
 passerelle/apps/mobyt/models.py           |  4 +-
 passerelle/apps/orange/models.py          |  3 +-
 passerelle/apps/ovh/models.py             |  4 +-
 passerelle/apps/oxyd/models.py            |  4 +-
 passerelle/base/models.py                 |  5 +-
 passerelle/settings.py                    |  1 +
 passerelle/sms/__init__.py                |  0
 passerelle/sms/migrations/0001_initial.py | 25 ++++++
 passerelle/sms/migrations/__init__.py     |  0
 passerelle/sms/models.py                  | 97 +++++++++++++++++++++++
 tests/test_sms.py                         |  3 +-
 12 files changed, 136 insertions(+), 14 deletions(-)
 create mode 100644 passerelle/sms/__init__.py
 create mode 100644 passerelle/sms/migrations/0001_initial.py
 create mode 100644 passerelle/sms/migrations/__init__.py
 create mode 100644 passerelle/sms/models.py
passerelle/apps/choosit/models.py
1 1
# -*- coding: utf-8 -*-
2 2
import json
3 3
import requests
4 4

  
5
from django.db import models
5 6
from django.utils.six import string_types
6 7
from django.utils.translation import ugettext_lazy as _
7
from django.db import models
8 8

  
9
from passerelle.sms.models import SMSResource
9 10
from passerelle.utils.jsonresponse import APIError
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 15

  
16 16
    TEST_DEFAULTS = {
17 17
        'create_kwargs': {
18 18
            'key': '1234',
passerelle/apps/mobyt/models.py
1
from django.utils.translation import ugettext_lazy as _
2 1
from django.db import models
2
from django.utils.translation import ugettext_lazy as _
3 3

  
4 4
import requests
5 5

  
6
from passerelle.sms.models import SMSResource
6 7
from passerelle.utils.jsonresponse import APIError
7
from passerelle.base.models import SMSResource
8 8

  
9 9

  
10 10
class MobytSMSGateway(SMSResource):
11 11
    URL = 'http://multilevel.mobyt.fr/sms/batch.php'
12 12
    MESSAGES_QUALITIES = (
13 13
        ('l', _('sms direct')),
14 14
        ('ll', _('sms low-cost')),
15 15
        ('n', _('sms top')),
passerelle/apps/orange/models.py
17 17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 18
# GNU Affero General Public License for more details.
19 19
#
20 20
# You should have received a copy of the GNU Affero General Public License
21 21
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
22 22
from django.db import models
23 23
from django.utils.translation import ugettext_lazy as _
24 24

  
25
from passerelle.base.models import SMSResource
25
from passerelle.sms.models import SMSResource
26 26
from passerelle.utils.jsonresponse import APIError
27 27

  
28

  
28 29
BASE_API = 'https://contact-everyone.orange-business.com/api/v1.2/'
29 30
URL_TOKEN = BASE_API + 'oauth/token'
30 31
URL_GROUPS = BASE_API + 'groups'
31 32
URL_DIFFUSION = BASE_API + 'groups/%s/diffusion-requests'
32 33

  
33 34

  
34 35
class OrangeError(APIError):
35 36
    pass
passerelle/apps/ovh/models.py
1 1
import requests
2 2

  
3
from django.utils.translation import ugettext_lazy as _
4 3
from django.db import models
5 4
from django.utils.encoding import force_text
5
from django.utils.translation import ugettext_lazy as _
6 6

  
7
from passerelle.sms.models import SMSResource
7 8
from passerelle.utils.jsonresponse import APIError
8
from passerelle.base.models import SMSResource
9 9

  
10 10

  
11 11
class OVHSMSGateway(SMSResource):
12 12
    URL = 'https://www.ovh.com/cgi-bin/sms/http2sms.cgi'
13 13
    MESSAGES_CLASSES = (
14 14
        (0, _('Message are directly shown to users on phone screen '
15 15
              'at reception. The message is never stored, neither in the '
16 16
              'phone memory nor in the SIM card. It is deleted as '
passerelle/apps/oxyd/models.py
1 1
import requests
2 2

  
3 3
from django.db import models
4 4
from django.utils.encoding import force_text
5
from django.utils.translation import ugettext_lazy as _
5 6

  
6 7
from passerelle.utils.jsonresponse import APIError
7
from passerelle.base.models import SMSResource
8
from django.utils.translation import ugettext_lazy as _
8
from passerelle.sms.models import SMSResource
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 14

  
15 15
    manager_view_template_name = 'passerelle/manage/messages_service_view.html'
16 16

  
passerelle/base/models.py
9 9
import traceback
10 10
import base64
11 11
import itertools
12 12
import uuid
13 13

  
14 14
from django.apps import apps
15 15
from django.conf import settings
16 16
from django.contrib.postgres.fields import JSONField
17
from django.core.exceptions import ValidationError, ObjectDoesNotExist, PermissionDenied
17
from django.core.exceptions import ValidationError, PermissionDenied
18 18
from django.core.urlresolvers import reverse
19 19
from django.db import connection, models, transaction
20 20
from django.db.models import Q
21 21
from django.test import override_settings
22 22
from django.utils.text import slugify
23 23
from django.utils import timezone, six
24
from django.utils import six
25 24
from django.utils.encoding import force_text
26 25
from django.utils.six.moves.urllib.parse import urlparse
27 26
from django.utils.translation import ugettext_lazy as _
28 27
from django.utils.timezone import now
29 28
from django.core.files.base import ContentFile
30 29

  
31 30
from django.contrib.contenttypes.models import ContentType
32 31
from django.contrib.contenttypes import fields
33 32

  
34 33
from model_utils.managers import InheritanceManager as ModelUtilsInheritanceManager
35 34

  
36 35
import passerelle
37
import requests
38
from passerelle.compat import json_loads
39 36
from passerelle.utils.api import endpoint
40 37
from passerelle.utils.jsonresponse import APIError
41 38

  
42 39
KEYTYPE_CHOICES = (
43 40
    ('API', _('API Key')),
44 41
    ('SIGN', _('HMAC Signature')),
45 42
)
46 43

  
passerelle/settings.py
113 113
    'django.contrib.contenttypes',
114 114
    'django.contrib.sessions',
115 115
    'django.contrib.messages',
116 116
    'django.contrib.staticfiles',
117 117
    'django.contrib.admin',
118 118
    'django.contrib.postgres',
119 119
    # base app
120 120
    'passerelle.base',
121
    'passerelle.sms',
121 122
    # connectors
122 123
    'passerelle.apps.actesweb',
123 124
    'passerelle.apps.airquality',
124 125
    'passerelle.apps.api_entreprise',
125 126
    'passerelle.apps.api_particulier',
126 127
    'passerelle.apps.arcgis',
127 128
    'passerelle.apps.arpege_ecp',
128 129
    'passerelle.apps.astregs',
passerelle/sms/migrations/0001_initial.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2020-05-02 17:57
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
    ]
14

  
15
    operations = [
16
        migrations.CreateModel(
17
            name='SMSLog',
18
            fields=[
19
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20
                ('timestamp', models.DateTimeField(auto_now_add=True)),
21
                ('appname', models.CharField(max_length=128, null=True, verbose_name='appname')),
22
                ('slug', models.CharField(max_length=128, null=True, verbose_name='slug')),
23
            ],
24
        ),
25
    ]
passerelle/sms/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 logging
17
import re
18

  
19
from django.db import models
20
from django.utils import six
21
from django.utils.translation import ugettext_lazy as _
22

  
23
from passerelle.base.models import BaseResource
24
from passerelle.compat import json_loads
25
from passerelle.utils.api import endpoint
26
from passerelle.utils.jsonresponse import APIError
27

  
28

  
29
class SMSResource(BaseResource):
30
    category = _('SMS Providers')
31
    documentation_url = 'https://doc-publik.entrouvert.com/admin-fonctionnel/les-tutos/configuration-envoi-sms/'
32

  
33
    _can_send_messages_description = _('Sending messages is limited to the following API users:')
34

  
35
    default_country_code = models.CharField(verbose_name=_('Default country code'), max_length=3,
36
                                            default=u'33')
37
    default_trunk_prefix = models.CharField(verbose_name=_('Default trunk prefix'), max_length=2,
38
                                            default=u'0')  # Yeah France first !
39
    # FIXME: add regexp field, to check destination and from format
40
    max_message_length = models.IntegerField(_('Maximum message length'), default=160)
41

  
42
    def clean_numbers(self, destinations):
43
        numbers = []
44
        for dest in destinations:
45
            # most gateways needs the number prefixed by the country code, this is
46
            # really unfortunate.
47
            dest = dest.strip()
48
            number = ''.join(re.findall('[0-9]', dest))
49
            if dest.startswith('+'):
50
                number = '00' + number
51
            elif number.startswith('00'):
52
                # assumes 00 is international access code, remove it
53
                pass
54
            elif number.startswith(self.default_trunk_prefix):
55
                number = '00' + self.default_country_code + number[len(self.default_trunk_prefix):]
56
            else:
57
                raise APIError('phone number %r is unsupported (no international prefix, '
58
                               'no local trunk prefix)' % number)
59
            numbers.append(number)
60
        return numbers
61

  
62
    @endpoint(perm='can_send_messages', methods=['post'])
63
    def send(self, request, *args, **kwargs):
64
        try:
65
            data = json_loads(request.body)
66
            assert isinstance(data, dict), 'JSON payload is not a dict'
67
            assert 'message' in data, 'missing "message" in JSON payload'
68
            assert 'from' in data, 'missing "from" in JSON payload'
69
            assert 'to' in data, 'missing "to" in JSON payload'
70
            assert isinstance(data['message'], six.text_type), 'message is not a string'
71
            assert isinstance(data['from'], six.text_type), 'from is not a string'
72
            assert all(map(lambda x: isinstance(x, six.text_type), data['to'])), \
73
                'to is not a list of strings'
74
        except (ValueError, AssertionError) as e:
75
            raise APIError('Payload error: %s' % e)
76
        data['message'] = data['message'][:self.max_message_length]
77
        data['to'] = self.clean_numbers(data['to'])
78
        stop = not bool('nostop' in request.GET)
79
        logging.info('sending message %r to %r with sending number %r',
80
                     data['message'], data['to'], data['from'])
81
        # unfortunately it lacks a batch API...
82
        result = {'data': self.send_msg(data['message'], data['from'], data['to'], stop=stop)}
83
        SMSLog.objects.create(appname=self.get_connector_slug(), slug=self.slug)
84
        return result
85

  
86
    class Meta:
87
        abstract = True
88

  
89

  
90
@six.python_2_unicode_compatible
91
class SMSLog(models.Model):
92
    timestamp = models.DateTimeField(auto_now_add=True)
93
    appname = models.CharField(max_length=128, verbose_name='appname', null=True)
94
    slug = models.CharField(max_length=128, verbose_name='slug', null=True)
95

  
96
    def __str__(self):
97
        return '%s %s %s' % (self.timestamp, self.appname, self.slug)
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
from passerelle.base.models import ApiUser, AccessRight, SMSResource, SMSLog
7
from passerelle.base.models import ApiUser, AccessRight
8
from passerelle.sms.models import SMSResource, SMSLog
8 9
from passerelle.utils.jsonresponse import APIError
9 10

  
10 11
from test_manager import login, admin_user
11 12

  
12 13
import utils
13 14

  
14 15
pytestmark = pytest.mark.django_db
15 16

  
16
-