Projet

Général

Profil

0001-sms-remove-all-but-passerelle-provider-39088.patch

Frédéric Péters, 21 décembre 2020 08:19

Télécharger (19,8 ko)

Voir les différences:

Subject: [PATCH] sms: remove all but passerelle provider (#39088)

 tests/admin_pages/test_workflow.py |   4 +-
 tests/backoffice_pages/test_all.py |   4 +-
 tests/test_workflows.py            |   2 +-
 tests/utilities.py                 |  21 ++-
 wcs/admin/settings.py              |  95 +------------
 wcs/backoffice/management.py       |   5 +-
 wcs/qommon/sms.py                  | 207 +----------------------------
 wcs/workflows.py                   |   7 +-
 8 files changed, 30 insertions(+), 315 deletions(-)
tests/admin_pages/test_workflow.py
486 486
    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
487 487
        pub.site_options.write(fd)
488 488

  
489
    pub.cfg['sms'] = {'mode': 'foobar'}
489
    pub.cfg['sms'] = {'passerelle_url': 'xx', 'sender': 'xx'}
490 490
    pub.write_cfg()
491 491
    workflow.criticality_levels = [WorkflowCriticalityLevel(name='green')]
492 492
    workflow.store()
......
814 814
    workflow.add_status(name='baz')
815 815
    workflow.store()
816 816

  
817
    pub.cfg['sms'] = {'mode': 'foobar'}
817
    pub.cfg['sms'] = {'passerelle_url': 'xx', 'sender': 'xx'}
818 818
    pub.write_cfg()
819 819

  
820 820
    app = login(get_app(pub))
tests/backoffice_pages/test_all.py
3419 3419
    user_view_resp = app.get('/backoffice/management/users/%s/' % user.id)
3420 3420
    assert 'Send tracking code' in user_view_resp.text
3421 3421

  
3422
    pub.cfg['sms'] = {'mode': 'none'}
3422
    pub.cfg['sms'] = {}
3423 3423
    pub.write_cfg()
3424 3424
    resp = user_view_resp.click('Send tracking code')
3425 3425
    assert not 'sms' in resp.form.fields
......
3431 3431
    assert emails.get('Tracking Code reminder')['email_rcpt'] == [user.email]
3432 3432
    assert form_class.get(number31.id).tracking_code in emails.get('Tracking Code reminder')['payload']
3433 3433

  
3434
    pub.cfg['sms'] = {'mode': 'xx'}
3434
    pub.cfg['sms'] = {'passerelle_url': 'xx', 'sender': 'xx'}
3435 3435
    pub.write_cfg()
3436 3436
    resp = user_view_resp.click('Send tracking code', index=0)
3437 3437
    resp.form['method'].value = 'SMS'
tests/test_workflows.py
2341 2341

  
2342 2342

  
2343 2343
def test_sms(pub, sms_mocking):
2344
    pub.cfg['sms'] = {'mode': 'xxx'}
2344
    pub.cfg['sms'] = {'sender': 'xxx', 'passerelle_url': 'http://passerelle.invalid/'}
2345 2345
    formdef = FormDef()
2346 2346
    formdef.name = 'baz'
2347 2347
    formdef.fields = []
tests/utilities.py
398 398
        return len(self.requests)
399 399

  
400 400

  
401
class SMSMocking(wcs.qommon.sms.MobytSMS):
402
    def get_sms_class(self, mode):
403
        if mode == 'none':
404
            return None
405
        return self
406

  
407
    def send(self, sender, destinations, text, quality=None):
401
class SMSMocking(wcs.qommon.sms.PasserelleSMS):
402
    def get_sms_class(self):
403
        sms_cfg = get_publisher().cfg.get('sms', {})
404
        if sms_cfg.get('sender') and sms_cfg.get('passerelle_url'):
405
            return self
406
        return None
407

  
408
    def send(self, sender, destinations, text):
408 409
        self.sms.append({'sender': sender, 'destinations': destinations, 'text': text})
409 410

  
410
    def get_sms_left(self, type='standard'):
411
        raise NotImplementedError
412

  
413
    def get_money_left(self):
414
        raise NotImplementedError
415

  
416 411
    def __enter__(self):
417 412
        self.sms = []
418 413
        self.wcs_get_sms_class = wcs.qommon.sms.SMS.get_sms_class
wcs/admin/settings.py
425 425
            'template', 'emails', 'debug_options', 'language',
426 426
            ('import', 'p_import'), 'export', 'identification', 'sitename',
427 427
            'sms', 'certificates', 'texts', 'install_theme',
428
            'session', 'download_theme', 'smstest', 'postgresql',
428
            'session', 'download_theme', 'postgresql',
429 429
            ('admin-permissions', 'admin_permissions'), 'geolocation',
430 430
            'theme_preview', 'filetypes',
431 431
            ('user-template', 'user_template'),
......
1101 1101
        r = TemplateIO(html=True)
1102 1102
        r += htmltext('<h2>%s</h2>') % _('SMS Options')
1103 1103
        sms_cfg = get_cfg('sms', {})
1104
        mode = sms_cfg.get('mode', 'none')
1105
        sms = SMS.get_sms_class(mode)
1106
        if sms:
1107
            r += htmltext('<ul>')
1108
            try:
1109
                try:
1110
                    r += htmltext('<li>%s %s</li>') % (_('SMS Credit:'), sms.get_money_left())
1111
                except NotImplementedError:
1112
                    pass
1113
                try:
1114
                    r += htmltext('<li>%s %s</li>') % (_('SMS Left:'), sms.get_sms_left())
1115
                except NotImplementedError:
1116
                    pass
1117
            except errors.SMSError:
1118
                r += htmltext("<p>%s</li>") % _("Connection with SMS provider failed")
1119
            r += htmltext('</ul>')
1120

  
1121 1104
        form = Form(enctype='multipart/form-data')
1122
        form.add(SingleSelectWidget, 'mode', title = _('SMS Mode'),
1123
                value = mode,
1124
                options = [(str('none'), _('No support'), str('none'))]+
1125
                          [(str(k), _(SMS.providers.get(k)[0]), str(k)) for k in SMS.providers.keys()])
1126

  
1105
        form.add(StringWidget, 'sender', title=_('Sender (number of name)'),
1106
                value=sms_cfg.get('sender'))
1107
        form.add(StringWidget, 'passerelle_url', title=_('URL'),
1108
                value=sms_cfg.get('passerelle_url'))
1127 1109
        form.add_submit('submit', _('Submit'))
1128 1110
        form.add_submit('cancel', _('Cancel'))
1129 1111

  
1130 1112
        if form.get_widget('cancel').parse():
1131 1113
            return redirect('.')
1132 1114

  
1133
        if sms:
1134
            for widget, name, title in sms.parameters:
1135
                form.add(widget, name, title=_(title),
1136
                         value=sms_cfg.get(name, ''),
1137
                         required=True)
1138
            if form.get_submit() and not form.has_errors():
1139
                cfg_submit(form, 'sms', ['mode'] + [x[1] for x in sms.parameters])
1140
                if mode != form.get_widget('mode').parse():
1141
                    return redirect('sms')
1142
                else:
1143
                    return redirect('.')
1144
            elif mode != form.get_widget('mode').parse():
1145
                cfg_submit(form, 'sms', ['mode',])
1146
                return redirect('sms')
1147
        else:
1148
            if form.get_submit() and form.get_widget('mode').parse() == str('none'):
1149
                return redirect('.')
1150

  
1151 1115
        if form.get_submit() and not form.has_errors():
1152
            cfg_submit(form, 'sms', ['mode',])
1153
            return redirect('sms')
1154
        else:
1155
            r += form.render()
1156

  
1157
        if mode != 'none':
1158
            r += htmltext('<p><a href="smstest">%s</a></p>') % _('SMS Test')
1159

  
1160
        return r.getvalue()
1161

  
1162

  
1163
    def smstest(self):
1164
        form = Form(enctype='multipart/form-data', action='smstest')
1165
        form.add(StringWidget, 'sender', title=_('Sender'), required=True)
1166
        form.add(StringWidget, 'destinations', title=_('Destinations'), required=True)
1167
        form.add(StringWidget, 'text', title=_('Text'), required=True)
1168
        form.add_submit('submit', _('Submit'))
1169
        form.add_submit('cancel', _('Cancel'))
1170

  
1171
        if form.get_widget('cancel').parse():
1172
            return redirect('sms')
1116
            cfg_submit(form, 'sms', ['sender', 'passerelle_url'])
1117
            return redirect('.')
1173 1118

  
1174
        get_response().breadcrumb.append(('sms', _('SMS')))
1175
        get_response().breadcrumb.append(('smstest', _('SMS Test')))
1176
        html_top('settings', title = _('SMS Test'))
1177
        r = TemplateIO(html=True)
1178
        r += htmltext('<h2>%s</h2>') % _('SMS Test')
1179 1119
        r += form.render()
1180

  
1181
        if form.get_submit() and not form.has_errors():
1182
            sms_cfg = get_cfg('sms', {})
1183
            mode = sms_cfg.get('mode', 'none')
1184
            sms = SMS.get_sms_class(mode)
1185

  
1186
            sender = str(form.get_widget('sender').parse())
1187
            destinations = str(form.get_widget('destinations').parse()).split(str(','))
1188
            text = str(form.get_widget('text').parse())
1189

  
1190
            try:
1191
                sms.send(sender, destinations, text)
1192
            except Exception as e:
1193
                r += htmltext('<pre>')
1194
                r += repr(e)
1195
                r += htmltext('</pre>')
1196
            else:
1197
                r += htmltext('<p>')
1198
                r += _('The SMS has been sent successfully.')
1199
                r += htmltext('</p>')
1200

  
1201 1120
        return r.getvalue()
1202 1121

  
1203 1122
    def postgresql(self):
wcs/backoffice/management.py
146 146
                value=submitter_email)
147 147
        sms_class = None
148 148
        if get_publisher().use_sms_feature:
149
            sms_cfg = get_cfg('sms', {})
150
            mode = sms_cfg.get('mode', 'none')
151
            sms_class = sms.SMS.get_sms_class(mode)
149
            sms_class = sms.SMS.get_sms_class()
152 150
            if sms_class:
153 151
                form.add(StringWidget, 'sms', title=_('SMS Number'), required=False)
154 152
                form.add(RadiobuttonsWidget, 'method',
......
180 178
        if sms_class and form.get_widget('method').parse() == 'sms':
181 179
            # send sms
182 180
            sitename = get_cfg('misc', {}).get('sitename') or 'w.c.s.'
181
            sms_cfg = get_cfg('sms', {})
183 182
            sender = sms_cfg.get('sender', sitename)[:11]
184 183
            message = Template(message).render(data)
185 184
            try:
wcs/qommon/sms.py
24 24
from wcs.wscalls import call_webservice
25 25

  
26 26

  
27
class MobytSMS(object):
28
    """This class allows to send a SMS using Mobyt provider"""
29
    parameters = [
30
         (StringWidget, 'sender', N_('Sender (number or name)')),
31
         (StringWidget, 'mobyt_username', N_('Username')),
32
         (PasswordWidget, 'mobyt_password', N_('Password')),
33
    ]
34

  
35
    def __init__(self):
36
        sms_cfg = get_cfg('sms', {})
37
        self.user = sms_cfg.get('mobyt_username', '')
38
        self.password = sms_cfg.get('mobyt_password', '')
39

  
40
    def send(self, sender, destinations, text, quality='l'):
41
        """ Send a sms using Mobyt provider"""
42
        rcpt = ""
43
        for dest in destinations:
44
            rcpt += "%s," % dest
45
        rcpt = rcpt[:len(rcpt) - 1]
46
        params = urllib.urlencode({
47
            'user': self.user,
48
            'pass': self.password,
49
            'rcpt': rcpt,
50
            'data': text,
51
            'sender': sender,
52
            'qty': quality
53
            })
54
        try:
55
            r = misc.urlopen("http://multilevel.mobyt.fr/sms/batch.php", params)
56
        except misc.ConnectionError as e:
57
            raise errors.SMSError("failed to POST to mobyt.fr (%s)" % e)
58
        answer = r.read()
59
        r.close()
60
        if answer[:2] == "KO":
61
            raise errors.SMSError(answer[3:])
62

  
63
    def get_credit(self, type):
64
        params = urllib.urlencode({
65
            'user': self.user,
66
            'pass': self.password,
67
            'type': type
68
            })
69
        try:
70
            r = misc.urlopen("http://multilevel.mobyt.fr/sms/credit.php", params)
71
        except misc.ConnectionError as e:
72
            raise errors.SMSError("failed to POST to mobyt.fr (%s)" % e)
73
        answer = r.read()
74
        r.close()
75
        if answer[:2] == "KO":
76
            raise errors.SMSError(answer[3:])
77
        else:
78
            return answer[3:]
79

  
80
    def get_sms_left(self, type="standard"):
81
        """
82
        type (mobyt provider): standard, lowcost or top
83
        """
84
        if type == "standard":
85
            return self.get_credit("l")
86
        elif type == "lowcost":
87
            return self.get_credit("ll")
88
        elif type == "top":
89
            return self.get_credit("n")
90
        else:
91
            raise errors.SMSError("%s is invalid type for provider Mobyt" % type)
92

  
93
    def get_money_left(self):
94
        """ return money left in euros """
95
        return self.get_credit("credit")
96

  
97

  
98
class OxydSMS(object):
99
    """This class allows to send a SMS using Oxyd provider"""
100
    parameters = [
101
         (StringWidget, 'oxyd_username', N_('Username')),
102
         (PasswordWidget, 'oxyd_password', N_('Password')),
103
         (StringWidget, 'oxyd_default_country_code', N_('Default Country Code')),
104
    ]
105

  
106
    def __init__(self):
107
        sms_cfg = get_cfg('sms', {})
108
        self.user = sms_cfg.get('oxyd_username', '')
109
        self.password = sms_cfg.get('oxyd_password', '')
110
        self.default_country_code = sms_cfg.get('oxyd_default_country_code')
111
        if not self.default_country_code:
112
            self.default_country_code = '33' # arbitrary
113

  
114
    def send(self, sender, destinations, text, quality=None):
115
        """Send a SMS using Oxyd provider"""
116
        # unfortunately it lacks a batch API...
117
        for dest in destinations:
118
            # oxyd needs the number prefixed by the country code, this is
119
            # really unfortunate.
120
            number = ''.join(re.findall(r'\d', dest))
121
            if dest.startswith('+'):
122
                pass # it already is fully qualified
123
            elif number.startswith('00'):
124
                # assumes 00 is international access code, remove it
125
                number = number[2:]
126
            elif number.startswith('0'):
127
                # local prefix, remove 0 and add default country code
128
                number = self.default_country_code + number[1:]
129
            params = urllib.urlencode({
130
                'id': self.user,
131
                'pass': self.password,
132
                'num': number,
133
                'sms': text,
134
                'flash': '0'
135
                })
136
            try:
137
                r = misc.urlopen('http://sms.oxyd.fr/send.php', params)
138
            except misc.ConnectionError as e:
139
                # XXX: add proper handling of errors
140
                raise errors.SMSError("failed to POST to oxyd.fr (%s)" % e)
141
            r.close()
142

  
143
    def get_sms_left(self, type='standard'):
144
        raise NotImplementedError
145

  
146
    def get_money_left(self):
147
        raise NotImplementedError
148

  
149

  
150
class ChoositSMS(object):
151
    """This class allows to send a SMS using the Choosit provider
152

  
153
    http://sms.choosit.com/documentation_technique.html
154
    """
155
    parameters = [
156
         (StringWidget, 'choosit_key', N_('Key')),
157
         (StringWidget, 'choosit_default_country_code', N_('Default Country Code')),
158
    ]
159

  
160
    def __init__(self):
161
        sms_cfg = get_cfg('sms', {})
162
        self.key = sms_cfg.get('choosit_key', '')
163
        self.default_country_code = sms_cfg.get('choosit_default_country_code')
164
        if not self.default_country_code:
165
            self.default_country_code = '33' # arbitrary
166

  
167
    def send(self, sender, destinations, text, quality=None):
168
        """Send a SMS using the Choosit provider"""
169
        # unfortunately it lacks a batch API...
170
        for dest in destinations:
171
            # choosit needs the number prefixed by the country code, this is
172
            # really unfortunate.
173
            number = ''.join(re.findall(r'\d', dest))
174
            if dest.startswith('+'):
175
                pass # it already is fully qualified
176
            elif number.startswith('00'):
177
                # assumes 00 is international access code, remove it
178
                number = number[2:]
179
            elif number.startswith('0'):
180
                # local prefix, remove 0 and add default country code
181
                number = self.default_country_code + number[1:]
182
            params = urllib.urlencode({
183
                'key': self.key,
184
                'recipient': number,
185
                'content': text[:160],
186
                })
187
            try:
188
                r = misc.urlopen('http://sms.choosit.com/webservice', params)
189
            except misc.ConnectionError as e:
190
                # XXX: add proper handling of errors
191
                raise errors.SMSError("failed to POST to choosit.com (%s)" % e)
192
            r.close()
193

  
194
    def get_sms_left(self, type='standard'):
195
        raise NotImplementedError
196

  
197
    def get_money_left(self):
198
        raise NotImplementedError
199

  
200

  
201 27
class PasserelleSMS(object):
202
    """This class allows to send a SMS using Passerelle
203
    """
204
    parameters = [
205
         (StringWidget, 'sender', N_('Sender (number or name)')),
206
         (StringWidget, 'passerelle_url', N_('URL')),
207
    ]
208

  
209 28
    TIMEOUT = 10
210 29

  
211 30
    def __init__(self):
......
214 33
        self.url = sms_cfg.get('passerelle_url', '')
215 34

  
216 35
    def send(self, sender, destinations, text, quality=None):
217
        """Send a SMS using the Choosit provider"""
218 36
        sender = sender or self.sender
219 37
        payload = {
220 38
              'from': sender,
......
227 45
        get_logger().debug('sms %r sent using passerelle to %r, result: %r',
228 46
                text, destinations, data)
229 47

  
230
    def get_sms_left(self, type='standard'):
231
        raise NotImplementedError
232

  
233
    def get_money_left(self):
234
        raise NotImplementedError
235

  
236 48

  
237 49
class SMS(object):
238
    providers = {
239
        'mobyt': (N_('Mobyt provider'), MobytSMS),
240
        'oxyd': (N_('Oxyd provider'), OxydSMS),
241
        'choosit': (N_('Choosit provider'), ChoositSMS),
242
        'passerelle': (N_('Passerelle provider'), PasserelleSMS),
243
    }
244 50

  
245 51
    @classmethod
246
    def get_sms_class(cls, provider_id):
247
        if not provider_id:
248
            sms_cfg = get_cfg('sms', {})
249
            provider_id = sms_cfg.get('mode', '')
250
        if provider_id in cls.providers:
251
            return cls.providers.get(provider_id)[1]()
252
        else:
253
            return None
52
    def get_sms_class(cls):
53
        sms_cfg = get_cfg('sms', {})
54
        if sms_cfg.get('sender') and sms_cfg.get('passerelle_url'):
55
            return PasserelleSMS()
56
        return None
wcs/workflows.py
2826 2826

  
2827 2827
    @classmethod
2828 2828
    def is_available(cls, workflow=None):
2829
        sms_mode = get_cfg('sms', {}).get('mode') or 'none'
2830
        return sms_mode != 'none'
2829
        sms_cfg = get_cfg('sms', {})
2830
        return bool(sms_cfg.get('sender') and sms_cfg.get('passerelle_url'))
2831 2831

  
2832 2832
    def get_parameters(self):
2833 2833
        return ('to', 'body', 'condition')
......
2868 2868

  
2869 2869
        sms_cfg = get_cfg('sms', {})
2870 2870
        sender = sms_cfg.get('sender', 'AuQuotidien')[:11]
2871
        mode = sms_cfg.get('mode', 'none')
2872 2871
        try:
2873
            sms.SMS.get_sms_class(mode).send(sender, destinations, sms_body)
2872
            sms.SMS.get_sms_class().send(sender, destinations, sms_body)
2874 2873
        except errors.SMSError as e:
2875 2874
            get_logger().error(e)
2876 2875

  
2877
-