From ebeec206394f83e13e8fea71562bb834b372906e Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Tue, 31 Mar 2015 15:53:13 +0200 Subject: [PATCH] sms: improve SMSGatewayMixin.clean_numbers() (fixes #6867) - normalize numbers to the 00... international format - a default_trunk_prefix setting to all sms backends - set default_trunk_prefix and default_country_code to French ones - modify test --- .../0005_choositsmsgateway_default_trunk_prefix.py | 20 +++++++++++++++++++ passerelle/apps/choosit/models.py | 23 +++++++++++++--------- .../0005_mobytsmsgateway_default_trunk_prefix.py | 20 +++++++++++++++++++ passerelle/apps/mobyt/models.py | 13 ++++++++---- .../0005_ovhsmsgateway_default_trunk_prefix.py | 20 +++++++++++++++++++ passerelle/apps/ovh/models.py | 21 ++++++++++++-------- .../0005_oxydsmsgateway_default_trunk_prefix.py | 20 +++++++++++++++++++ passerelle/apps/oxyd/models.py | 18 +++++++++++------ passerelle/sms/__init__.py | 19 +++++++++++------- tests/test_sms.py | 17 +++++++--------- 10 files changed, 147 insertions(+), 44 deletions(-) create mode 100644 passerelle/apps/choosit/migrations/0005_choositsmsgateway_default_trunk_prefix.py create mode 100644 passerelle/apps/mobyt/migrations/0005_mobytsmsgateway_default_trunk_prefix.py create mode 100644 passerelle/apps/ovh/migrations/0005_ovhsmsgateway_default_trunk_prefix.py create mode 100644 passerelle/apps/oxyd/migrations/0005_oxydsmsgateway_default_trunk_prefix.py diff --git a/passerelle/apps/choosit/migrations/0005_choositsmsgateway_default_trunk_prefix.py b/passerelle/apps/choosit/migrations/0005_choositsmsgateway_default_trunk_prefix.py new file mode 100644 index 0000000..d85d3d4 --- /dev/null +++ b/passerelle/apps/choosit/migrations/0005_choositsmsgateway_default_trunk_prefix.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('choosit', '0004_auto_20160407_0456'), + ] + + operations = [ + migrations.AddField( + model_name='choositsmsgateway', + name='default_trunk_prefix', + field=models.CharField(default='0', max_length=2), + preserve_default=True, + ), + ] diff --git a/passerelle/apps/choosit/models.py b/passerelle/apps/choosit/models.py index d8bb92a..2db8fff 100644 --- a/passerelle/apps/choosit/models.py +++ b/passerelle/apps/choosit/models.py @@ -14,8 +14,11 @@ from .choosit import ChoositRegisterWS class ChoositSMSGateway(BaseResource, SMSGatewayMixin): - key = models.CharField(max_length=64) - default_country_code = models.CharField(max_length=3, default=u'33') + key = models.CharField(verbose_name=_('key'), max_length=64) + default_country_code = models.CharField(verbose_name=_('default country code'), max_length=3, + default=u'33') + default_trunk_prefix = models.CharField(verbose_name=_('default trunk prefix'), max_length=2, + default=u'0') # FIXME: add regexp field, to check destination and from format TEST_DEFAULTS = { @@ -29,9 +32,9 @@ class ChoositSMSGateway(BaseResource, SMSGatewayMixin): 'err': 1, 'err_desc': 'Choosit error: some destinations failed', 'data': [ - [u'33688888888', u'Choosit error: bad JSON response No JSON object ' + [u'0033688888888', u'Choosit error: bad JSON response No JSON object ' 'could be decoded'], - [u'33677777777', u'Choosit error: bad JSON response No JSON object ' + [u'0033677777777', u'Choosit error: bad JSON response No JSON object ' 'could be decoded'], ] } @@ -44,8 +47,8 @@ class ChoositSMSGateway(BaseResource, SMSGatewayMixin): 'err': 1, 'err_desc': 'Choosit error: some destinations failed', 'data': [ - [u'33688888888', u'Choosit error: not ok'], - [u'33677777777', u'Choosit error: not ok'], + [u'0033688888888', u'Choosit error: not ok'], + [u'0033677777777', u'Choosit error: not ok'], ], } }, @@ -57,8 +60,8 @@ class ChoositSMSGateway(BaseResource, SMSGatewayMixin): 'result': { 'err': 0, 'data': [ - [u'33688888888', {'result': u'Envoi terminé', 'sms_id': 1234}], - [u'33677777777', {'result': u'Envoi terminé', 'sms_id': 1234}], + [u'0033688888888', {'result': u'Envoi terminé', 'sms_id': 1234}], + [u'0033677777777', {'result': u'Envoi terminé', 'sms_id': 1234}], ], } } @@ -83,7 +86,9 @@ class ChoositSMSGateway(BaseResource, SMSGatewayMixin): """Send a SMS using the Choosit provider""" # from http://sms.choosit.com/documentation_technique.html # unfortunately it lacks a batch API... - destinations = self.clean_numbers(destinations, self.default_country_code, prefix='') + destinations = self.clean_numbers(destinations, + self.default_country_code, + self.default_trunk_prefix) results = [] for dest in destinations: params = { diff --git a/passerelle/apps/mobyt/migrations/0005_mobytsmsgateway_default_trunk_prefix.py b/passerelle/apps/mobyt/migrations/0005_mobytsmsgateway_default_trunk_prefix.py new file mode 100644 index 0000000..fcfe2f2 --- /dev/null +++ b/passerelle/apps/mobyt/migrations/0005_mobytsmsgateway_default_trunk_prefix.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('mobyt', '0004_auto_20160407_0456'), + ] + + operations = [ + migrations.AddField( + model_name='mobytsmsgateway', + name='default_trunk_prefix', + field=models.CharField(default='0', max_length=2), + preserve_default=True, + ), + ] diff --git a/passerelle/apps/mobyt/models.py b/passerelle/apps/mobyt/models.py index 26baff3..20e1652 100644 --- a/passerelle/apps/mobyt/models.py +++ b/passerelle/apps/mobyt/models.py @@ -15,11 +15,14 @@ class MobytSMSGateway(BaseResource, SMSGatewayMixin): ('ll', _('sms low-cost')), ('n', _('sms top')), ) - username = models.CharField(max_length=64) - password = models.CharField(max_length=64) + username = models.CharField(verbose_name=_('username'), max_length=64) + password = models.CharField(verbose_name=_('password'), max_length=64) quality = models.CharField(max_length=4, choices=MESSAGES_QUALITIES, default='l', verbose_name=_('message quality')) - default_country_code = models.CharField(max_length=3, default=u'33') + default_country_code = models.CharField(verbose_name=_('default contry code'), max_length=3, + default=u'33') + default_trunk_prefix = models.CharField(verbose_name=_('default trunk prefix'), max_length=2, + default=u'0') # FIXME: add regexp field, to check destination and from format manager_view_template_name = 'passerelle/manage/messages_service_view.html' @@ -53,7 +56,9 @@ class MobytSMSGateway(BaseResource, SMSGatewayMixin): def send_msg(self, text, sender, destinations, **kwargs): """Send a SMS using the Mobyt provider""" # unfortunately it lacks a batch API... - destinations = self.clean_numbers(destinations, self.default_country_code) + destinations = self.clean_numbers(destinations, + self.default_country_code, + self.default_trunk_prefix) rcpt = ','.join(destinations) params = { 'user': self.username, diff --git a/passerelle/apps/ovh/migrations/0005_ovhsmsgateway_default_trunk_prefix.py b/passerelle/apps/ovh/migrations/0005_ovhsmsgateway_default_trunk_prefix.py new file mode 100644 index 0000000..e9a65b8 --- /dev/null +++ b/passerelle/apps/ovh/migrations/0005_ovhsmsgateway_default_trunk_prefix.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ovh', '0004_auto_20160407_0456'), + ] + + operations = [ + migrations.AddField( + model_name='ovhsmsgateway', + name='default_trunk_prefix', + field=models.CharField(default='0', max_length=2), + preserve_default=True, + ), + ] diff --git a/passerelle/apps/ovh/models.py b/passerelle/apps/ovh/models.py index e9e08c1..6c6f5fc 100644 --- a/passerelle/apps/ovh/models.py +++ b/passerelle/apps/ovh/models.py @@ -21,14 +21,18 @@ class OVHSMSGateway(BaseResource, SMSGatewayMixin): (3, _('Messages are stored in external storage like a PDA or ' 'a PC.')), ) - account = models.CharField(max_length=64) - username = models.CharField(max_length=64) - password = models.CharField(max_length=64) + account = models.CharField(verbose_name=_('account'), max_length=64) + username = models.CharField(verbose_name=_('username'), max_length=64) + password = models.CharField(verbose_name=_('password'), max_length=64) msg_class = models.IntegerField(choices=MESSAGES_CLASSES, default=1, verbose_name=_('message class')) - credit_threshold_alert = models.PositiveIntegerField(default=100) - default_country_code = models.CharField(max_length=3, default=u'33') - credit_left = models.PositiveIntegerField(default=0) + credit_threshold_alert = models.PositiveIntegerField(verbose_name=_('credit alert threshold'), + default=100) + default_country_code = models.CharField(verbose_name=_('default country code'), max_length=3, + default=u'33') + default_trunk_prefix = models.CharField(verbose_name=_('default trunk prefix'), max_length=2, + default=u'0') + credit_left = models.PositiveIntegerField(verbose_name=_('credit left'), default=0) # FIXME: add regexp field, to check destination and from format manager_view_template_name = 'passerelle/manage/messages_service_view.html' @@ -83,8 +87,9 @@ class OVHSMSGateway(BaseResource, SMSGatewayMixin): def send_msg(self, text, sender, destinations, **kwargs): """Send a SMS using the OVH provider""" - # unfortunately it lacks a batch API... - destinations = self.clean_numbers(destinations, self.default_country_code) + destinations = self.clean_numbers(destinations, + self.default_country_code, + self.default_trunk_prefix) text = unicode(text).encode('utf-8') to = ','.join(destinations) diff --git a/passerelle/apps/oxyd/migrations/0005_oxydsmsgateway_default_trunk_prefix.py b/passerelle/apps/oxyd/migrations/0005_oxydsmsgateway_default_trunk_prefix.py new file mode 100644 index 0000000..a5c6c78 --- /dev/null +++ b/passerelle/apps/oxyd/migrations/0005_oxydsmsgateway_default_trunk_prefix.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('oxyd', '0004_auto_20160407_0456'), + ] + + operations = [ + migrations.AddField( + model_name='oxydsmsgateway', + name='default_trunk_prefix', + field=models.CharField(default='0', max_length=2), + preserve_default=True, + ), + ] diff --git a/passerelle/apps/oxyd/models.py b/passerelle/apps/oxyd/models.py index e52e710..627b30d 100644 --- a/passerelle/apps/oxyd/models.py +++ b/passerelle/apps/oxyd/models.py @@ -5,12 +5,16 @@ from django.db import models from passerelle.utils.jsonresponse import APIError from passerelle.base.models import BaseResource from passerelle.sms import SMSGatewayMixin +from django.utils.translation import ugettext_lazy as _ class OxydSMSGateway(BaseResource, SMSGatewayMixin): - username = models.CharField(max_length=64) - password = models.CharField(max_length=64) - default_country_code = models.CharField(max_length=3, default=u'33') + username = models.CharField(verbose_name=_('username'), max_length=64) + password = models.CharField(verbose_name=_('password'), max_length=64) + default_country_code = models.CharField(verbose_name=_('default country code'), max_length=3, + default=u'33') + default_trunk_prefix = models.CharField(verbose_name=_('default trunk prefix'), max_length=2, + default=u'0') # FIXME: add regexp field, to check destination and from format manager_view_template_name = 'passerelle/manage/messages_service_view.html' @@ -27,8 +31,8 @@ class OxydSMSGateway(BaseResource, SMSGatewayMixin): 'err': 1, 'err_desc': 'OXYD error: some destinations failed', 'data': [ - ['33688888888', "OXYD error: received content '' instead of 200"], - ['33677777777', "OXYD error: received content '' instead of 200"], + ['0033688888888', "OXYD error: received content '' instead of 200"], + ['0033677777777', "OXYD error: received content '' instead of 200"], ], } }, @@ -54,7 +58,9 @@ class OxydSMSGateway(BaseResource, SMSGatewayMixin): def send_msg(self, text, sender, destinations, **kwargs): """Send a SMS using the Oxyd provider""" # unfortunately it lacks a batch API... - destinations = self.clean_numbers(destinations, self.default_country_code, prefix='') + destinations = self.clean_numbers(destinations, + self.default_country_code, + self.default_trunk_prefix) results = [] for dest in destinations: params = { diff --git a/passerelle/sms/__init__.py b/passerelle/sms/__init__.py index 21d6240..f43a52e 100644 --- a/passerelle/sms/__init__.py +++ b/passerelle/sms/__init__.py @@ -11,21 +11,26 @@ class SMSGatewayMixin(object): category = _('SMS Providers') @classmethod - def clean_numbers(cls, destinations, default_country_code, prefix='+'): + def clean_numbers(cls, destinations, default_country_code='33', + default_trunk_prefix='0'): # Yeah France first ! numbers = [] for dest in destinations: # most gateways needs the number prefixed by the country code, this is # really unfortunate. + dest = dest.strip() number = ''.join(re.findall('[0-9]', dest)) if dest.startswith('+'): - pass # it already is fully qualified + number = '00' + number elif number.startswith('00'): # assumes 00 is international access code, remove it - number = number[2:] - elif number.startswith('0'): - # local prefix, remove 0 and add default country code - number = default_country_code + number[1:] - numbers.append(prefix + number) + pass + elif number.startswith(default_trunk_prefix): + number = '00' + default_country_code + number[len(default_trunk_prefix):] + else: + raise NotImplementedError('phone number %r is unsupported (no ' + 'international prefix, no local ' + 'trunk prefix)' % number) + numbers.append(number) return numbers @endpoint('json-api', perm='can_send_messages', methods=['post']) diff --git a/tests/test_sms.py b/tests/test_sms.py index 3fa669d..64802bf 100644 --- a/tests/test_sms.py +++ b/tests/test_sms.py @@ -15,15 +15,12 @@ klasses = SMSGatewayMixin.__subclasses__() def test_clean_numbers(): - assert SMSGatewayMixin.clean_numbers(['+ 33 12'], '33') == ['+3312'] - assert SMSGatewayMixin.clean_numbers(['0 0 33 12'], '33') == ['+3312'] - assert SMSGatewayMixin.clean_numbers(['0 12'], '33') == ['+3312'] - - -def test_clean_numbers_no_prefix(): - assert SMSGatewayMixin.clean_numbers(['+ 33 12'], '33', prefix='') == ['3312'] - assert SMSGatewayMixin.clean_numbers(['0 0 33 12'], '33', prefix='') == ['3312'] - assert SMSGatewayMixin.clean_numbers(['0 12'], '33', prefix='') == ['3312'] + assert SMSGatewayMixin.clean_numbers(['+ 33 12']) == ['003312'] + assert SMSGatewayMixin.clean_numbers(['0 0 33 12']) == ['003312'] + assert SMSGatewayMixin.clean_numbers(['0 12']) == ['003312'] + assert SMSGatewayMixin.clean_numbers(['+ 33 12'], '32', '1') == ['003312'] + assert SMSGatewayMixin.clean_numbers(['0 0 33 12'], '32', '1') == ['003312'] + assert SMSGatewayMixin.clean_numbers(['1 12'], '32', '1') == ['003212'] @pytest.fixture(params=klasses) @@ -60,11 +57,11 @@ def test_connectors(app, connector): for test_vector in getattr(connector, 'TEST_DEFAULTS', {}).get('test_vectors', []): with utils.mock_url(connector.URL, test_vector['response']): result = app.post_json(path, params=payload) - print result.json for key, value in test_vector['result'].iteritems(): assert key in result.json assert result.json[key] == value + def test_manage_views(admin_user, app, connector): url = '/%s/%s/' % (connector.get_connector_slug(), connector.slug) resp = app.get(url) -- 2.1.4