Projet

Général

Profil

0001-sms-add-number-authorization-masks-to-SMSResource-39.patch

Voir les différences:

Subject: [PATCH 1/2] sms: add number authorization masks to SMSResource
 (#39650)

 .../migrations/0010_auto_20200505_1216.py     | 26 +++++++
 .../migrations/0009_auto_20200505_1216.py     | 26 +++++++
 .../migrations/0009_auto_20200505_1216.py     | 26 +++++++
 .../ovh/migrations/0009_auto_20200505_1216.py | 26 +++++++
 .../migrations/0009_auto_20200505_1216.py     | 26 +++++++
 passerelle/sms/forms.py                       | 26 +++++++
 passerelle/sms/models.py                      | 74 +++++++++++++++++++
 tests/test_sms.py                             | 21 ++++++
 8 files changed, 251 insertions(+)
 create mode 100644 passerelle/apps/choosit/migrations/0010_auto_20200505_1216.py
 create mode 100644 passerelle/apps/mobyt/migrations/0009_auto_20200505_1216.py
 create mode 100644 passerelle/apps/orange/migrations/0009_auto_20200505_1216.py
 create mode 100644 passerelle/apps/ovh/migrations/0009_auto_20200505_1216.py
 create mode 100644 passerelle/apps/oxyd/migrations/0009_auto_20200505_1216.py
passerelle/apps/choosit/migrations/0010_auto_20200505_1216.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2020-05-05 10:16
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6
import passerelle.sms.models
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('choosit', '0009_choositsmsgateway_max_message_length'),
13
    ]
14

  
15
    operations = [
16
        migrations.AddField(
17
            model_name='choositsmsgateway',
18
            name='authorized',
19
            field=passerelle.sms.models.SMSMultipleChoiceField(choices=[('fr-metro', 'France mainland (+33)'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgium (+32) ')], default=['fr-metro', 'fr-domtom', 'be'], max_length=128, null=True, verbose_name='Authorized Countries'),
20
        ),
21
        migrations.AddField(
22
            model_name='choositsmsgateway',
23
            name='premium_rate',
24
            field=models.CharField(choices=[('no', 'Do no allow'), ('yes', 'Allow')], default='no', help_text='This option is only applyed to France mainland', max_length=32, verbose_name='Premium rate numbers'),
25
        ),
26
    ]
passerelle/apps/mobyt/migrations/0009_auto_20200505_1216.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2020-05-05 10:16
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6
import passerelle.sms.models
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('mobyt', '0008_auto_20200310_1539'),
13
    ]
14

  
15
    operations = [
16
        migrations.AddField(
17
            model_name='mobytsmsgateway',
18
            name='authorized',
19
            field=passerelle.sms.models.SMSMultipleChoiceField(choices=[('fr-metro', 'France mainland (+33)'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgium (+32) ')], default=['fr-metro', 'fr-domtom', 'be'], max_length=128, null=True, verbose_name='Authorized Countries'),
20
        ),
21
        migrations.AddField(
22
            model_name='mobytsmsgateway',
23
            name='premium_rate',
24
            field=models.CharField(choices=[('no', 'Do no allow'), ('yes', 'Allow')], default='no', help_text='This option is only applyed to France mainland', max_length=32, verbose_name='Premium rate numbers'),
25
        ),
26
    ]
passerelle/apps/orange/migrations/0009_auto_20200505_1216.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2020-05-05 10:16
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6
import passerelle.sms.models
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('orange', '0008_auto_20200412_1240'),
13
    ]
14

  
15
    operations = [
16
        migrations.AddField(
17
            model_name='orangesmsgateway',
18
            name='authorized',
19
            field=passerelle.sms.models.SMSMultipleChoiceField(choices=[('fr-metro', 'France mainland (+33)'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgium (+32) ')], default=['fr-metro', 'fr-domtom', 'be'], max_length=128, null=True, verbose_name='Authorized Countries'),
20
        ),
21
        migrations.AddField(
22
            model_name='orangesmsgateway',
23
            name='premium_rate',
24
            field=models.CharField(choices=[('no', 'Do no allow'), ('yes', 'Allow')], default='no', help_text='This option is only applyed to France mainland', max_length=32, verbose_name='Premium rate numbers'),
25
        ),
26
    ]
passerelle/apps/ovh/migrations/0009_auto_20200505_1216.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2020-05-05 10:16
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6
import passerelle.sms.models
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('ovh', '0008_ovhsmsgateway_max_message_length'),
13
    ]
14

  
15
    operations = [
16
        migrations.AddField(
17
            model_name='ovhsmsgateway',
18
            name='authorized',
19
            field=passerelle.sms.models.SMSMultipleChoiceField(choices=[('fr-metro', 'France mainland (+33)'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgium (+32) ')], default=['fr-metro', 'fr-domtom', 'be'], max_length=128, null=True, verbose_name='Authorized Countries'),
20
        ),
21
        migrations.AddField(
22
            model_name='ovhsmsgateway',
23
            name='premium_rate',
24
            field=models.CharField(choices=[('no', 'Do no allow'), ('yes', 'Allow')], default='no', help_text='This option is only applyed to France mainland', max_length=32, verbose_name='Premium rate numbers'),
25
        ),
26
    ]
passerelle/apps/oxyd/migrations/0009_auto_20200505_1216.py
1
# -*- coding: utf-8 -*-
2
# Generated by Django 1.11.18 on 2020-05-05 10:16
3
from __future__ import unicode_literals
4

  
5
from django.db import migrations, models
6
import passerelle.sms.models
7

  
8

  
9
class Migration(migrations.Migration):
10

  
11
    dependencies = [
12
        ('oxyd', '0008_oxydsmsgateway_max_message_length'),
13
    ]
14

  
15
    operations = [
16
        migrations.AddField(
17
            model_name='oxydsmsgateway',
18
            name='authorized',
19
            field=passerelle.sms.models.SMSMultipleChoiceField(choices=[('fr-metro', 'France mainland (+33)'), ('fr-domtom', 'France DOM/TOM (+262, etc.)'), ('be', 'Belgium (+32) ')], default=['fr-metro', 'fr-domtom', 'be'], max_length=128, null=True, verbose_name='Authorized Countries'),
20
        ),
21
        migrations.AddField(
22
            model_name='oxydsmsgateway',
23
            name='premium_rate',
24
            field=models.CharField(choices=[('no', 'Do no allow'), ('yes', 'Allow')], default='no', help_text='This option is only applyed to France mainland', max_length=32, verbose_name='Premium rate numbers'),
25
        ),
26
    ]
passerelle/sms/forms.py
8 8
#
9 9
# This program is distributed in the hope that it will be useful,
10 10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 12
# GNU Affero General Public License for more details.
13 13
#
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
from django import forms
17
from django.utils.translation import ugettext_lazy as _
18

  
16 19
from passerelle.forms import GenericConnectorForm
17 20
from passerelle.sms.models import SMSResource
18 21

  
19 22

  
20 23
class SMSConnectorForm(GenericConnectorForm):
21 24
    class Meta:
22 25
        model = SMSResource
23 26
        fields = '__all__'
27
        widgets = {
28
            'premium_rate': forms.RadioSelect,
29
        }
30

  
31
    def __init__(self, *args, **kwargs):
32
        super(SMSConnectorForm, self).__init__(*args, **kwargs)
33

  
34
        FR_METRO = 'fr-metro'
35
        FR_DOMTOM = 'fr-domtom'
36
        BE = 'be'
37
        ALL = 'all'
38
        AUTHORIZED = [
39
            (FR_METRO, _('France mainland (+33)')),
40
            (FR_DOMTOM, _('France DOM/TOM (+262, etc.)')),
41
            (BE, _('Belgium (+32) ')),
42
            (ALL, _('All')),
43
        ]
44

  
45
        self.fields['authorized'] = forms.MultipleChoiceField(
46
            choices=AUTHORIZED,
47
            widget=forms.CheckboxSelectMultiple,
48
            label=_('Authorized Countries'),
49
        )
passerelle/sms/models.py
11 11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 12
# GNU Affero General Public License for more details.
13 13
#
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16
import logging
17 17
import re
18 18

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

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

  
28 29

  
30
def parse_choice(choice_string):
31
    if choice_string is None:
32
        return []
33
    return choice_string[1:-1].split(',')
34

  
35

  
36
class SMSMultipleChoiceField(models.TextField):
37
    def from_db_value(self, value, *args, **kwargs):
38
        if value is None:
39
            return value
40
        return parse_choice(value)
41

  
42
    def to_python(self, value):
43
        if isinstance(value, list):
44
            return value
45
        if value is None:
46
            return value
47
        return parse_choice(value)
48

  
49
    def validate(self, value, model_instance):
50
        if not self.editable:
51
            # Skip validation for non-editable fields.
52
            return
53

  
54
        for key in value:
55
            for option_key, option_value in self.choices:
56
                if key == option_key:
57
                    break
58
            else:
59
                raise ValidationError(
60
                    self.error_messages['invalid_choice'],
61
                    code='invalid_choice',
62
                    params={'value': value},
63
                )
64

  
65

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

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

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

  
79
    FR_METRO = 'fr-metro'
80
    FR_DOMTOM = 'fr-domtom'
81
    BE = 'be'
82
    ALL = 'all'
83
    AUTHORIZED = [
84
        (FR_METRO, _('France mainland (+33 [67])')),
85
        (FR_DOMTOM, _('France DOM/TOM (+262, etc.)')),
86
        (BE, _('Belgian (+32 4[5-9]) ')),
87
        (ALL, _('All')),
88
    ]
89
    authorized = SMSMultipleChoiceField(
90
        _('Authorized Countries'),
91
        max_length=128, null=True, choices=AUTHORIZED, default=[ALL])
92

  
93
    NO = 'no'
94
    YES = 'yes'
95
    PREMIUM_RATE = [
96
        (NO, _('Do no allow')),
97
        (YES, _('Allow')),
98
    ]
99
    premium_rate = models.CharField(
100
        _('Premium rate numbers'),
101
        max_length=32, choices=PREMIUM_RATE, default=NO,
102
        help_text=_('This option is only applyed to France mainland')
103
    )
104

  
105
    def _get_authorized_display(self):
106
        result = []
107
        for key, value in self.AUTHORIZED:
108
            if key in self.authorized:
109
                result.append(str(value))
110
        return ', '.join(result)
111

  
112
    def __init__(self, *args, **kwargs):
113
        super(SMSResource, self).__init__(*args, **kwargs)
114
        self.get_authorized_display = self._get_authorized_display
115

  
42 116
    def clean_numbers(self, destinations):
43 117
        numbers = []
44 118
        for dest in destinations:
45 119
            # most gateways needs the number prefixed by the country code, this is
46 120
            # really unfortunate.
47 121
            dest = dest.strip()
48 122
            number = ''.join(re.findall('[0-9]', dest))
49 123
            if dest.startswith('+'):
tests/test_sms.py
1
import logging
1 2
import mock
2 3
import pytest
3 4

  
4 5
from django.contrib.contenttypes.models import ContentType
5 6
from django.utils.translation import ugettext as _
6 7

  
7 8
from passerelle.apps.choosit.models import ChoositSMSGateway
8 9
from passerelle.apps.ovh.models import OVHSMSGateway
......
119 120
@pytest.mark.parametrize('connector', [ChoositSMSGateway], indirect=True)
120 121
def test_manager(admin_user, app, connector):
121 122
    app = login(app)
122 123
    path = '/%s/%s/' % (connector.get_connector_slug(), connector.slug)
123 124
    resp = app.get(path)
124 125
    assert '33' in [
125 126
        x.text for x in resp.html.find('div', {'id': 'description'}).find_all('p')
126 127
        if x.text.startswith(_('Default country code'))][0]
128
    assert _('All') in [
129
        x.text for x in resp.html.find_all('p')
130
        if x.text.startswith(_('Authorized Countries'))][0]
131
    assert _('Do no allow') in [
132
        x.text for x in resp.html.find_all('p')
133
        if x.text.startswith(_('Premium rate numbers'))][0]
134

  
135
    path = '/manage/%s/%s/edit' % (connector.get_connector_slug(), connector.slug)
136
    resp = app.get(path)
137
    resp.form['authorized'] = []
138
    resp = resp.form.submit()
139
    assert resp.html.find('div', {'class': 'errornotice'}).p.text == \
140
        'There were errors processing your form.'
141
    resp.html.find('div', {'class': 'error'}).text.strip() == 'This field is required.'
142
    resp.form['authorized'] = [SMSResource.FR_METRO, SMSResource.FR_DOMTOM]
143
    resp = resp.form.submit()
144
    resp = resp.follow()
145
    assert _('France mainland (+33 [67])') in [
146
        x.text for x in resp.html.find_all('p')
147
        if x.text.startswith(_('Authorized Countries'))][0]
127
-