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:
parent
ca6dd26906
commit
3d87036f85
25
__init__.py
25
__init__.py
|
@ -1,5 +1,9 @@
|
|||
'''Appy allows you to create easily complete applications in Python.'''
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
import os.path
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
def getPath(): return os.path.dirname(__file__)
|
||||
def versionIsGreaterThanOrEquals(version):
|
||||
'''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('.')]
|
||||
currentVersion = [int(i) for i in appy.version.short.split('.')]
|
||||
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')
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -18,28 +18,9 @@ mimeTypesExts = {
|
|||
'image/jpeg' : 'jpg',
|
||||
'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:
|
||||
'''Used for producing file objects from a marshalled Python object.'''
|
||||
def __init__(self):
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
# 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 ' \
|
||||
|
@ -61,9 +61,9 @@ class CsvParser:
|
|||
# list): string, integer, float, boolean.
|
||||
self.references = references
|
||||
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
|
||||
# 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.
|
||||
|
||||
def identifySeparator(self, line):
|
||||
|
@ -125,10 +125,10 @@ class CsvParser:
|
|||
|
||||
def parse(self):
|
||||
'''Parses the CSV file named self.fileName and creates a list of
|
||||
corresponding Python objects (UnmarshalledObject instances). Among
|
||||
object fields, some may be references. If it is the case, you may
|
||||
specify in p_references a dict of referred objects. The parser will
|
||||
then replace string values of some fields (which are supposed to be
|
||||
corresponding Python objects (Object instances). Among object fields,
|
||||
some may be references. If it is the case, you may specify in
|
||||
p_references a dict of referred objects. The parser will then
|
||||
replace string values of some fields (which are supposed to be
|
||||
ids of referred objects) with corresponding objects in p_references.
|
||||
|
||||
How does this work? p_references must be a dictionary:
|
||||
|
@ -154,7 +154,7 @@ class CsvParser:
|
|||
firstLine = False
|
||||
else:
|
||||
# Add an object corresponding to this line.
|
||||
lineObject = UnmarshalledObject()
|
||||
lineObject = Object()
|
||||
if self.klass:
|
||||
lineObject.__class__ = self.klass
|
||||
i = -1
|
||||
|
|
104
shared/dav.py
104
shared/dav.py
|
@ -4,11 +4,13 @@ from urllib import quote
|
|||
from StringIO import StringIO
|
||||
from mimetypes import guess_type
|
||||
from base64 import encodestring
|
||||
from appy import Object
|
||||
from appy.shared.utils import copyData
|
||||
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.'''
|
||||
def __init__(self, data):
|
||||
self.data = data # The data to encode, as a dict
|
||||
|
@ -34,6 +36,33 @@ class DataEncoder:
|
|||
res.append(self.marshalValue(name, value))
|
||||
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:
|
||||
'''Stores information about a HTTP response.'''
|
||||
|
@ -42,13 +71,13 @@ class HttpResponse:
|
|||
self.text = text # Textual description of the code
|
||||
self.headers = headers # A dict-like object containing the headers
|
||||
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 previous fields. For example, when response if 302 (Redirect),
|
||||
# self.data contains the URI where we must redirect the user to.
|
||||
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):
|
||||
duration = ''
|
||||
|
@ -58,9 +87,15 @@ class HttpResponse:
|
|||
def extractData(self):
|
||||
'''This method extracts, from the various parts of the HTTP response,
|
||||
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:
|
||||
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)
|
||||
|
@ -109,7 +144,7 @@ class Resource:
|
|||
headers['Accept'] = '*/*'
|
||||
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.'''
|
||||
conn = httplib.HTTP()
|
||||
conn.connect(self.host, self.port)
|
||||
|
@ -143,13 +178,13 @@ class Resource:
|
|||
#body = '<d:propertyupdate xmlns:d="DAV:"><d:set><d:prop>' \
|
||||
# '<d:displayname>%s</d:displayname></d:prop></d:set>' \
|
||||
# '</d:propertyupdate>' % name
|
||||
return self.sendRequest('MKCOL', folderUri)
|
||||
return self.send('MKCOL', folderUri)
|
||||
|
||||
def delete(self, name):
|
||||
'''Deletes a file or a folder (and all contained files if any) named
|
||||
p_name within this resource.'''
|
||||
toDeleteUri = self.uri + '/' + name
|
||||
return self.sendRequest('DELETE', toDeleteUri)
|
||||
return self.send('DELETE', toDeleteUri)
|
||||
|
||||
def add(self, content, type='fileName', name=''):
|
||||
'''Adds a file in this resource. p_type can be:
|
||||
|
@ -178,45 +213,44 @@ class Resource:
|
|||
headers = {'Content-Length': str(size)}
|
||||
if fileType: headers['Content-Type'] = fileType
|
||||
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
|
||||
if type =='fileName': body.close()
|
||||
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={}):
|
||||
'''Perform a HTTP GET on the server.'''
|
||||
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'):
|
||||
'''Perform a HTTP POST on the server. If p_type is:
|
||||
- "form", p_data is a dict representing form data that will be
|
||||
form-encoded;
|
||||
- "soap", p_data is a XML request that will be wrapped in a SOAP
|
||||
message.'''
|
||||
def post(self, data=None, uri=None, headers={}, encode='form'):
|
||||
'''Perform a HTTP POST on the server. If p_encode is "form", p_data is
|
||||
considered to be a dict representing form data that will be
|
||||
form-encoded. Else, p_data will be considered as the ready-to-send
|
||||
body of the HTTP request.'''
|
||||
if not uri: uri = self.uri
|
||||
# Prepare the data to send
|
||||
if type == 'form':
|
||||
headers['Host'] = self.host
|
||||
if encode == 'form':
|
||||
# Format the form data and prepare headers
|
||||
body = DataEncoder(data).encode()
|
||||
body = FormDataEncoder(data).encode()
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
elif type =='soap':
|
||||
else:
|
||||
body = data
|
||||
headers['SOAPAction'] = self.url
|
||||
headers['Content-Type'] = 'text/xml'
|
||||
headers['Content-Length'] = str(len(body))
|
||||
return self.sendRequest('POST', uri, headers=headers, body=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['Content-Type'] = 'text/xml'
|
||||
res = self.post(data, uri, headers=headers, encode=None)
|
||||
# Unwrap content from the SOAP envelope
|
||||
res.data = res.data.Body
|
||||
return res
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -140,7 +140,8 @@ class XmlParser(ContentHandler, ErrorHandler):
|
|||
return self.res
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
from appy.shared import UnmarshalledObject, UnmarshalledFile
|
||||
from appy.shared import UnmarshalledFile
|
||||
from appy import Object
|
||||
try:
|
||||
from DateTime import DateTime
|
||||
except ImportError:
|
||||
|
@ -161,14 +162,14 @@ class XmlUnmarshaller(XmlParser):
|
|||
XmlParser.__init__(self)
|
||||
# self.classes below is a dict whose keys are tag names and values are
|
||||
# Python classes. During the unmarshalling process, when an object is
|
||||
# encountered, instead of creating an instance of UnmarshalledObject,
|
||||
# we will create an instance of the class specified in self.classes.
|
||||
# encountered, instead of creating an instance of Object, we will create
|
||||
# an instance of the class specified in self.classes.
|
||||
# Root tag is named "xmlPythonData" by default by the XmlMarshaller.
|
||||
# This will not work if the object in the specified tag is not a
|
||||
# UnmarshalledObject instance (ie it is a list or tuple or simple
|
||||
# value). Note that we will not call the constructor of the specified
|
||||
# class. We will simply create an instance of UnmarshalledObject and
|
||||
# dynamically change the class of the created instance to this class.
|
||||
# This will not work if the object in the specified tag is not an
|
||||
# Object instance (ie it is a list or tuple or simple value). Note that
|
||||
# we will not call the constructor of the specified class. We will
|
||||
# simply create an instance of Objects and dynamically change the class
|
||||
# of the created instance to this class.
|
||||
if not isinstance(classes, dict) and classes:
|
||||
# The user may only need to define a class for the root tag
|
||||
self.classes = {'xmlPythonData': classes}
|
||||
|
@ -198,12 +199,14 @@ class XmlUnmarshaller(XmlParser):
|
|||
def convertAttrs(self, attrs):
|
||||
'''Converts XML attrs to a dict.'''
|
||||
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
|
||||
|
||||
def startDocument(self):
|
||||
self.res = None # The resulting web of Python objects
|
||||
# (UnmarshalledObject instances).
|
||||
self.res = None # The resulting web of Python objects (Object instances)
|
||||
self.env.containerStack = [] # The stack of current "containers" where
|
||||
# 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).
|
||||
|
@ -214,6 +217,10 @@ class XmlUnmarshaller(XmlParser):
|
|||
containerTags = ('tuple', 'list', 'object', 'file')
|
||||
numericTypes = ('bool', 'int', 'float', 'long')
|
||||
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)
|
||||
# Determine the type of the element.
|
||||
elemType = 'unicode' # Default value
|
||||
|
@ -224,7 +231,7 @@ class XmlUnmarshaller(XmlParser):
|
|||
if elemType in self.containerTags:
|
||||
# I must create a new container object.
|
||||
if elemType == 'object':
|
||||
newObject = UnmarshalledObject(**self.convertAttrs(attrs))
|
||||
newObject = Object(**self.convertAttrs(attrs))
|
||||
elif elemType == 'tuple': newObject = [] # Tuples become lists
|
||||
elif elemType == 'list': newObject = []
|
||||
elif elemType == 'file':
|
||||
|
@ -233,20 +240,31 @@ class XmlUnmarshaller(XmlParser):
|
|||
newObject.name = attrs['name']
|
||||
if attrs.has_key('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.
|
||||
self.storeValue(elem, newObject)
|
||||
# Push the new object on the container stack
|
||||
e.containerStack.append(newObject)
|
||||
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
|
||||
|
||||
def storeValue(self, name, value):
|
||||
'''Stores the newly parsed p_value (contained in tag p_name) on the
|
||||
current container in environment 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
|
||||
if (name in self.classes) and isinstance(value, UnmarshalledObject):
|
||||
if (name in self.classes) and isinstance(value, Object):
|
||||
value.__class__ = self.classes[name]
|
||||
# Where must I store this value?
|
||||
if not e.containerStack:
|
||||
|
@ -344,7 +362,8 @@ class XmlMarshaller:
|
|||
atFiles = ('image', 'file') # Types of archetypes fields that contain files.
|
||||
|
||||
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.
|
||||
self.cdata = cdata
|
||||
# If p_dumpUnicode is True, the result will be unicode.
|
||||
|
@ -363,6 +382,41 @@ class XmlMarshaller:
|
|||
self.dumpXmlPrologue = dumpXmlPrologue
|
||||
# The name of the root tag
|
||||
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):
|
||||
'''Dumps a string into the result.'''
|
||||
|
@ -382,7 +436,8 @@ class XmlMarshaller:
|
|||
if not v: return
|
||||
# p_value contains the (possibly binary) content of a file. We will
|
||||
# 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'):
|
||||
# The file is an Archetypes file.
|
||||
valueType = v.data.__class__.__name__
|
||||
|
@ -393,8 +448,9 @@ class XmlMarshaller:
|
|||
nextPart = v.data.next
|
||||
nextPartNumber = 2
|
||||
while nextPart:
|
||||
res.write('</part>') # Close the previous part
|
||||
res.write('<part type="base64" number="%d">'%nextPartNumber)
|
||||
res.write('</%s>' % partTag) # Close the previous part
|
||||
res.write('<%s type="base64" number="%d">' % \
|
||||
(partTag, nextPartNumber))
|
||||
res.write(nextPart.data.encode('base64'))
|
||||
nextPart = nextPart.next
|
||||
nextPartNumber += 1
|
||||
|
@ -402,7 +458,7 @@ class XmlMarshaller:
|
|||
res.write(v.data.encode('base64'))
|
||||
else:
|
||||
res.write(v.encode('base64'))
|
||||
res.write('</part>')
|
||||
res.write('</%s>' % partTag)
|
||||
|
||||
def dumpValue(self, res, value, fieldType, isRef=False):
|
||||
'''Dumps the XML version of p_value to p_res.'''
|
||||
|
@ -432,19 +488,23 @@ class XmlMarshaller:
|
|||
res.write(self.trueFalse[value])
|
||||
elif fieldType == 'object':
|
||||
if hasattr(value, 'absolute_url'):
|
||||
# Dump the URL to the object only
|
||||
res.write(value.absolute_url())
|
||||
else:
|
||||
res.write(value)
|
||||
# We could dump the entire object content, too. 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,...)
|
||||
# Dump the entire object content
|
||||
for k, v in value.__dict__.iteritems():
|
||||
if not k.startswith('__'):
|
||||
self.dumpField(res, k, v)
|
||||
# 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:
|
||||
res.write(value)
|
||||
|
||||
def dumpField(self, res, fieldName, fieldValue, fieldType='basic'):
|
||||
'''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
|
||||
fType = None # No type will mean "unicode".
|
||||
if fieldType == 'file': fType = 'file'
|
||||
|
@ -457,10 +517,11 @@ class XmlMarshaller:
|
|||
elif isinstance(fieldValue, list): fType = 'list'
|
||||
elif fieldValue.__class__.__name__ == 'DateTime': fType = 'DateTime'
|
||||
elif self.isAnObject(fieldValue): fType = 'object'
|
||||
if fType: res.write(' type="%s"' % fType)
|
||||
# Dump other attributes if needed
|
||||
if type(fieldValue) in self.sequenceTypes:
|
||||
res.write(' count="%d"' % len(fieldValue))
|
||||
if self.objectType != 'popo':
|
||||
if fType: res.write(' type="%s"' % fType)
|
||||
# Dump other attributes if needed
|
||||
if type(fieldValue) in self.sequenceTypes:
|
||||
res.write(' count="%d"' % len(fieldValue))
|
||||
if fType == 'file':
|
||||
if hasattr(fieldValue, 'content_type'):
|
||||
res.write(' mimeType="%s"' % fieldValue.content_type)
|
||||
|
@ -471,7 +532,7 @@ class XmlMarshaller:
|
|||
res.write('>')
|
||||
# Dump the field value
|
||||
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):
|
||||
'''Returns True if p_instance is a class instance, False if it is a
|
||||
|
@ -484,6 +545,7 @@ class XmlMarshaller:
|
|||
return True
|
||||
elif iType.__class__.__name__ == 'ExtensionClass':
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
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
|
||||
an instance at all, but another Python data structure or basic type,
|
||||
p_objectType is ignored.'''
|
||||
self.objectType = objectType
|
||||
# Call the XmlMarshaller constructor if it hasn't been called yet.
|
||||
if not hasattr(self, 'cdata'):
|
||||
XmlMarshaller.__init__(self)
|
||||
|
@ -505,14 +568,9 @@ class XmlMarshaller:
|
|||
if self.dumpXmlPrologue:
|
||||
res.write(xmlPrologue)
|
||||
if self.isAnObject(instance):
|
||||
# Determine object ID
|
||||
if objectType in ('archetype', 'appy'):
|
||||
objectId = instance.UID() # ID in DB
|
||||
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
|
||||
# Dump the root tag
|
||||
rootTagName = self.dumpRootTag(res, instance)
|
||||
# Dump the fields of this root object
|
||||
if objectType == 'popo':
|
||||
for fieldName, fieldValue in instance.__dict__.iteritems():
|
||||
mustDump = False
|
||||
|
@ -565,18 +623,20 @@ class XmlMarshaller:
|
|||
self.dumpField(res, field.name,field.getValue(instance),
|
||||
fieldType=fieldType)
|
||||
# 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)
|
||||
if wfInfo:
|
||||
history = instance.workflow_history[wfInfo[0].id]
|
||||
for event in history:
|
||||
res.write('<event type="object">')
|
||||
res.write('<%s type="object">' % eventTag)
|
||||
for k, v in event.iteritems():
|
||||
self.dumpField(res, k, v)
|
||||
res.write('</event>')
|
||||
res.write('</history>')
|
||||
res.write('</%s>' % eventTag)
|
||||
res.write('</%s>' % histTag)
|
||||
self.marshallSpecificElements(instance, res)
|
||||
res.write('</'); res.write(self.rootElementName); res.write('>')
|
||||
res.write('</'); res.write(rootTagName); res.write('>')
|
||||
else:
|
||||
self.dumpField(res, self.rootElementName, instance)
|
||||
# Return the result
|
||||
|
|
Loading…
Reference in a new issue