The Resource object in shared/dav.py is now able to send SOAP requests, with marshalled and unmarshalled Python objects.

This commit is contained in:
Gaetan Delannay 2010-11-08 11:40:41 +01:00
parent ca6dd26906
commit 3d87036f85
5 changed files with 206 additions and 106 deletions

View file

@ -1,5 +1,9 @@
'''Appy allows you to create easily complete applications in Python.'''
# ------------------------------------------------------------------------------
import os.path import os.path
# ------------------------------------------------------------------------------
def getPath(): return os.path.dirname(__file__) def getPath(): return os.path.dirname(__file__)
def versionIsGreaterThanOrEquals(version): def versionIsGreaterThanOrEquals(version):
'''This method returns True if the current Appy version is greater than or '''This method returns True if the current Appy version is greater than or
@ -12,3 +16,24 @@ def versionIsGreaterThanOrEquals(version):
paramVersion = [int(i) for i in version.split('.')] paramVersion = [int(i) for i in version.split('.')]
currentVersion = [int(i) for i in appy.version.short.split('.')] currentVersion = [int(i) for i in appy.version.short.split('.')]
return currentVersion >= paramVersion return currentVersion >= paramVersion
# ------------------------------------------------------------------------------
class Object:
'''At every place we need an object, but without any requirement on its
class (methods, attributes,...) we will use this minimalist class.'''
def __init__(self, **fields):
for k, v in fields.iteritems():
setattr(self, k, v)
def __repr__(self):
res = u'<Object '
for attrName, attrValue in self.__dict__.iteritems():
v = attrValue
if hasattr(v, '__repr__'):
v = v.__repr__()
try:
res += u'%s=%s ' % (attrName, v)
except UnicodeDecodeError:
res += u'%s=<encoding problem> ' % attrName
res = res.strip() + '>'
return res.encode('utf-8')
# ------------------------------------------------------------------------------

View file

@ -18,28 +18,9 @@ mimeTypesExts = {
'image/jpeg' : 'jpg', 'image/jpeg' : 'jpg',
'image/gif' : 'gif' 'image/gif' : 'gif'
} }
xmlPrologue = '<?xml version="1.0" encoding="utf-8"?>\n' xmlPrologue = '<?xml version="1.0" encoding="utf-8" ?>\n'
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class UnmarshalledObject:
'''Used for producing objects from a marshalled Python object (in some files
like a CSV file or an XML file).'''
def __init__(self, **fields):
for k, v in fields.iteritems():
setattr(self, k, v)
def __repr__(self):
res = u'<PythonObject '
for attrName, attrValue in self.__dict__.iteritems():
v = attrValue
if hasattr(v, '__repr__'):
v = v.__repr__()
try:
res += u'%s = %s ' % (attrName, v)
except UnicodeDecodeError:
res += u'%s = <encoding problem> ' % attrName
res = res.strip() + '>'
return res.encode('utf-8')
class UnmarshalledFile: class UnmarshalledFile:
'''Used for producing file objects from a marshalled Python object.''' '''Used for producing file objects from a marshalled Python object.'''
def __init__(self): def __init__(self):

View file

@ -17,7 +17,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from appy.shared import UnmarshalledObject from appy import Object
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
WRONG_LINE = 'Line number %d in file %s does not have the right number of ' \ WRONG_LINE = 'Line number %d in file %s does not have the right number of ' \
@ -61,9 +61,9 @@ class CsvParser:
# list): string, integer, float, boolean. # list): string, integer, float, boolean.
self.references = references self.references = references
self.klass = klass # If a klass is given here, instead of creating self.klass = klass # If a klass is given here, instead of creating
# UnmarshalledObject instances we will create instances of this class. # Object instances we will create instances of this class.
# But be careful: we will not call the constructor of this class. We # But be careful: we will not call the constructor of this class. We
# will simply create instances of UnmarshalledObject and dynamically # will simply create instances of Object and dynamically
# change the class of created instances to this class. # change the class of created instances to this class.
def identifySeparator(self, line): def identifySeparator(self, line):
@ -125,10 +125,10 @@ class CsvParser:
def parse(self): def parse(self):
'''Parses the CSV file named self.fileName and creates a list of '''Parses the CSV file named self.fileName and creates a list of
corresponding Python objects (UnmarshalledObject instances). Among corresponding Python objects (Object instances). Among object fields,
object fields, some may be references. If it is the case, you may some may be references. If it is the case, you may specify in
specify in p_references a dict of referred objects. The parser will p_references a dict of referred objects. The parser will then
then replace string values of some fields (which are supposed to be replace string values of some fields (which are supposed to be
ids of referred objects) with corresponding objects in p_references. ids of referred objects) with corresponding objects in p_references.
How does this work? p_references must be a dictionary: How does this work? p_references must be a dictionary:
@ -154,7 +154,7 @@ class CsvParser:
firstLine = False firstLine = False
else: else:
# Add an object corresponding to this line. # Add an object corresponding to this line.
lineObject = UnmarshalledObject() lineObject = Object()
if self.klass: if self.klass:
lineObject.__class__ = self.klass lineObject.__class__ = self.klass
i = -1 i = -1

View file

@ -4,11 +4,13 @@ from urllib import quote
from StringIO import StringIO from StringIO import StringIO
from mimetypes import guess_type from mimetypes import guess_type
from base64 import encodestring from base64 import encodestring
from appy import Object
from appy.shared.utils import copyData from appy.shared.utils import copyData
from appy.gen.utils import sequenceTypes from appy.gen.utils import sequenceTypes
from appy.shared.xml_parser import XmlUnmarshaller, XmlMarshaller
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class DataEncoder: class FormDataEncoder:
'''Allows to encode form data for sending it through a HTTP request.''' '''Allows to encode form data for sending it through a HTTP request.'''
def __init__(self, data): def __init__(self, data):
self.data = data # The data to encode, as a dict self.data = data # The data to encode, as a dict
@ -34,6 +36,33 @@ class DataEncoder:
res.append(self.marshalValue(name, value)) res.append(self.marshalValue(name, value))
return '&'.join(res) return '&'.join(res)
# ------------------------------------------------------------------------------
class SoapDataEncoder:
'''Allows to encode SOAP data for sending it through a HTTP request.'''
namespaces = {'SOAP-ENV': 'http://schemas.xmlsoap.org/soap/envelope/',
'xsd' : 'http://www.w3.org/2001/XMLSchema',
'xsi' : 'http://www.w3.org/2001/XMLSchema-instance'}
namespacedTags = {'Envelope': 'SOAP-ENV', 'Body': 'SOAP-ENV', '*': 'py'}
def __init__(self, data, namespace='http://appyframework.org'):
self.data = data
# p_data can be:
# - a string already containing a complete SOAP message
# - a Python object, that we will convert to a SOAP message
# Define the namespaces for this request
self.ns = self.namespaces.copy()
self.ns['py'] = namespace
def encode(self):
# Do nothing if we have a SOAP message already
if isinstance(self.data, basestring): return self.data
# self.data is here a Python object. Wrap it a SOAP Body.
soap = Object(Body=self.data)
# Marshall it.
marshaller = XmlMarshaller(rootTag='Envelope', namespaces=self.ns,
namespacedTags=self.namespacedTags)
return marshaller.marshall(soap)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class HttpResponse: class HttpResponse:
'''Stores information about a HTTP response.''' '''Stores information about a HTTP response.'''
@ -42,13 +71,13 @@ class HttpResponse:
self.text = text # Textual description of the code self.text = text # Textual description of the code
self.headers = headers # A dict-like object containing the headers self.headers = headers # A dict-like object containing the headers
self.body = body # The body of the HTTP response self.body = body # The body of the HTTP response
# p_duration, if given, is the time, in seconds, we have waited, before
# getting this response after having sent the request.
self.duration = duration
# The following attribute may contain specific data extracted from # The following attribute may contain specific data extracted from
# the previous fields. For example, when response if 302 (Redirect), # the previous fields. For example, when response if 302 (Redirect),
# self.data contains the URI where we must redirect the user to. # self.data contains the URI where we must redirect the user to.
self.data = self.extractData() self.data = self.extractData()
# p_duration, if given, is the time, in seconds, we have waited, before
# getting this response after having sent the request.
self.duration = duration
def __repr__(self): def __repr__(self):
duration = '' duration = ''
@ -58,9 +87,15 @@ class HttpResponse:
def extractData(self): def extractData(self):
'''This method extracts, from the various parts of the HTTP response, '''This method extracts, from the various parts of the HTTP response,
some useful information. For example, it will find the URI where to some useful information. For example, it will find the URI where to
redirect the user to if self.code is 302.''' redirect the user to if self.code is 302, or will unmarshall XML
data into Python objects.'''
if self.code == 302: if self.code == 302:
return urlparse.urlparse(self.headers['location'])[2] return urlparse.urlparse(self.headers['location'])[2]
elif self.headers.has_key('content-type') and \
self.headers['content-type'].startswith('text/xml'):
# Return an unmarshalled version of the XML content, for easy use
# in Python.
return XmlUnmarshaller().parse(self.body)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
urlRex = re.compile(r'http://([^:/]+)(:[0-9]+)?(/.+)?', re.I) urlRex = re.compile(r'http://([^:/]+)(:[0-9]+)?(/.+)?', re.I)
@ -109,7 +144,7 @@ class Resource:
headers['Accept'] = '*/*' headers['Accept'] = '*/*'
return headers return headers
def sendRequest(self, method, uri, body=None, headers={}, bodyType=None): def send(self, method, uri, body=None, headers={}, bodyType=None):
'''Sends a HTTP request with p_method, for p_uri.''' '''Sends a HTTP request with p_method, for p_uri.'''
conn = httplib.HTTP() conn = httplib.HTTP()
conn.connect(self.host, self.port) conn.connect(self.host, self.port)
@ -143,13 +178,13 @@ class Resource:
#body = '<d:propertyupdate xmlns:d="DAV:"><d:set><d:prop>' \ #body = '<d:propertyupdate xmlns:d="DAV:"><d:set><d:prop>' \
# '<d:displayname>%s</d:displayname></d:prop></d:set>' \ # '<d:displayname>%s</d:displayname></d:prop></d:set>' \
# '</d:propertyupdate>' % name # '</d:propertyupdate>' % name
return self.sendRequest('MKCOL', folderUri) return self.send('MKCOL', folderUri)
def delete(self, name): def delete(self, name):
'''Deletes a file or a folder (and all contained files if any) named '''Deletes a file or a folder (and all contained files if any) named
p_name within this resource.''' p_name within this resource.'''
toDeleteUri = self.uri + '/' + name toDeleteUri = self.uri + '/' + name
return self.sendRequest('DELETE', toDeleteUri) return self.send('DELETE', toDeleteUri)
def add(self, content, type='fileName', name=''): def add(self, content, type='fileName', name=''):
'''Adds a file in this resource. p_type can be: '''Adds a file in this resource. p_type can be:
@ -178,45 +213,44 @@ class Resource:
headers = {'Content-Length': str(size)} headers = {'Content-Length': str(size)}
if fileType: headers['Content-Type'] = fileType if fileType: headers['Content-Type'] = fileType
if encoding: headers['Content-Encoding'] = encoding if encoding: headers['Content-Encoding'] = encoding
res = self.sendRequest('PUT', fileUri, body, headers, bodyType=bodyType) res = self.send('PUT', fileUri, body, headers, bodyType=bodyType)
# Close the file when relevant # Close the file when relevant
if type =='fileName': body.close() if type =='fileName': body.close()
return res return res
def _encodeFormData(self, data):
'''Returns the encoded form p_data.'''
res = []
for name, value in data.items():
n = name.rfind( '__')
if n > 0:
tag = name[n+2:]
key = name[:n]
else: tag = 'string'
func = varfuncs.get(tag, marshal_string)
res.append(func(name, value))
return '&'.join(res)
def get(self, uri=None, headers={}): def get(self, uri=None, headers={}):
'''Perform a HTTP GET on the server.''' '''Perform a HTTP GET on the server.'''
if not uri: uri = self.uri if not uri: uri = self.uri
return self.sendRequest('GET', uri, headers=headers) return self.send('GET', uri, headers=headers)
def post(self, data=None, uri=None, headers={}, type='form'): def post(self, data=None, uri=None, headers={}, encode='form'):
'''Perform a HTTP POST on the server. If p_type is: '''Perform a HTTP POST on the server. If p_encode is "form", p_data is
- "form", p_data is a dict representing form data that will be considered to be a dict representing form data that will be
form-encoded; form-encoded. Else, p_data will be considered as the ready-to-send
- "soap", p_data is a XML request that will be wrapped in a SOAP body of the HTTP request.'''
message.'''
if not uri: uri = self.uri if not uri: uri = self.uri
# Prepare the data to send # Prepare the data to send
if type == 'form': headers['Host'] = self.host
if encode == 'form':
# Format the form data and prepare headers # Format the form data and prepare headers
body = DataEncoder(data).encode() body = FormDataEncoder(data).encode()
headers['Content-Type'] = 'application/x-www-form-urlencoded' headers['Content-Type'] = 'application/x-www-form-urlencoded'
elif type =='soap': else:
body = data body = data
headers['Content-Length'] = str(len(body))
return self.send('POST', uri, headers=headers, body=body)
def soap(self, data, uri=None, headers={}, namespace=None):
'''Sends a SOAP message to this resource. p_namespace is the URL of the
server-specific namespace.'''
if not uri: uri = self.uri
# Prepare the data to send
data = SoapDataEncoder(data, namespace).encode()
headers['SOAPAction'] = self.url headers['SOAPAction'] = self.url
headers['Content-Type'] = 'text/xml' headers['Content-Type'] = 'text/xml'
headers['Content-Length'] = str(len(body)) res = self.post(data, uri, headers=headers, encode=None)
return self.sendRequest('POST', uri, headers=headers, body=body) # Unwrap content from the SOAP envelope
res.data = res.data.Body
return res
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -140,7 +140,8 @@ class XmlParser(ContentHandler, ErrorHandler):
return self.res return self.res
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from appy.shared import UnmarshalledObject, UnmarshalledFile from appy.shared import UnmarshalledFile
from appy import Object
try: try:
from DateTime import DateTime from DateTime import DateTime
except ImportError: except ImportError:
@ -161,14 +162,14 @@ class XmlUnmarshaller(XmlParser):
XmlParser.__init__(self) XmlParser.__init__(self)
# self.classes below is a dict whose keys are tag names and values are # self.classes below is a dict whose keys are tag names and values are
# Python classes. During the unmarshalling process, when an object is # Python classes. During the unmarshalling process, when an object is
# encountered, instead of creating an instance of UnmarshalledObject, # encountered, instead of creating an instance of Object, we will create
# we will create an instance of the class specified in self.classes. # an instance of the class specified in self.classes.
# Root tag is named "xmlPythonData" by default by the XmlMarshaller. # Root tag is named "xmlPythonData" by default by the XmlMarshaller.
# This will not work if the object in the specified tag is not a # This will not work if the object in the specified tag is not an
# UnmarshalledObject instance (ie it is a list or tuple or simple # Object instance (ie it is a list or tuple or simple value). Note that
# value). Note that we will not call the constructor of the specified # we will not call the constructor of the specified class. We will
# class. We will simply create an instance of UnmarshalledObject and # simply create an instance of Objects and dynamically change the class
# dynamically change the class of the created instance to this class. # of the created instance to this class.
if not isinstance(classes, dict) and classes: if not isinstance(classes, dict) and classes:
# The user may only need to define a class for the root tag # The user may only need to define a class for the root tag
self.classes = {'xmlPythonData': classes} self.classes = {'xmlPythonData': classes}
@ -198,12 +199,14 @@ class XmlUnmarshaller(XmlParser):
def convertAttrs(self, attrs): def convertAttrs(self, attrs):
'''Converts XML attrs to a dict.''' '''Converts XML attrs to a dict.'''
res = {} res = {}
for k, v in attrs.items(): res[str(k)] = v for k, v in attrs.items():
if ':' in k: # An attr prefixed with a namespace. Remove this.
k = k.split(':')[-1]
res[str(k)] = v
return res return res
def startDocument(self): def startDocument(self):
self.res = None # The resulting web of Python objects self.res = None # The resulting web of Python objects (Object instances)
# (UnmarshalledObject instances).
self.env.containerStack = [] # The stack of current "containers" where self.env.containerStack = [] # The stack of current "containers" where
# to store the next parsed element. A container can be a list, a tuple, # to store the next parsed element. A container can be a list, a tuple,
# an object (the root object of the whole web or a sub-object). # an object (the root object of the whole web or a sub-object).
@ -214,6 +217,10 @@ class XmlUnmarshaller(XmlParser):
containerTags = ('tuple', 'list', 'object', 'file') containerTags = ('tuple', 'list', 'object', 'file')
numericTypes = ('bool', 'int', 'float', 'long') numericTypes = ('bool', 'int', 'float', 'long')
def startElement(self, elem, attrs): def startElement(self, elem, attrs):
# Remember the name of the previous element
previousElem = None
if self.env.currentElem:
previousElem = self.env.currentElem.name
e = XmlParser.startElement(self, elem, attrs) e = XmlParser.startElement(self, elem, attrs)
# Determine the type of the element. # Determine the type of the element.
elemType = 'unicode' # Default value elemType = 'unicode' # Default value
@ -224,7 +231,7 @@ class XmlUnmarshaller(XmlParser):
if elemType in self.containerTags: if elemType in self.containerTags:
# I must create a new container object. # I must create a new container object.
if elemType == 'object': if elemType == 'object':
newObject = UnmarshalledObject(**self.convertAttrs(attrs)) newObject = Object(**self.convertAttrs(attrs))
elif elemType == 'tuple': newObject = [] # Tuples become lists elif elemType == 'tuple': newObject = [] # Tuples become lists
elif elemType == 'list': newObject = [] elif elemType == 'list': newObject = []
elif elemType == 'file': elif elemType == 'file':
@ -233,20 +240,31 @@ class XmlUnmarshaller(XmlParser):
newObject.name = attrs['name'] newObject.name = attrs['name']
if attrs.has_key('mimeType'): if attrs.has_key('mimeType'):
newObject.mimeType = attrs['mimeType'] newObject.mimeType = attrs['mimeType']
else: newObject = UnmarshalledObject(**self.convertAttrs(attrs)) else: newObject = Object(**self.convertAttrs(attrs))
# Store the value on the last container, or on the root object. # Store the value on the last container, or on the root object.
self.storeValue(elem, newObject) self.storeValue(elem, newObject)
# Push the new object on the container stack # Push the new object on the container stack
e.containerStack.append(newObject) e.containerStack.append(newObject)
else: else:
# If we are already parsing a basic type, it means that we were
# wrong for our diagnotsic of the containing element: it was not
# basic. We will make the assumption that the containing element is
# then an object.
if e.currentBasicType:
# Previous elem was an object: create it on the stack.
newObject = Object()
self.storeValue(previousElem, newObject)
e.containerStack.append(newObject)
e.currentBasicType = elemType e.currentBasicType = elemType
def storeValue(self, name, value): def storeValue(self, name, value):
'''Stores the newly parsed p_value (contained in tag p_name) on the '''Stores the newly parsed p_value (contained in tag p_name) on the
current container in environment self.env.''' current container in environment self.env.'''
e = self.env e = self.env
# Remove namespace prefix when relevant
if ':' in name: name = name.split(':')[-1]
# Change the class of the value if relevant # Change the class of the value if relevant
if (name in self.classes) and isinstance(value, UnmarshalledObject): if (name in self.classes) and isinstance(value, Object):
value.__class__ = self.classes[name] value.__class__ = self.classes[name]
# Where must I store this value? # Where must I store this value?
if not e.containerStack: if not e.containerStack:
@ -344,7 +362,8 @@ class XmlMarshaller:
atFiles = ('image', 'file') # Types of archetypes fields that contain files. atFiles = ('image', 'file') # Types of archetypes fields that contain files.
def __init__(self, cdata=False, dumpUnicode=False, conversionFunctions={}, def __init__(self, cdata=False, dumpUnicode=False, conversionFunctions={},
dumpXmlPrologue=True, rootTag='xmlPythonData'): dumpXmlPrologue=True, rootTag='xmlPythonData', namespaces={},
namespacedTags={}):
# If p_cdata is True, all string values will be dumped as XML CDATA. # If p_cdata is True, all string values will be dumped as XML CDATA.
self.cdata = cdata self.cdata = cdata
# If p_dumpUnicode is True, the result will be unicode. # If p_dumpUnicode is True, the result will be unicode.
@ -363,6 +382,41 @@ class XmlMarshaller:
self.dumpXmlPrologue = dumpXmlPrologue self.dumpXmlPrologue = dumpXmlPrologue
# The name of the root tag # The name of the root tag
self.rootElementName = rootTag self.rootElementName = rootTag
# The namespaces that will be defined at the root of the XML message.
# It is a dict whose keys are namespace prefixes and whose values are
# namespace URLs.
self.namespaces = namespaces
# The following dict will tell which XML tags will get which namespace
# prefix ({s_tagName: s_prefix}). Special optional dict entry
# '*':s_prefix will indicate a default prefix that will be applied to
# any tag that does not have it own key in this dict.
self.namespacedTags = namespacedTags
self.objectType = None # Will be given by method m_marshal
def getTagName(self, name):
'''Returns the name of tag p_name as will be dumped. It can be p_name,
or p_name prefixed with a namespace prefix (will depend on
self.prefixedTags).'''
# Determine the prefix
prefix = ''
if name in self.namespacedTags: prefix = self.namespacedTags[name]
elif '*' in self.namespacedTags: prefix = self.namespacedTags['*']
if prefix: return '%s:%s' % (prefix, name)
return name
def dumpRootTag(self, res, instance):
'''Dumps the root tag.'''
# Dumps the name of the tag.
tagName = self.getTagName(self.rootElementName)
res.write('<'); res.write(tagName)
# Dumps namespace definitions if any
for prefix, url in self.namespaces.iteritems():
res.write(' xmlns:%s="%s"' % (prefix, url))
# Dumps Appy- or Plone-specific attributed
if self.objectType != 'popo':
res.write(' type="object" id="%s"' % instance.UID())
res.write('>')
return tagName
def dumpString(self, res, s): def dumpString(self, res, s):
'''Dumps a string into the result.''' '''Dumps a string into the result.'''
@ -382,7 +436,8 @@ class XmlMarshaller:
if not v: return if not v: return
# p_value contains the (possibly binary) content of a file. We will # p_value contains the (possibly binary) content of a file. We will
# encode it in Base64, in one or several parts. # encode it in Base64, in one or several parts.
res.write('<part type="base64" number="1">') partTag = self.getTagName('part')
res.write('<%s type="base64" number="1">' % partTag)
if hasattr(v, 'data'): if hasattr(v, 'data'):
# The file is an Archetypes file. # The file is an Archetypes file.
valueType = v.data.__class__.__name__ valueType = v.data.__class__.__name__
@ -393,8 +448,9 @@ class XmlMarshaller:
nextPart = v.data.next nextPart = v.data.next
nextPartNumber = 2 nextPartNumber = 2
while nextPart: while nextPart:
res.write('</part>') # Close the previous part res.write('</%s>' % partTag) # Close the previous part
res.write('<part type="base64" number="%d">'%nextPartNumber) res.write('<%s type="base64" number="%d">' % \
(partTag, nextPartNumber))
res.write(nextPart.data.encode('base64')) res.write(nextPart.data.encode('base64'))
nextPart = nextPart.next nextPart = nextPart.next
nextPartNumber += 1 nextPartNumber += 1
@ -402,7 +458,7 @@ class XmlMarshaller:
res.write(v.data.encode('base64')) res.write(v.data.encode('base64'))
else: else:
res.write(v.encode('base64')) res.write(v.encode('base64'))
res.write('</part>') res.write('</%s>' % partTag)
def dumpValue(self, res, value, fieldType, isRef=False): def dumpValue(self, res, value, fieldType, isRef=False):
'''Dumps the XML version of p_value to p_res.''' '''Dumps the XML version of p_value to p_res.'''
@ -432,19 +488,23 @@ class XmlMarshaller:
res.write(self.trueFalse[value]) res.write(self.trueFalse[value])
elif fieldType == 'object': elif fieldType == 'object':
if hasattr(value, 'absolute_url'): if hasattr(value, 'absolute_url'):
# Dump the URL to the object only
res.write(value.absolute_url()) res.write(value.absolute_url())
else: else:
res.write(value) # Dump the entire object content
# We could dump the entire object content, too. Maybe we could add a for k, v in value.__dict__.iteritems():
# parameter to the marshaller to know how to marshall objects if not k.startswith('__'):
# (produce an ID, an URL, include the entire tag but we need to take self.dumpField(res, k, v)
# care of circular references,...) # Maybe we could add a parameter to the marshaller to know how
# to marshall objects (produce an ID, an URL, include the entire
# tag but we need to take care of circular references,...)
else: else:
res.write(value) res.write(value)
def dumpField(self, res, fieldName, fieldValue, fieldType='basic'): def dumpField(self, res, fieldName, fieldValue, fieldType='basic'):
'''Dumps in p_res, the value of the p_field for p_instance.''' '''Dumps in p_res, the value of the p_field for p_instance.'''
res.write('<'); res.write(fieldName); fieldTag = self.getTagName(fieldName)
res.write('<'); res.write(fieldTag)
# Dump the type of the field as an XML attribute # Dump the type of the field as an XML attribute
fType = None # No type will mean "unicode". fType = None # No type will mean "unicode".
if fieldType == 'file': fType = 'file' if fieldType == 'file': fType = 'file'
@ -457,6 +517,7 @@ class XmlMarshaller:
elif isinstance(fieldValue, list): fType = 'list' elif isinstance(fieldValue, list): fType = 'list'
elif fieldValue.__class__.__name__ == 'DateTime': fType = 'DateTime' elif fieldValue.__class__.__name__ == 'DateTime': fType = 'DateTime'
elif self.isAnObject(fieldValue): fType = 'object' elif self.isAnObject(fieldValue): fType = 'object'
if self.objectType != 'popo':
if fType: res.write(' type="%s"' % fType) if fType: res.write(' type="%s"' % fType)
# Dump other attributes if needed # Dump other attributes if needed
if type(fieldValue) in self.sequenceTypes: if type(fieldValue) in self.sequenceTypes:
@ -471,7 +532,7 @@ class XmlMarshaller:
res.write('>') res.write('>')
# Dump the field value # Dump the field value
self.dumpValue(res, fieldValue, fType, isRef=(fieldType=='ref')) self.dumpValue(res, fieldValue, fType, isRef=(fieldType=='ref'))
res.write('</'); res.write(fieldName); res.write('>') res.write('</'); res.write(fieldTag); res.write('>')
def isAnObject(self, instance): def isAnObject(self, instance):
'''Returns True if p_instance is a class instance, False if it is a '''Returns True if p_instance is a class instance, False if it is a
@ -484,6 +545,7 @@ class XmlMarshaller:
return True return True
elif iType.__class__.__name__ == 'ExtensionClass': elif iType.__class__.__name__ == 'ExtensionClass':
return True return True
return False return False
def marshall(self, instance, objectType='popo', conversionFunctions={}): def marshall(self, instance, objectType='popo', conversionFunctions={}):
@ -494,6 +556,7 @@ class XmlMarshaller:
a Appy object, specify "appy" as p_objectType. If p_instance is not a Appy object, specify "appy" as p_objectType. If p_instance is not
an instance at all, but another Python data structure or basic type, an instance at all, but another Python data structure or basic type,
p_objectType is ignored.''' p_objectType is ignored.'''
self.objectType = objectType
# Call the XmlMarshaller constructor if it hasn't been called yet. # Call the XmlMarshaller constructor if it hasn't been called yet.
if not hasattr(self, 'cdata'): if not hasattr(self, 'cdata'):
XmlMarshaller.__init__(self) XmlMarshaller.__init__(self)
@ -505,14 +568,9 @@ class XmlMarshaller:
if self.dumpXmlPrologue: if self.dumpXmlPrologue:
res.write(xmlPrologue) res.write(xmlPrologue)
if self.isAnObject(instance): if self.isAnObject(instance):
# Determine object ID # Dump the root tag
if objectType in ('archetype', 'appy'): rootTagName = self.dumpRootTag(res, instance)
objectId = instance.UID() # ID in DB # Dump the fields of this root object
else:
objectId = str(id(instance)) # ID in RAM
res.write('<'); res.write(self.rootElementName)
res.write(' type="object" id="');res.write(objectId);res.write('">')
# Dump the object ID and the value of the fields that must be dumped
if objectType == 'popo': if objectType == 'popo':
for fieldName, fieldValue in instance.__dict__.iteritems(): for fieldName, fieldValue in instance.__dict__.iteritems():
mustDump = False mustDump = False
@ -565,18 +623,20 @@ class XmlMarshaller:
self.dumpField(res, field.name,field.getValue(instance), self.dumpField(res, field.name,field.getValue(instance),
fieldType=fieldType) fieldType=fieldType)
# Dump the object history. # Dump the object history.
res.write('<history type="list">') histTag = self.getTagName('history')
eventTag = self.getTagName('event')
res.write('<%s type="list">' % histTag)
wfInfo = instance.portal_workflow.getWorkflowsFor(instance) wfInfo = instance.portal_workflow.getWorkflowsFor(instance)
if wfInfo: if wfInfo:
history = instance.workflow_history[wfInfo[0].id] history = instance.workflow_history[wfInfo[0].id]
for event in history: for event in history:
res.write('<event type="object">') res.write('<%s type="object">' % eventTag)
for k, v in event.iteritems(): for k, v in event.iteritems():
self.dumpField(res, k, v) self.dumpField(res, k, v)
res.write('</event>') res.write('</%s>' % eventTag)
res.write('</history>') res.write('</%s>' % histTag)
self.marshallSpecificElements(instance, res) self.marshallSpecificElements(instance, res)
res.write('</'); res.write(self.rootElementName); res.write('>') res.write('</'); res.write(rootTagName); res.write('>')
else: else:
self.dumpField(res, self.rootElementName, instance) self.dumpField(res, self.rootElementName, instance)
# Return the result # Return the result