Commit 63ac2d77 authored by Jérome Perrin's avatar Jérome Perrin

ERP5ShortMessage: simplify gateway interface

Implementation dependant parameters, such as message type or quality,
must be defined by the gateway class, not in the common
ShortMessage_send script.

This should make it easier to switch implementations.

Also:
 * send method is expected to be called once per recipient. I guess we
   prefer to isolate messages than to batch send messages efficiently.
 * send method expect relative url of sender and reciever documents, so
   that it can decide which properties to use.
 * drop `sender_title` parameter, now the gateway implementation decide
   what to use.
 * drop `test` parameter. For simulation mode, set simulation mode on
   the gateway instance
 * sms.send() no longer accept direct from_url / to_url / body message.
   It only supports sending what's defined on document properties.
 * SMSTool_afterSend will be called with message_id parameter, not
   message_id_list
 * Split interfaces in two SMS Sending / SMS Receiving.
parent 5a53b421
"""Save the message id of the relative document""" """Save the message id of the relative document"""
if document_relative_url: if document_relative_url:
document = context.getPortalObject().restrictedTraverse(document_relative_url) document = context.getPortalObject().restrictedTraverse(document_relative_url)
document.edit(destination_reference=message_id_list[0], document.edit(destination_reference=message_id,
gateway = gateway_relative_url) gateway=gateway_relative_url)
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>message_id_list, document_relative_url=None, gateway_relative_url=None, **kw</string> </value> <value> <string>message_id, document_relative_url=None, gateway_relative_url=None, **kw</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
...@@ -4,42 +4,29 @@ ...@@ -4,42 +4,29 @@
""" """
#Get recipients #Get recipients
if not to_url: recipient_phone_list = [
recipient_phone_list = [person.getDefaultMobileTelephoneValue() for person in context.getDestinationValueList()] person.getDefaultMobileTelephoneValue() for person in context.getDestinationValueList()]
if None in recipient_phone_list: if None in recipient_phone_list:
raise ValueError("All recipients should have a default mobile phone") raise ValueError("All recipients should have a default mobile phone")
to_url = [phone.asURL() for phone in recipient_phone_list] to_url = [phone.asURL() for phone in recipient_phone_list]
if None in to_url: if None in to_url:
raise ValueError("All recipients should have a valid default mobile phone number") raise ValueError("All recipients should have a valid default mobile phone number")
#Get sender body = context.getTextContent()
if not from_url:
if context.getSourceValue():
sender_phone = context.getSourceValue().getDefaultMobileTelephoneValue()
if not sender_phone:
raise ValueError("The sender(%s) should have a default mobile phone" % context.getSourceValue())
#We use title of sender
from_title = sender_phone.getTitle()
from_url = sender_phone.asURL()
if not body:
body = context.getTextContent()
if not context.getStartDate(): if not context.getStartDate():
context.setStartDate(DateTime()) context.setStartDate(DateTime())
context.portal_sms.activate( for recipient in context.getDestinationList():
activity="SQLQueue", context.portal_sms.activate(
# We do not retry these activities not to send SMS multiple times activity="SQLQueue",
max_retry=0, # We do not retry these activities not to send SMS multiple times
conflict_retry=False, max_retry=0,
).send( conflict_retry=False,
text=body, ).send(
recipient=to_url, text=body,
sender=from_url, sender=context.getSource(),
sender_title=from_title, recipient=recipient,
message_type="MULTITEXT", document_relative_url=context.getRelativeUrl(),
test=download, )
document_relative_url=context.getRelativeUrl(),
**kw)
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>from_url=None, from_title=None, to_url=None, reply_url=None, subject=None, body=None, attachment_format=None, attachment_list=None,download=False,**kw</string> </value> <value> <string>from_url=None, from_title=None, to_url=None, reply_url=None, subject=None, body=None, attachment_format=None, attachment_list=None, download=False, **kw</string> </value>
</item> </item>
<item> <item>
<key> <string>_proxy_roles</string> </key> <key> <string>_proxy_roles</string> </key>
......
...@@ -55,51 +55,29 @@ class DummyGateway(XMLObject): ...@@ -55,51 +55,29 @@ class DummyGateway(XMLObject):
add_permission = Permissions.AddPortalContent add_permission = Permissions.AddPortalContent
zope.interface.implements(interfaces.ISmsGateway) zope.interface.implements(
interfaces.ISmsSendingGateway,
interfaces.ISmsReceivingGateway)
# Declarative security # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation) security.declareObjectProtected(Permissions.AccessContentsInformation)
# Declarative properi ties # Declarative properties
property_sheets = ( PropertySheet.Base property_sheets = ( PropertySheet.Base
, PropertySheet.XMLObject , PropertySheet.XMLObject
, PropertySheet.Reference , PropertySheet.Reference
, PropertySheet.SMSGateway , PropertySheet.SMSGateway
) )
security.declarePublic('getAllowedMessageType')
def getAllowedMessageType(self):
"""List of all message type"""
return ['text',]
security.declareProtected(Permissions.ManagePortal, 'send') security.declareProtected(Permissions.ManagePortal, 'send')
def send(self, text,recipient,sender=None, sender_title=None, def send(self, text, recipient, sender):
message_type="text",test=False,**kw): """Send a short message.
"""Send a message. """
Parameters:
text -- message
recipient -- phone url of destination_reference. Could be a list
sender -- phone url of source
sender_title -- Use it as source if the gateway has title mode enable
message_type -- Only 'text' is available today
test -- Force the test mode
Kw Parameters:
quality -- Quality of the SMS (default,n)
Return message id
"""
#Check messsage type
# XXX does it make sense to check message type in dummy gateway ? -jerome
#if message_type not in self.getAllowedMessageType():
# raise ValueError, "Type of message in not allowed"
#Send message (or test) #Send message (or test)
if test or self.isSimulationMode(): if self.isSimulationMode():
return None return None
else: return self._generateRandomMessageId()
return self._generateRandomMessageId()
security.declareProtected(Permissions.ManagePortal, 'getMessageStatus') security.declareProtected(Permissions.ManagePortal, 'getMessageStatus')
def getMessageStatus(self, message_id): def getMessageStatus(self, message_id):
......
...@@ -61,7 +61,9 @@ class EssendexGateway(XMLObject): ...@@ -61,7 +61,9 @@ class EssendexGateway(XMLObject):
add_permission = Permissions.AddPortalContent add_permission = Permissions.AddPortalContent
zope.interface.implements(interfaces.ISmsGateway) zope.interface.implements(
interfaces.ISmsSendingGateway,
interfaces.ISmsReceivingGateway)
# Declarative security # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
...@@ -75,10 +77,6 @@ class EssendexGateway(XMLObject): ...@@ -75,10 +77,6 @@ class EssendexGateway(XMLObject):
) )
api_url = "https://www.esendex.com/secure/messenger/formpost" api_url = "https://www.esendex.com/secure/messenger/formpost"
security.declarePublic('getAllowedMessageType')
def getAllowedMessageType(self):
"""List of all message type"""
return ['text', 'binary', 'smartMessage', 'unicode']
security.declarePrivate("_fetchPageAsDict") security.declarePrivate("_fetchPageAsDict")
def _fetchPageAsDict(self,page): def _fetchPageAsDict(self,page):
...@@ -132,32 +130,17 @@ class EssendexGateway(XMLObject): ...@@ -132,32 +130,17 @@ class EssendexGateway(XMLObject):
return timedelta.seconds + (timedelta.days * 24 * 60 * 60) return timedelta.seconds + (timedelta.days * 24 * 60 * 60)
security.declareProtected(Permissions.ManagePortal, 'send') security.declareProtected(Permissions.ManagePortal, 'send')
def send(self, text,recipient,sender=None, sender_title=None, def send(self, text, recipient, sender):
message_type="text",test=False,**kw):
"""Send a message. """Send a message.
Parameters: """
text -- message traverse = self.getPortalObject().restrictedTraverse
recipient -- phone url of destination_reference. Could be a list message_type = self.getProperty('essendex_message_type', 'text')
sender -- phone url of source assert message_type in ('text', 'binary', 'smartMessage', 'unicode')
sender_title -- Use it as source if the gateway has title mode enable
message_type -- Only 'text' is available today
test -- Force the test mode
Kw Parameters:
validity_period -- Validity Period of SMS (default,0)
Return message id (or list if multiple recipient)
"""
if message_type not in self.getAllowedMessageType():
raise ValueError, "Type of message in not allowed"
validity_period = kw.get('validity_period',0) validity_period = self.getProperty('essendex_validity_period', 0)
if not isinstance(recipient, str): recipient = self._transformPhoneUrlToGatewayNumber(
recipient = ",".join([self._transformPhoneUrlToGatewayNumber(x) for x in recipient]) traverse(recipient).getDefaultMobileTelephoneValue().asURL())
else:
recipient = self._transformPhoneUrlToGatewayNumber(recipient)
base_url = self.api_url + "/SendSMS.aspx" base_url = self.api_url + "/SendSMS.aspx"
params = {'Username': self.getGatewayUser(), params = {'Username': self.getGatewayUser(),
...@@ -170,16 +153,13 @@ class EssendexGateway(XMLObject): ...@@ -170,16 +153,13 @@ class EssendexGateway(XMLObject):
'PlainText': 1, 'PlainText': 1,
} }
if self.isTitleMode():
params['Originator'] = traverse(sender).getDefaultMobileTelephoneValue().getTitle()
else:
params['Originator'] = self._transformPhoneUrlToGatewayNumber(
traverse(sender).getDefaultMobileTelephoneValue().asURL()) or self.getDefaultSender()
if self.isSimulationMode():
if sender_title and self.isTitleMode():
params['Originator'] = sender_title
elif sender:
params['Originator'] = self._transformPhoneUrlToGatewayNumber(sender)
elif self.getDefaultSender():
params['Originator'] = self.getDefaultSender()
if test or self.isSimulationMode():
params['Test'] = 1 params['Test'] = 1
LOG("EssendexGateway", INFO, params) LOG("EssendexGateway", INFO, params)
......
...@@ -57,7 +57,9 @@ class MobytGateway(XMLObject): ...@@ -57,7 +57,9 @@ class MobytGateway(XMLObject):
add_permission = Permissions.AddPortalContent add_permission = Permissions.AddPortalContent
zope.interface.implements(interfaces.ISmsGateway) zope.interface.implements(
interfaces.ISmsSendingGateway,
interfaces.ISmsReceivingGateway)
# Declarative security # Declarative security
security = ClassSecurityInfo() security = ClassSecurityInfo()
...@@ -74,12 +76,6 @@ class MobytGateway(XMLObject): ...@@ -74,12 +76,6 @@ class MobytGateway(XMLObject):
# for documentation of this old API # for documentation of this old API
api_url = "http://multilevel.mobyt.fr/sms" api_url = "http://multilevel.mobyt.fr/sms"
security.declarePublic('getAllowedMessageType')
def getAllowedMessageType(self):
"""List of all message type"""
# `text` is here for compatibility, but the API always expected uppercase
return ['text', 'TEXT', 'MULTITEXT', 'WAPPUSH', 'UCS2', 'MULTIUCS2']
security.declarePrivate("_fetchSendResponseAsDict") security.declarePrivate("_fetchSendResponseAsDict")
def _fetchSendResponseAsDict(self,page): def _fetchSendResponseAsDict(self,page):
"""Page result is like Key=value in text format. """Page result is like Key=value in text format.
...@@ -141,42 +137,24 @@ class MobytGateway(XMLObject): ...@@ -141,42 +137,24 @@ class MobytGateway(XMLObject):
return phone return phone
security.declareProtected(Permissions.ManagePortal, 'send') security.declareProtected(Permissions.ManagePortal, 'send')
def send(self, text,recipient,sender=None, sender_title=None, def send(self, text, recipient, sender):
message_type="text",test=False,**kw):
"""Send a message. """Send a message.
Parameters: """
text -- message traverse = self.getPortalObject().restrictedTraverse
recipient -- phone url of destination_reference. Could be a list
sender -- phone url of source
sender_title -- Use it as source if the gateway has title mode enable
message_type -- see getAllowedMessageType
test -- Force the test mode
Kw Parameters:
quality -- Quality of the SMS (default,n)
Return message id
"""
#Check messsage type #Check messsage type
if message_type not in self.getAllowedMessageType(): message_type = self.getProperty('mobyt_message_type', 'MULTITEXT')
raise ValueError, "Type of message in not allowed" if message_type not in ('TEXT', 'MULTITEXT', 'WAPPUSH', 'UCS2', 'MULTIUCS2'):
raise ValueError("Type of message in not allowed")
#Check message qualit #Check message quality
quality = kw.get('quality','n') #Allow sender personalization and status of SMS quality = self.getProperty('mobyt_quality', 'n') #Allow sender personalization and status of SMS
assert quality in ['n','l','ll'], "Unknown quality : '%s'" % quality assert quality in ('n','l','ll'), "Unknown quality : '%s'" % quality
#Recipients #Recipient
if not isinstance(recipient, str): recipient = self._transformPhoneUrlToGatewayNumber(
recipient_count = len(recipient) traverse(recipient).getDefaultMobileTelephoneValue().asURL())
recipient = ",".join([self._transformPhoneUrlToGatewayNumber(x) for x in recipient])
else:
recipient = self._transformPhoneUrlToGatewayNumber(recipient)
recipient_count = 1
if recipient_count > 1: base_url = self.api_url + "/send.php"
base_url = self.api_url + "/batch.php" #Multi recipient
else:
base_url = self.api_url + "/send.php"
#Common params #Common params
params = { "user" : self.getGatewayUser(), params = { "user" : self.getGatewayUser(),
...@@ -186,22 +164,19 @@ class MobytGateway(XMLObject): ...@@ -186,22 +164,19 @@ class MobytGateway(XMLObject):
"qty" : quality, "qty" : quality,
"return_id": 1} "return_id": 1}
#Define sender if self.isTitleMode():
if sender_title and self.isTitleMode() and quality == 'n': params['sender'] = traverse(sender).getDefaultMobileTelephoneValue().getTitle()
params['sender'] = sender_title else:
elif sender: params['sender'] = self._transformPhoneUrlToGatewayNumber(
params['sender'] = self._transformPhoneUrlToGatewayNumber(sender) traverse(sender).getDefaultMobileTelephoneValue().asURL()) or self.getDefaultSender()
elif self.getDefaultSender():
params['sender'] = self.getDefaultSender()
#Define type of message #Define type of message
if message_type != "text": if message_type != "text":
assert quality == 'n', "This type of message require top level messsage quality" assert quality == 'n', "This type of message require top level messsage quality"
assert message_type in self.getAllowedMessageType(), "Unknown message type"
params['operation'] = message_type params['operation'] = message_type
#Send message (or test) #Send message (or test)
if test or self.isSimulationMode(): if self.isSimulationMode():
LOG("MobytGateway", INFO, params) LOG("MobytGateway", INFO, params)
result = {'status': "Test"} result = {'status': "Test"}
else: else:
......
...@@ -50,24 +50,22 @@ class SMSTool(BaseTool): ...@@ -50,24 +50,22 @@ class SMSTool(BaseTool):
manage_overview = DTMLFile('explainSMSTool', _dtmldir ) manage_overview = DTMLFile('explainSMSTool', _dtmldir )
security.declareProtected(ManagePortal, 'send') security.declareProtected(ManagePortal, 'send')
def send(self, text,recipient, sender=None, sender_title=None, def send(self, text, recipient, sender, gateway_reference='default',
message_type="text", test=False, gateway_reference='default', document_relative_url=None, activate_kw=None):
document_relative_url=None, activate_kw=None, **kw): """Send the message
"""
gateway_reference: send message throught the gateway with this reference.
document_relative_url (optional) : allows to send back result to a document document_relative_url (optional) : allows to send back result to a document
activate_kw (optional) : Call SMSTool_afterSend if founded in activity with activate_kw (optional) : Call SMSTool_afterSend if founded in activity with
message_id_list and document_relative_url message_id and document_relative_url
""" """
gateway = self.find(gateway_reference) gateway = self.find(gateway_reference)
message_id_list = gateway.send(text=text, message_id = gateway.send(
recipient=recipient, text=text,
sender=sender, recipient=recipient,
sender_title=sender_title, sender=sender)
message_type=message_type,
test=test,
**kw)
if getattr(self, 'SMSTool_afterSend'): if getattr(self, 'SMSTool_afterSend'):
# We need to use activities in order to avoid any conflict # We need to use activities in order to avoid any conflict
...@@ -75,9 +73,9 @@ class SMSTool(BaseTool): ...@@ -75,9 +73,9 @@ class SMSTool(BaseTool):
if activate_kw is not None: if activate_kw is not None:
send_activate_kw.update(**activate_kw) send_activate_kw.update(**activate_kw)
self.activate(**send_activate_kw).SMSTool_afterSend( self.activate(**send_activate_kw).SMSTool_afterSend(
message_id_list, message_id,
document_relative_url=document_relative_url, document_relative_url=document_relative_url,
gateway_relative_url=gateway.getRelativeUrl(),**kw) gateway_relative_url=gateway.getRelativeUrl())
security.declareProtected(ManagePortal, 'getMessageStatus') security.declareProtected(ManagePortal, 'getMessageStatus')
def getMessageStatus(self,message_id, gateway_reference='default'): def getMessageStatus(self,message_id, gateway_reference='default'):
......
...@@ -29,25 +29,33 @@ ...@@ -29,25 +29,33 @@
from zope.interface import Interface from zope.interface import Interface
class ISmsGateway(Interface): class ISmsSendingGateway(Interface):
"""SMS Gateway allow sending Short Messages to phones.
"""
def send(text, recipient, def send(text, recipient, sender):
sender=None, sender_title=None, """Send a message.
message_type="text", test=False, **kw):
"""Send a message. * text: the message as an utf-8 encoded string
* recipient: relative URL of recipient person or organisation. Recipient must have a defaut mobile phone
TODO: write * sender: relative URL of sender person or organisation.
TODO: is getAllowedMessageType part of this API ? On most implementations, returns a message-id that can be later passed to
shouldn't we rely on content_type ? ( text/plain -> SMS, text/html -> MMS ? ) getMessageStatus to check the status of the message.
""" """
def receive(REQUEST): def getMessageStatus(message_id):
"""Public handler to push notification from the gateway""" """Retrieve the status of a message
Should return x in ['sent', 'delivered', 'queued', 'failed']"""
def getAllowedMessageType():
"""List of all allowed message type when sending a message."""
def getMessageStatus(message_id): class ISmsReceivingGateway(Interface):
"""Retrieve the status of a message """Gateway to subscribe to events fired when Short Messages are send to SMS interface.
Should return x in ['sent', 'delivered', 'queued', 'failed']""" """
def receive(REQUEST):
"""Public handler to push notifications from the gateway
REQUEST parameters are service provider dependent.
"""
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment