sms: remove all but passerelle provider (#39088)

This commit is contained in:
Frédéric Péters 2020-12-20 22:23:41 +01:00
parent fd45925d20
commit 3d2c05b87a
8 changed files with 29 additions and 314 deletions

View File

@ -486,7 +486,7 @@ def test_workflows_check_available_actions(pub):
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
pub.site_options.write(fd)
pub.cfg['sms'] = {'mode': 'foobar'}
pub.cfg['sms'] = {'passerelle_url': 'xx', 'sender': 'xx'}
pub.write_cfg()
workflow.criticality_levels = [WorkflowCriticalityLevel(name='green')]
workflow.store()
@ -814,7 +814,7 @@ def test_workflows_edit_sms_action(pub):
workflow.add_status(name='baz')
workflow.store()
pub.cfg['sms'] = {'mode': 'foobar'}
pub.cfg['sms'] = {'passerelle_url': 'xx', 'sender': 'xx'}
pub.write_cfg()
app = login(get_app(pub))

View File

@ -3419,7 +3419,7 @@ def test_per_user_view_tracking_code(pub, emails, sms_mocking):
user_view_resp = app.get('/backoffice/management/users/%s/' % user.id)
assert 'Send tracking code' in user_view_resp.text
pub.cfg['sms'] = {'mode': 'none'}
pub.cfg['sms'] = {}
pub.write_cfg()
resp = user_view_resp.click('Send tracking code')
assert not 'sms' in resp.form.fields
@ -3431,7 +3431,7 @@ def test_per_user_view_tracking_code(pub, emails, sms_mocking):
assert emails.get('Tracking Code reminder')['email_rcpt'] == [user.email]
assert form_class.get(number31.id).tracking_code in emails.get('Tracking Code reminder')['payload']
pub.cfg['sms'] = {'mode': 'xx'}
pub.cfg['sms'] = {'passerelle_url': 'xx', 'sender': 'xx'}
pub.write_cfg()
resp = user_view_resp.click('Send tracking code', index=0)
resp.form['method'].value = 'SMS'

View File

@ -2341,7 +2341,7 @@ def test_jump_missing_previous_mark(two_pubs):
def test_sms(pub, sms_mocking):
pub.cfg['sms'] = {'mode': 'xxx'}
pub.cfg['sms'] = {'sender': 'xxx', 'passerelle_url': 'http://passerelle.invalid/'}
formdef = FormDef()
formdef.name = 'baz'
formdef.fields = []

View File

@ -398,21 +398,16 @@ class HttpRequestsMocking(object):
return len(self.requests)
class SMSMocking(wcs.qommon.sms.MobytSMS):
def get_sms_class(self, mode):
if mode == 'none':
return None
return self
class SMSMocking(wcs.qommon.sms.PasserelleSMS):
def get_sms_class(self):
sms_cfg = get_publisher().cfg.get('sms', {})
if sms_cfg.get('sender') and sms_cfg.get('passerelle_url'):
return self
return None
def send(self, sender, destinations, text, quality=None):
def send(self, sender, destinations, text):
self.sms.append({'sender': sender, 'destinations': destinations, 'text': text})
def get_sms_left(self, type='standard'):
raise NotImplementedError
def get_money_left(self):
raise NotImplementedError
def __enter__(self):
self.sms = []
self.wcs_get_sms_class = wcs.qommon.sms.SMS.get_sms_class

View File

@ -425,7 +425,7 @@ class SettingsDirectory(QommonSettingsDirectory):
'template', 'emails', 'debug_options', 'language',
('import', 'p_import'), 'export', 'identification', 'sitename',
'sms', 'certificates', 'texts', 'install_theme',
'session', 'download_theme', 'smstest', 'postgresql',
'session', 'download_theme', 'postgresql',
('admin-permissions', 'admin_permissions'), 'geolocation',
'theme_preview', 'filetypes',
('user-template', 'user_template'),
@ -1101,103 +1101,22 @@ class SettingsDirectory(QommonSettingsDirectory):
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('SMS Options')
sms_cfg = get_cfg('sms', {})
mode = sms_cfg.get('mode', 'none')
sms = SMS.get_sms_class(mode)
if sms:
r += htmltext('<ul>')
try:
try:
r += htmltext('<li>%s %s</li>') % (_('SMS Credit:'), sms.get_money_left())
except NotImplementedError:
pass
try:
r += htmltext('<li>%s %s</li>') % (_('SMS Left:'), sms.get_sms_left())
except NotImplementedError:
pass
except errors.SMSError:
r += htmltext("<p>%s</li>") % _("Connection with SMS provider failed")
r += htmltext('</ul>')
form = Form(enctype='multipart/form-data')
form.add(SingleSelectWidget, 'mode', title = _('SMS Mode'),
value = mode,
options = [(str('none'), _('No support'), str('none'))]+
[(str(k), _(SMS.providers.get(k)[0]), str(k)) for k in SMS.providers.keys()])
form.add(StringWidget, 'sender', title=_('Sender (number of name)'),
value=sms_cfg.get('sender'))
form.add(StringWidget, 'passerelle_url', title=_('URL'),
value=sms_cfg.get('passerelle_url'))
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('.')
if sms:
for widget, name, title in sms.parameters:
form.add(widget, name, title=_(title),
value=sms_cfg.get(name, ''),
required=True)
if form.get_submit() and not form.has_errors():
cfg_submit(form, 'sms', ['mode'] + [x[1] for x in sms.parameters])
if mode != form.get_widget('mode').parse():
return redirect('sms')
else:
return redirect('.')
elif mode != form.get_widget('mode').parse():
cfg_submit(form, 'sms', ['mode',])
return redirect('sms')
else:
if form.get_submit() and form.get_widget('mode').parse() == str('none'):
return redirect('.')
if form.get_submit() and not form.has_errors():
cfg_submit(form, 'sms', ['mode',])
return redirect('sms')
else:
r += form.render()
cfg_submit(form, 'sms', ['sender', 'passerelle_url'])
return redirect('.')
if mode != 'none':
r += htmltext('<p><a href="smstest">%s</a></p>') % _('SMS Test')
return r.getvalue()
def smstest(self):
form = Form(enctype='multipart/form-data', action='smstest')
form.add(StringWidget, 'sender', title=_('Sender'), required=True)
form.add(StringWidget, 'destinations', title=_('Destinations'), required=True)
form.add(StringWidget, 'text', title=_('Text'), required=True)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('sms')
get_response().breadcrumb.append(('sms', _('SMS')))
get_response().breadcrumb.append(('smstest', _('SMS Test')))
html_top('settings', title = _('SMS Test'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('SMS Test')
r += form.render()
if form.get_submit() and not form.has_errors():
sms_cfg = get_cfg('sms', {})
mode = sms_cfg.get('mode', 'none')
sms = SMS.get_sms_class(mode)
sender = str(form.get_widget('sender').parse())
destinations = str(form.get_widget('destinations').parse()).split(str(','))
text = str(form.get_widget('text').parse())
try:
sms.send(sender, destinations, text)
except Exception as e:
r += htmltext('<pre>')
r += repr(e)
r += htmltext('</pre>')
else:
r += htmltext('<p>')
r += _('The SMS has been sent successfully.')
r += htmltext('</p>')
return r.getvalue()
def postgresql(self):

View File

@ -146,9 +146,7 @@ class SendCodeFormdefDirectory(Directory):
value=submitter_email)
sms_class = None
if get_publisher().use_sms_feature:
sms_cfg = get_cfg('sms', {})
mode = sms_cfg.get('mode', 'none')
sms_class = sms.SMS.get_sms_class(mode)
sms_class = sms.SMS.get_sms_class()
if sms_class:
form.add(StringWidget, 'sms', title=_('SMS Number'), required=False)
form.add(RadiobuttonsWidget, 'method',
@ -180,6 +178,7 @@ class SendCodeFormdefDirectory(Directory):
if sms_class and form.get_widget('method').parse() == 'sms':
# send sms
sitename = get_cfg('misc', {}).get('sitename') or 'w.c.s.'
sms_cfg = get_cfg('sms', {})
sender = sms_cfg.get('sender', sitename)[:11]
message = Template(message).render(data)
try:

View File

@ -24,188 +24,7 @@ from .form import StringWidget, PasswordWidget
from wcs.wscalls import call_webservice
class MobytSMS(object):
"""This class allows to send a SMS using Mobyt provider"""
parameters = [
(StringWidget, 'sender', N_('Sender (number or name)')),
(StringWidget, 'mobyt_username', N_('Username')),
(PasswordWidget, 'mobyt_password', N_('Password')),
]
def __init__(self):
sms_cfg = get_cfg('sms', {})
self.user = sms_cfg.get('mobyt_username', '')
self.password = sms_cfg.get('mobyt_password', '')
def send(self, sender, destinations, text, quality='l'):
""" Send a sms using Mobyt provider"""
rcpt = ""
for dest in destinations:
rcpt += "%s," % dest
rcpt = rcpt[:len(rcpt) - 1]
params = urllib.urlencode({
'user': self.user,
'pass': self.password,
'rcpt': rcpt,
'data': text,
'sender': sender,
'qty': quality
})
try:
r = misc.urlopen("http://multilevel.mobyt.fr/sms/batch.php", params)
except misc.ConnectionError as e:
raise errors.SMSError("failed to POST to mobyt.fr (%s)" % e)
answer = r.read()
r.close()
if answer[:2] == "KO":
raise errors.SMSError(answer[3:])
def get_credit(self, type):
params = urllib.urlencode({
'user': self.user,
'pass': self.password,
'type': type
})
try:
r = misc.urlopen("http://multilevel.mobyt.fr/sms/credit.php", params)
except misc.ConnectionError as e:
raise errors.SMSError("failed to POST to mobyt.fr (%s)" % e)
answer = r.read()
r.close()
if answer[:2] == "KO":
raise errors.SMSError(answer[3:])
else:
return answer[3:]
def get_sms_left(self, type="standard"):
"""
type (mobyt provider): standard, lowcost or top
"""
if type == "standard":
return self.get_credit("l")
elif type == "lowcost":
return self.get_credit("ll")
elif type == "top":
return self.get_credit("n")
else:
raise errors.SMSError("%s is invalid type for provider Mobyt" % type)
def get_money_left(self):
""" return money left in euros """
return self.get_credit("credit")
class OxydSMS(object):
"""This class allows to send a SMS using Oxyd provider"""
parameters = [
(StringWidget, 'oxyd_username', N_('Username')),
(PasswordWidget, 'oxyd_password', N_('Password')),
(StringWidget, 'oxyd_default_country_code', N_('Default Country Code')),
]
def __init__(self):
sms_cfg = get_cfg('sms', {})
self.user = sms_cfg.get('oxyd_username', '')
self.password = sms_cfg.get('oxyd_password', '')
self.default_country_code = sms_cfg.get('oxyd_default_country_code')
if not self.default_country_code:
self.default_country_code = '33' # arbitrary
def send(self, sender, destinations, text, quality=None):
"""Send a SMS using Oxyd provider"""
# unfortunately it lacks a batch API...
for dest in destinations:
# oxyd needs the number prefixed by the country code, this is
# really unfortunate.
number = ''.join(re.findall(r'\d', dest))
if dest.startswith('+'):
pass # it already is fully qualified
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 = self.default_country_code + number[1:]
params = urllib.urlencode({
'id': self.user,
'pass': self.password,
'num': number,
'sms': text,
'flash': '0'
})
try:
r = misc.urlopen('http://sms.oxyd.fr/send.php', params)
except misc.ConnectionError as e:
# XXX: add proper handling of errors
raise errors.SMSError("failed to POST to oxyd.fr (%s)" % e)
r.close()
def get_sms_left(self, type='standard'):
raise NotImplementedError
def get_money_left(self):
raise NotImplementedError
class ChoositSMS(object):
"""This class allows to send a SMS using the Choosit provider
http://sms.choosit.com/documentation_technique.html
"""
parameters = [
(StringWidget, 'choosit_key', N_('Key')),
(StringWidget, 'choosit_default_country_code', N_('Default Country Code')),
]
def __init__(self):
sms_cfg = get_cfg('sms', {})
self.key = sms_cfg.get('choosit_key', '')
self.default_country_code = sms_cfg.get('choosit_default_country_code')
if not self.default_country_code:
self.default_country_code = '33' # arbitrary
def send(self, sender, destinations, text, quality=None):
"""Send a SMS using the Choosit provider"""
# unfortunately it lacks a batch API...
for dest in destinations:
# choosit needs the number prefixed by the country code, this is
# really unfortunate.
number = ''.join(re.findall(r'\d', dest))
if dest.startswith('+'):
pass # it already is fully qualified
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 = self.default_country_code + number[1:]
params = urllib.urlencode({
'key': self.key,
'recipient': number,
'content': text[:160],
})
try:
r = misc.urlopen('http://sms.choosit.com/webservice', params)
except misc.ConnectionError as e:
# XXX: add proper handling of errors
raise errors.SMSError("failed to POST to choosit.com (%s)" % e)
r.close()
def get_sms_left(self, type='standard'):
raise NotImplementedError
def get_money_left(self):
raise NotImplementedError
class PasserelleSMS(object):
"""This class allows to send a SMS using Passerelle
"""
parameters = [
(StringWidget, 'sender', N_('Sender (number or name)')),
(StringWidget, 'passerelle_url', N_('URL')),
]
TIMEOUT = 10
def __init__(self):
@ -214,7 +33,6 @@ class PasserelleSMS(object):
self.url = sms_cfg.get('passerelle_url', '')
def send(self, sender, destinations, text, quality=None):
"""Send a SMS using the Choosit provider"""
sender = sender or self.sender
payload = {
'from': sender,
@ -227,27 +45,12 @@ class PasserelleSMS(object):
get_logger().debug('sms %r sent using passerelle to %r, result: %r',
text, destinations, data)
def get_sms_left(self, type='standard'):
raise NotImplementedError
def get_money_left(self):
raise NotImplementedError
class SMS(object):
providers = {
'mobyt': (N_('Mobyt provider'), MobytSMS),
'oxyd': (N_('Oxyd provider'), OxydSMS),
'choosit': (N_('Choosit provider'), ChoositSMS),
'passerelle': (N_('Passerelle provider'), PasserelleSMS),
}
@classmethod
def get_sms_class(cls, provider_id):
if not provider_id:
sms_cfg = get_cfg('sms', {})
provider_id = sms_cfg.get('mode', '')
if provider_id in cls.providers:
return cls.providers.get(provider_id)[1]()
else:
return None
def get_sms_class(cls):
sms_cfg = get_cfg('sms', {})
if sms_cfg.get('sender') and sms_cfg.get('passerelle_url'):
return PasserelleSMS()
return None

View File

@ -2826,8 +2826,8 @@ class SendSMSWorkflowStatusItem(WorkflowStatusItem):
@classmethod
def is_available(cls, workflow=None):
sms_mode = get_cfg('sms', {}).get('mode') or 'none'
return sms_mode != 'none'
sms_cfg = get_cfg('sms', {})
return bool(sms_cfg.get('sender') and sms_cfg.get('passerelle_url'))
def get_parameters(self):
return ('to', 'body', 'condition')
@ -2868,9 +2868,8 @@ class SendSMSWorkflowStatusItem(WorkflowStatusItem):
sms_cfg = get_cfg('sms', {})
sender = sms_cfg.get('sender', 'AuQuotidien')[:11]
mode = sms_cfg.get('mode', 'none')
try:
sms.SMS.get_sms_class(mode).send(sender, destinations, sms_body)
sms.SMS.get_sms_class().send(sender, destinations, sms_body)
except errors.SMSError as e:
get_logger().error(e)