From 07dab5d2af34084aacff82865b1da50c0842c8db Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Tue, 10 Mar 2020 15:25:43 +0100 Subject: [PATCH 1/2] sms: turn mixin into abstract class (#39654) --- .../apps/choosit/migrations/0001_initial.py | 2 +- passerelle/apps/choosit/models.py | 5 +- .../apps/mobyt/migrations/0001_initial.py | 2 +- passerelle/apps/mobyt/models.py | 5 +- .../apps/orange/migrations/0001_initial.py | 2 +- passerelle/apps/orange/models.py | 5 +- .../apps/ovh/migrations/0001_initial.py | 2 +- passerelle/apps/ovh/models.py | 5 +- .../apps/oxyd/migrations/0001_initial.py | 2 +- passerelle/apps/oxyd/models.py | 5 +- passerelle/base/models.py | 54 +++++++++++++++++ passerelle/sms/__init__.py | 58 ------------------- tests/test_sms.py | 17 +++--- 13 files changed, 77 insertions(+), 87 deletions(-) delete mode 100644 passerelle/sms/__init__.py diff --git a/passerelle/apps/choosit/migrations/0001_initial.py b/passerelle/apps/choosit/migrations/0001_initial.py index f6ced43e..2e42e847 100644 --- a/passerelle/apps/choosit/migrations/0001_initial.py +++ b/passerelle/apps/choosit/migrations/0001_initial.py @@ -56,7 +56,7 @@ class Migration(migrations.Migration): 'db_table': 'sms_choosit', 'verbose_name': 'Choosit', }, - bases=(models.Model, passerelle.sms.SMSGatewayMixin), + bases=(models.Model,), ), migrations.AddField( model_name='choositregistergateway', diff --git a/passerelle/apps/choosit/models.py b/passerelle/apps/choosit/models.py index 026da911..a4678dcd 100644 --- a/passerelle/apps/choosit/models.py +++ b/passerelle/apps/choosit/models.py @@ -7,11 +7,10 @@ from django.utils.translation import ugettext_lazy as _ from django.db import models from passerelle.utils.jsonresponse import APIError -from passerelle.base.models import BaseResource -from passerelle.sms import SMSGatewayMixin +from passerelle.base.models import SMSResource -class ChoositSMSGateway(BaseResource, SMSGatewayMixin): +class ChoositSMSGateway(SMSResource): 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') diff --git a/passerelle/apps/mobyt/migrations/0001_initial.py b/passerelle/apps/mobyt/migrations/0001_initial.py index a4a3846b..9dfc9007 100644 --- a/passerelle/apps/mobyt/migrations/0001_initial.py +++ b/passerelle/apps/mobyt/migrations/0001_initial.py @@ -29,6 +29,6 @@ class Migration(migrations.Migration): 'db_table': 'sms_mobyt', 'verbose_name': 'Mobyt', }, - bases=(models.Model, passerelle.sms.SMSGatewayMixin), + bases=(models.Model,), ), ] diff --git a/passerelle/apps/mobyt/models.py b/passerelle/apps/mobyt/models.py index 52fa8dca..4983b9d7 100644 --- a/passerelle/apps/mobyt/models.py +++ b/passerelle/apps/mobyt/models.py @@ -4,11 +4,10 @@ from django.db import models import requests from passerelle.utils.jsonresponse import APIError -from passerelle.base.models import BaseResource -from passerelle.sms import SMSGatewayMixin +from passerelle.base.models import SMSResource -class MobytSMSGateway(BaseResource, SMSGatewayMixin): +class MobytSMSGateway(SMSResource): URL = 'http://multilevel.mobyt.fr/sms/batch.php' MESSAGES_QUALITIES = ( ('l', _('sms direct')), diff --git a/passerelle/apps/orange/migrations/0001_initial.py b/passerelle/apps/orange/migrations/0001_initial.py index a6d6d1e8..5099655e 100644 --- a/passerelle/apps/orange/migrations/0001_initial.py +++ b/passerelle/apps/orange/migrations/0001_initial.py @@ -26,6 +26,6 @@ class Migration(migrations.Migration): 'db_table': 'sms_orange', 'verbose_name': 'Orange', }, - bases=(models.Model, passerelle.sms.SMSGatewayMixin), + bases=(models.Model,), ), ] diff --git a/passerelle/apps/orange/models.py b/passerelle/apps/orange/models.py index d7eb4c98..eb1a2aa9 100644 --- a/passerelle/apps/orange/models.py +++ b/passerelle/apps/orange/models.py @@ -2,13 +2,12 @@ from django.core.files import File from django.utils.translation import ugettext_lazy as _ from django.db import models -from passerelle.base.models import BaseResource -from passerelle.sms import SMSGatewayMixin +from passerelle.base.models import SMSResource from . import soap -class OrangeSMSGateway(BaseResource, SMSGatewayMixin): +class OrangeSMSGateway(SMSResource): keystore = models.FileField(upload_to='orange', blank=True, null=True, verbose_name=_('Keystore'), help_text=_('Certificate and private key in PEM format')) diff --git a/passerelle/apps/ovh/migrations/0001_initial.py b/passerelle/apps/ovh/migrations/0001_initial.py index 2f6c058c..326268ba 100644 --- a/passerelle/apps/ovh/migrations/0001_initial.py +++ b/passerelle/apps/ovh/migrations/0001_initial.py @@ -32,6 +32,6 @@ class Migration(migrations.Migration): 'db_table': 'sms_ovh', 'verbose_name': 'OVH', }, - bases=(models.Model, passerelle.sms.SMSGatewayMixin), + bases=(models.Model,), ), ] diff --git a/passerelle/apps/ovh/models.py b/passerelle/apps/ovh/models.py index b72b1edc..d9d96135 100644 --- a/passerelle/apps/ovh/models.py +++ b/passerelle/apps/ovh/models.py @@ -5,11 +5,10 @@ from django.db import models from django.utils.encoding import force_text from passerelle.utils.jsonresponse import APIError -from passerelle.base.models import BaseResource -from passerelle.sms import SMSGatewayMixin +from passerelle.base.models import SMSResource -class OVHSMSGateway(BaseResource, SMSGatewayMixin): +class OVHSMSGateway(SMSResource): URL = 'https://www.ovh.com/cgi-bin/sms/http2sms.cgi' MESSAGES_CLASSES = ( (0, _('Message are directly shown to users on phone screen ' diff --git a/passerelle/apps/oxyd/migrations/0001_initial.py b/passerelle/apps/oxyd/migrations/0001_initial.py index 5510816f..39d1d533 100644 --- a/passerelle/apps/oxyd/migrations/0001_initial.py +++ b/passerelle/apps/oxyd/migrations/0001_initial.py @@ -28,6 +28,6 @@ class Migration(migrations.Migration): 'db_table': 'sms_oxyd', 'verbose_name': 'Oxyd', }, - bases=(models.Model, passerelle.sms.SMSGatewayMixin), + bases=(models.Model,), ), ] diff --git a/passerelle/apps/oxyd/models.py b/passerelle/apps/oxyd/models.py index 6a9f36d4..5e945b16 100644 --- a/passerelle/apps/oxyd/models.py +++ b/passerelle/apps/oxyd/models.py @@ -4,12 +4,11 @@ from django.db import models from django.utils.encoding import force_text from passerelle.utils.jsonresponse import APIError -from passerelle.base.models import BaseResource -from passerelle.sms import SMSGatewayMixin +from passerelle.base.models import SMSResource from django.utils.translation import ugettext_lazy as _ -class OxydSMSGateway(BaseResource, SMSGatewayMixin): +class OxydSMSGateway(SMSResource): 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, diff --git a/passerelle/base/models.py b/passerelle/base/models.py index 9ee9484f..02e46000 100644 --- a/passerelle/base/models.py +++ b/passerelle/base/models.py @@ -37,6 +37,7 @@ import jsonfield import passerelle import requests +from passerelle.compat import json_loads from passerelle.utils.api import endpoint from passerelle.utils.jsonresponse import APIError @@ -899,3 +900,56 @@ class HTTPResource(models.Model): class Meta: abstract = True + + +class SMSResource(BaseResource): + category = _('SMS Providers') + documentation_url = 'https://doc-publik.entrouvert.com/admin-fonctionnel/les-tutos/configuration-envoi-sms/' + + _can_send_messages_description = _('Sending messages is limited to the following API users:') + + @classmethod + 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('+'): + number = '00' + number + elif number.startswith('00'): + # assumes 00 is international access code, remove it + 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(perm='can_send_messages', methods=['post']) + def send(self, request, *args, **kwargs): + try: + data = json_loads(request.body) + assert isinstance(data, dict), 'JSON payload is not a dict' + assert 'message' in data, 'missing "message" in JSON payload' + assert 'from' in data, 'missing "from" in JSON payload' + assert 'to' in data, 'missing "to" in JSON payload' + assert isinstance(data['message'], six.text_type), 'message is not a string' + assert isinstance(data['from'], six.text_type), 'from is not a string' + assert all(map(lambda x: isinstance(x, six.text_type), data['to'])), \ + 'to is not a list of strings' + except (ValueError, AssertionError) as e: + raise APIError('Payload error: %s' % e) + logging.info('sending message %r to %r with sending number %r', + data['message'], data['to'], data['from']) + if 'nostop' in request.GET: + return {'data': self.send_msg(data['message'], data['from'], data['to'], stop=False)} + return {'data': self.send_msg(data['message'], data['from'], data['to'], stop=True)} + + class Meta: + abstract = True diff --git a/passerelle/sms/__init__.py b/passerelle/sms/__init__.py deleted file mode 100644 index 5e5c1b24..00000000 --- a/passerelle/sms/__init__.py +++ /dev/null @@ -1,58 +0,0 @@ -import logging -import re - -from django.utils import six -from django.utils.translation import ugettext_lazy as _ -from passerelle.compat import json_loads -from passerelle.utils.api import endpoint -from passerelle.utils.jsonresponse import APIError - - -class SMSGatewayMixin(object): - category = _('SMS Providers') - documentation_url = 'https://doc-publik.entrouvert.com/admin-fonctionnel/les-tutos/configuration-envoi-sms/' - - _can_send_messages_description = _('Sending messages is limited to the following API users:') - - @classmethod - 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('+'): - number = '00' + number - elif number.startswith('00'): - # assumes 00 is international access code, remove it - 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(perm='can_send_messages', methods=['post']) - def send(self, request, *args, **kwargs): - try: - data = json_loads(request.body) - assert isinstance(data, dict), 'JSON payload is not a dict' - assert 'message' in data, 'missing "message" in JSON payload' - assert 'from' in data, 'missing "from" in JSON payload' - assert 'to' in data, 'missing "to" in JSON payload' - assert isinstance(data['message'], six.text_type), 'message is not a string' - assert isinstance(data['from'], six.text_type), 'from is not a string' - assert all(map(lambda x: isinstance(x, six.text_type), data['to'])), \ - 'to is not a list of strings' - except (ValueError, AssertionError) as e: - raise APIError('Payload error: %s' % e) - logging.info('sending message %r to %r with sending number %r', - data['message'], data['to'], data['from']) - if 'nostop' in request.GET: - return {'data': self.send_msg(data['message'], data['from'], data['to'], stop=False)} - return {'data': self.send_msg(data['message'], data['from'], data['to'], stop=True)} diff --git a/tests/test_sms.py b/tests/test_sms.py index 46ec3df9..8386a18b 100644 --- a/tests/test_sms.py +++ b/tests/test_sms.py @@ -2,8 +2,7 @@ import pytest from django.contrib.contenttypes.models import ContentType -from passerelle.base.models import ApiUser, AccessRight -from passerelle.sms import SMSGatewayMixin +from passerelle.base.models import ApiUser, AccessRight, SMSResource from test_manager import login, admin_user @@ -11,16 +10,16 @@ import utils pytestmark = pytest.mark.django_db -klasses = SMSGatewayMixin.__subclasses__() +klasses = SMSResource.__subclasses__() def test_clean_numbers(): - 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'] + assert SMSResource.clean_numbers(['+ 33 12']) == ['003312'] + assert SMSResource.clean_numbers(['0 0 33 12']) == ['003312'] + assert SMSResource.clean_numbers(['0 12']) == ['003312'] + assert SMSResource.clean_numbers(['+ 33 12'], '32', '1') == ['003312'] + assert SMSResource.clean_numbers(['0 0 33 12'], '32', '1') == ['003312'] + assert SMSResource.clean_numbers(['1 12'], '32', '1') == ['003212'] @pytest.fixture(params=klasses) -- 2.20.1