[gen] Extended the HTTP-XML system to be able to call a method via a HTTP POST containing XML data (SOAP or REST-like).

This commit is contained in:
Gaetan Delannay 2013-10-08 22:41:21 +02:00
parent 91e0bd2240
commit 6a83285e64
4 changed files with 38 additions and 7 deletions

View file

@ -46,7 +46,7 @@ class Phase:
<img src=":url('edit')" title=":_('object_edit')"/></a> <img src=":url('edit')" title=":_('object_edit')"/></a>
<a if="editable and locked"> <a if="editable and locked">
<img style="cursor: help" <img style="cursor: help"
var="lockDate=tool.formatDate(locked[1]); var="lockDate=ztool.formatDate(locked[1]);
lockMap={'user':ztool.getUserName(locked[0]), \ lockMap={'user':ztool.getUserName(locked[0]), \
'date':lockDate}; 'date':lockDate};
lockMsg=_('page_locked', mapping=lockMap)" lockMsg=_('page_locked', mapping=lockMap)"

View file

@ -286,6 +286,13 @@ class ZopeInstaller:
if role not in roles: roles.append(role) if role not in roles: roles.append(role)
self.app.__ac_roles__ = tuple(roles) self.app.__ac_roles__ = tuple(roles)
def patchZope(self):
'''Patches some arts of Zope.'''
# Disables XMLRPC. This way, Zope can transmit HTTP POSTs containing
# XML to Appy without trying to recognize it himself as XMLRPC requests.
import ZPublisher.HTTPRequest
ZPublisher.HTTPRequest.xmlrpc = FakeXmlrpc()
def installDependencies(self): def installDependencies(self):
'''Zope products are installed in alphabetical order. But here, we need '''Zope products are installed in alphabetical order. But here, we need
ZCTextIndex to be installed before our Appy application. So, we cheat ZCTextIndex to be installed before our Appy application. So, we cheat
@ -297,6 +304,7 @@ class ZopeInstaller:
def install(self): def install(self):
self.logger.info('is being installed...') self.logger.info('is being installed...')
self.installDependencies() self.installDependencies()
self.patchZope()
self.installRoles() self.installRoles()
self.installAppyTypes() self.installAppyTypes()
self.installZopeClasses() self.installZopeClasses()

View file

@ -5,6 +5,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import os, os.path, sys, types, urllib, cgi import os, os.path, sys, types, urllib, cgi
from appy import Object from appy import Object
from appy.px import Px
from appy.fields.workflow import UiTransition from appy.fields.workflow import UiTransition
import appy.gen as gen import appy.gen as gen
from appy.gen.utils import * from appy.gen.utils import *
@ -479,7 +480,7 @@ class BaseMixin:
def xml(self, action=None): def xml(self, action=None):
'''If no p_action is defined, this method returns the XML version of '''If no p_action is defined, this method returns the XML version of
this object. Else, it calls method named p_action on the this object. Else, it calls method named p_action on the
corresponding Appy wrapper and returns, as XML, the its result.''' corresponding Appy wrapper and returns, as XML, its result.'''
self.REQUEST.RESPONSE.setHeader('Content-Type','text/xml;charset=utf-8') self.REQUEST.RESPONSE.setHeader('Content-Type','text/xml;charset=utf-8')
# Check if the user is allowed to consult this object # Check if the user is allowed to consult this object
if not self.allows('read'): if not self.allows('read'):
@ -492,6 +493,12 @@ class BaseMixin:
appyObj = self.appy() appyObj = self.appy()
try: try:
methodRes = getattr(appyObj, action)() methodRes = getattr(appyObj, action)()
if isinstance(methodRes, Px):
res = methodRes({'self': self.appy()})
elif isinstance(methodRes, file):
res = methodRes.read()
methodRes.close()
else:
res = XmlMarshaller().marshall(methodRes, objectType='appy') res = XmlMarshaller().marshall(methodRes, objectType='appy')
except Exception, e: except Exception, e:
tb = Traceback.get() tb = Traceback.get()
@ -1378,9 +1385,24 @@ class BaseMixin:
return res return res
def index_html(self): def index_html(self):
'''Redirects to /view.''' '''Base method called when hitting this object.
- The standard behaviour is to redirect to /view.
- If a parameter named "do" is present in the request, it is supposed
to contain the name of a method to call on this object. In this
case, we call this method and return its result as XML.
- If method is POST, we consider the request to be XML data, that we
marshall to Python, and we call the method in param "do" with, as
arg, this marshalled Python object. While this could sound strange
to expect a query string containing a param "do" in a HTTP POST,
the HTTP spec does not prevent to do it.'''
rq = self.REQUEST rq = self.REQUEST
if rq.has_key('do'): if (rq.REQUEST_METHOD == 'POST') and rq.QUERY_STRING:
# A POST method containing XML data.
rq.args = XmlUnmarshaller().parse(rq.stdin.getvalue())
# Find the name of the method to call.
methodName = rq.QUERY_STRING.split('=')[1]
return self.xml(action=methodName)
elif rq.has_key('do'):
# The user wants to call a method on this object and get its result # The user wants to call a method on this object and get its result
# as XML. # as XML.
return self.xml(action=rq['do']) return self.xml(action=rq['do'])

View file

@ -476,8 +476,9 @@ class AbstractWrapper(object):
<!-- Locked --> <!-- Locked -->
<a if="editable and locked"> <a if="editable and locked">
<img style="cursor: help" <img style="cursor: help"
var="lockDate=tool.formatDate(locked[1]); var="lockDate=ztool.formatDate(locked[1]);
lockMap={'user':tool.getUserName(locked[0]),'date':lockDate}; lockMap={'user':ztool.getUserName(locked[0]), \
'date':lockDate};
lockMsg=_('page_locked', mapping=lockMap)" lockMsg=_('page_locked', mapping=lockMap)"
src=":url('lockedBig')" title=":lockMsg"/></a> src=":url('lockedBig')" title=":lockMsg"/></a>
</td> </td>