0003-sms-upgrade-orange-connector-to-use-zeep-library-369.patch
passerelle/apps/orange/models.py | ||
---|---|---|
1 |
from StringIO import StringIO
|
|
1 |
from requests.exceptions import ConnectionError
|
|
2 | 2 | |
3 |
from django.core.files import File |
|
4 | 3 |
from django.utils.translation import ugettext_lazy as _ |
5 | 4 |
from django.db import models |
6 | 5 | |
7 | 6 |
from passerelle.base.models import BaseResource |
8 | 7 |
from passerelle.sms import SMSGatewayMixin |
9 | ||
10 |
from . import soap
|
|
8 |
from passerelle.utils.jsonresponse import APIError |
|
9 |
from passerelle.xml_builder import XmlBuilder
|
|
11 | 10 | |
12 | 11 | |
13 | 12 |
class OrangeSMSGateway(BaseResource, SMSGatewayMixin): |
... | ... | |
17 | 16 |
default_country_code = '33' |
18 | 17 | |
19 | 18 |
URL = ('https://www.api-contact-everyone.fr.orange-business.com/ContactEveryone/services' |
20 |
'/MultiDiffusionWS') |
|
19 |
'/MultiDiffusionWS?wsdl')
|
|
21 | 20 | |
22 | 21 |
manager_view_template_name = 'passerelle/manage/messages_service_view.html' |
23 | 22 | |
... | ... | |
29 | 28 |
"""Send a SMS using the Orange provider""" |
30 | 29 |
# unfortunately it lacks a batch API... |
31 | 30 |
destinations = self.clean_numbers(destinations, self.default_country_code) |
32 |
return soap.ContactEveryoneSoap(instance=self).send_advanced_message(destinations, sender, |
|
33 |
text) |
|
31 |
return self.send_advanced_message(destinations, sender, text) |
|
32 | ||
33 |
class ProfileListBuilder(XmlBuilder): |
|
34 |
schema = ( |
|
35 |
'PROFILE_LIST', |
|
36 |
('?loop', 'recipients', |
|
37 |
('PROFILE', |
|
38 |
('DEST_NAME', 'name_{to}'), |
|
39 |
('DEST_FORENAME', 'forename_{to}'), |
|
40 |
('DEST_ID', 'ID_{to}'), |
|
41 |
('TERMINAL_GROUP', |
|
42 |
('TERMINAL', |
|
43 |
('TERMINAL_NAME', 'mobile1'), |
|
44 |
('TERMINAL_ADDR', '{to}'), |
|
45 |
('MEDIA_TYPE_GROUP', |
|
46 |
('MEDIA_TYPE', 'sms'))))))) |
|
47 |
encoding = 'latin1' |
|
48 | ||
49 |
def get_client(self): |
|
50 |
try: |
|
51 |
soap_client = self.soap_client(wsdl_url=self.URL) |
|
52 |
except ConnectionError as e: |
|
53 |
raise APIError('Orange error: WSDL retrieval failed, %s' % e) |
|
54 |
return soap_client |
|
55 | ||
56 |
def send_message(self, recipients, content): |
|
57 |
client = self.get_client() |
|
58 |
message_type = client.get_type('ns0:WSMessage') |
|
59 |
message = message_type() |
|
60 |
message.fullContenu = True |
|
61 |
message.content = content |
|
62 |
message.subject = content |
|
63 |
message.resumeContent = content |
|
64 |
message.strategy = 'sms' |
|
65 |
send_profiles = self.ProfileListBuilder().string( |
|
66 |
context={'recipients': [{'to': to} for to in recipients]}) |
|
67 |
message.sendProfiles = send_profiles |
|
68 |
try: |
|
69 |
resp = client.service.sendMessage(message) |
|
70 |
except Exception as e: |
|
71 |
raise APIError('Orange error: %s' % e) |
|
72 |
else: |
|
73 |
return {'msg_id': resp} |
|
74 | ||
75 |
def send_advanced_message(self, recipients, sender, content): |
|
76 |
client = self.get_client() |
|
77 |
message_type = client.get_type('ns0:WSAdvancedMessage') |
|
78 |
message = message_type() |
|
79 |
message.fullContenu = True |
|
80 |
message.content = content |
|
81 |
message.subject = content |
|
82 |
message.resumeContent = content |
|
83 |
message.strategy = 'sms' |
|
84 |
message.smsReplyTo = sender |
|
85 |
send_profiles = self.ProfileListBuilder().string( |
|
86 |
context={'recipients': [{'to': to} for to in recipients]}) |
|
87 |
message.sendProfiles = send_profiles |
|
88 |
# required booleans |
|
89 |
message.validFaxReplyTo = False |
|
90 |
message.validFormatMailReplyTo = False |
|
91 |
message.validMaxStartCall = False |
|
92 |
message.validMinStartCall = False |
|
93 |
message.validSmsReplyTo = False |
|
94 |
message.validTelReplyTo = False |
|
95 |
try: |
|
96 |
resp = client.service.sendAdvancedMessage(message) |
|
97 |
except Exception as e: |
|
98 |
raise APIError('Orange error: %s' % e) |
|
99 |
else: |
|
100 |
return {'msg_id': resp} |
passerelle/apps/orange/soap.py | ||
---|---|---|
1 |
import datetime |
|
2 |
import os.path |
|
3 | ||
4 | ||
5 |
from passerelle.utils.jsonresponse import APIError |
|
6 |
from passerelle.soap import Soap |
|
7 |
from passerelle.xml_builder import XmlBuilder |
|
8 | ||
9 | ||
10 |
class ContactEveryoneSoap(Soap): |
|
11 |
WSDL_URL = ('https://www.api-contact-everyone.fr.orange-business.com/' |
|
12 |
'ContactEveryone/services/MultiDiffusionWS?wsdl') |
|
13 |
ORANGE_CERTIFICATE = os.path.join(os.path.dirname(__file__), 'orange.pem') |
|
14 | ||
15 |
url = WSDL_URL |
|
16 | ||
17 |
class ProfileListBuilder(XmlBuilder): |
|
18 |
schema = ( |
|
19 |
'PROFILE_LIST', |
|
20 |
('?loop', 'recipients', |
|
21 |
('PROFILE', |
|
22 |
('DEST_NAME', 'name_{to}'), |
|
23 |
('DEST_FORENAME', 'forename_{to}'), |
|
24 |
('DEST_ID', 'ID_{to}'), |
|
25 |
('TERMINAL_GROUP', |
|
26 |
('TERMINAL', |
|
27 |
('TERMINAL_NAME', 'mobile1'), |
|
28 |
('TERMINAL_ADDR', '{to}'), |
|
29 |
('MEDIA_TYPE_GROUP', |
|
30 |
('MEDIA_TYPE', 'sms'))))))) |
|
31 |
encoding = 'latin1' |
|
32 | ||
33 |
@property |
|
34 |
def verify(self): |
|
35 |
# Do not break if certificate is not updated |
|
36 |
if datetime.datetime.now() < datetime.datetime(2016, 11, 5): |
|
37 |
return self.ORANGE_CERTIFICATE |
|
38 |
else: |
|
39 |
return False |
|
40 | ||
41 |
def send_message(self, recipients, content): |
|
42 |
try: |
|
43 |
client = self.get_client() |
|
44 |
except Exception as e: |
|
45 |
raise APIError('Orange error: WSDL retrieval failed, %s' % e) |
|
46 |
message = client.factory.create('WSMessage') |
|
47 |
message.fullContenu = True |
|
48 |
message.content = content |
|
49 |
message.subject = content |
|
50 |
message.resumeContent = content |
|
51 |
message.strategy = 'sms' |
|
52 |
send_profiles = self.ProfileListBuilder().string( |
|
53 |
context={'recipients': [{'to': to} for to in recipients]}) |
|
54 |
message.sendProfiles = send_profiles |
|
55 |
try: |
|
56 |
resp = client.service.sendMessage(message) |
|
57 |
except Exception as e: |
|
58 |
raise APIError('Orange error: %s' % e) |
|
59 |
else: |
|
60 |
return {'msg_ids': [msg_id for msg_id in resp.msgId]} |
|
61 | ||
62 |
def send_advanced_message(self, recipients, sender, content): |
|
63 |
try: |
|
64 |
client = self.get_client() |
|
65 |
except Exception as e: |
|
66 |
raise APIError('Orange error: WSDL retrieval failed, %s' % e) |
|
67 |
message = client.factory.create('WSAdvancedMessage') |
|
68 |
message.fullContenu = True |
|
69 |
message.content = content |
|
70 |
message.subject = content |
|
71 |
message.resumeContent = content |
|
72 |
message.strategy = 'sms' |
|
73 |
message.smsReplyTo = sender |
|
74 |
send_profiles = self.ProfileListBuilder().string( |
|
75 |
context={'recipients': [{'to': to} for to in recipients]}) |
|
76 |
message.sendProfiles = send_profiles |
|
77 |
try: |
|
78 |
resp = client.service.sendAdvancedMessage(message) |
|
79 |
except Exception as e: |
|
80 |
raise APIError('Orange error: %s' % e) |
|
81 |
else: |
|
82 |
if isinstance(resp.msgId, list): |
|
83 |
return {'msg_ids': [msg_id for msg_id in resp.msgId]} |
|
84 |
return {'msg_ids': [resp.msgId]} |
tests/data/orange_sendAdvancedMessage_query.xml | ||
---|---|---|
1 |
<SOAP-ENV:Envelope xmlns:ns0="MultiDiffusionWS" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"> |
|
2 |
<SOAP-ENV:Header/> |
|
3 |
<ns1:Body> |
|
4 |
<ns0:sendAdvancedMessage> |
|
1 |
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"> |
|
2 |
<soap-env:Body> |
|
3 |
<ns0:sendAdvancedMessage xmlns:ns0="MultiDiffusionWS"> |
|
5 | 4 |
<ns0:wsAdvancedMessage> |
6 | 5 |
<ns0:content>hello</ns0:content> |
7 |
<ns0:custId xsi:nil="true"/> |
|
8 |
<ns0:faxReplyTo xsi:nil="true"/> |
|
9 |
<ns0:from xsi:nil="true"/> |
|
6 |
<ns0:custId xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
|
|
7 |
<ns0:faxReplyTo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
|
|
8 |
<ns0:from xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
|
|
10 | 9 |
<ns0:fullContenu>true</ns0:fullContenu> |
11 |
<ns0:mailReplyTo xsi:nil="true"/> |
|
12 |
<ns0:orgName xsi:nil="true"/> |
|
10 |
<ns0:mailReplyTo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
|
|
11 |
<ns0:orgName xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
|
|
13 | 12 |
<ns0:resumeContent>hello</ns0:resumeContent> |
14 | 13 |
<ns0:sendProfiles><PROFILE_LIST><PROFILE><DEST_NAME>name_0033688888888</DEST_NAME><DEST_FORENAME>forename_0033688888888</DEST_FORENAME><DEST_ID>ID_0033688888888</DEST_ID><TERMINAL_GROUP><TERMINAL><TERMINAL_NAME>mobile1</TERMINAL_NAME><TERMINAL_ADDR>0033688888888</TERMINAL_ADDR><MEDIA_TYPE_GROUP><MEDIA_TYPE>sms</MEDIA_TYPE></MEDIA_TYPE_GROUP></TERMINAL></TERMINAL_GROUP></PROFILE><PROFILE><DEST_NAME>name_0033677777777</DEST_NAME><DEST_FORENAME>forename_0033677777777</DEST_FORENAME><DEST_ID>ID_0033677777777</DEST_ID><TERMINAL_GROUP><TERMINAL><TERMINAL_NAME>mobile1</TERMINAL_NAME><TERMINAL_ADDR>0033677777777</TERMINAL_ADDR><MEDIA_TYPE_GROUP><MEDIA_TYPE>sms</MEDIA_TYPE></MEDIA_TYPE_GROUP></TERMINAL></TERMINAL_GROUP></PROFILE></PROFILE_LIST></ns0:sendProfiles> |
15 | 14 |
<ns0:smsReplyTo>+33699999999</ns0:smsReplyTo> |
16 |
<ns0:startCall xsi:nil="true"/> |
|
15 |
<ns0:startCall xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
|
|
17 | 16 |
<ns0:strategy>sms</ns0:strategy> |
18 | 17 |
<ns0:subject>hello</ns0:subject> |
19 |
<ns0:telReplyTo xsi:nil="true"/> |
|
20 |
<ns0:to xsi:nil="true"/> |
|
21 |
<ns0:validFaxReplyTo/>
|
|
22 |
<ns0:validFormatMailReplyTo/>
|
|
23 |
<ns0:validMaxStartCall/>
|
|
24 |
<ns0:validMinStartCall/>
|
|
25 |
<ns0:validSmsReplyTo/>
|
|
26 |
<ns0:validTelReplyTo/>
|
|
27 |
<ns0:validityPeriod xsi:nil="true"/> |
|
18 |
<ns0:telReplyTo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
|
|
19 |
<ns0:to xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
|
|
20 |
<ns0:validFaxReplyTo>false</ns0:validFaxReplyTo>
|
|
21 |
<ns0:validFormatMailReplyTo>false</ns0:validFormatMailReplyTo>
|
|
22 |
<ns0:validMaxStartCall>false</ns0:validMaxStartCall>
|
|
23 |
<ns0:validMinStartCall>false</ns0:validMinStartCall>
|
|
24 |
<ns0:validSmsReplyTo>false</ns0:validSmsReplyTo>
|
|
25 |
<ns0:validTelReplyTo>false</ns0:validTelReplyTo>
|
|
26 |
<ns0:validityPeriod xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
|
|
28 | 27 |
</ns0:wsAdvancedMessage> |
29 | 28 |
</ns0:sendAdvancedMessage> |
30 |
</ns1:Body> |
|
31 |
</SOAP-ENV:Envelope> |
|
29 |
</soap-env:Body> |
|
30 |
</soap-env:Envelope> |
tests/test_sms.py | ||
---|---|---|
9 | 9 |
from django.core.urlresolvers import reverse |
10 | 10 | |
11 | 11 |
from passerelle.apps.orange.models import OrangeSMSGateway |
12 |
from passerelle.apps.orange.soap import ContactEveryoneSoap |
|
13 | 12 |
from passerelle.base.models import ApiUser, AccessRight |
14 | 13 |
from passerelle.sms import SMSGatewayMixin |
15 | 14 | |
... | ... | |
97 | 96 |
assert 'accessright/add' in resp.body |
98 | 97 | |
99 | 98 | |
100 |
@mock.patch('passerelle.soap.requests.get')
|
|
101 |
@mock.patch('passerelle.soap.requests.post')
|
|
99 |
@mock.patch('passerelle.utils.Request.get')
|
|
100 |
@mock.patch('passerelle.utils.Request.post')
|
|
102 | 101 |
def test_orange_connector(mocked_post, mocked_get, app, orange_conn): |
103 | 102 | |
104 | 103 |
def orange_mocked_get(url, params=None, **kwargs): |
105 |
assert url == ContactEveryoneSoap.WSDL_URL
|
|
104 |
assert url == OrangeSMSGateway.URL
|
|
106 | 105 |
target_file = os.path.join(os.path.dirname(__file__), 'data', 'orange.wsdl') |
107 | 106 |
response = Response() |
108 | 107 |
response._content=file(target_file).read() |
... | ... | |
114 | 113 |
os.path.dirname(__file__), 'data', 'orange_sendAdvancedMessage_query.xml') |
115 | 114 |
xml1 = etree.parse(xml_sent_expeted) |
116 | 115 |
xml2 = etree.fromstring(kwargs['data']) |
117 |
if (url not in ContactEveryoneSoap.WSDL_URL
|
|
116 |
if (url not in OrangeSMSGateway.URL
|
|
118 | 117 |
or etree.tostring(xml1, pretty_print=True) |
119 | 118 |
!= etree.tostring(xml2, pretty_print=True)): |
120 | 119 |
# expected value may be updated here |
... | ... | |
153 | 152 |
mocked_post.side_effect=orange_mocked_post |
154 | 153 |
resp = app.post_json(url, params=payload) |
155 | 154 |
assert resp.json['err'] == 0 |
156 |
assert resp.json['data']['msg_ids'] == ['msg-123'] |
|
155 |
assert resp.json['data']['msg_id'] == 'msg-123' |
|
157 |
- |