From c53654a1a159324db50faa20d3528a6ba681d797 Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Mon, 8 Dec 2014 14:52:04 +0100 Subject: [PATCH] [gen] Added attribute 'xml' on every field allowing to customize the XML marshalling process. [gen] Added new layout 'xml', now different from the 'view' layout, allowing to define which fields are to be dumped in the XML version of some object. [gen] Security fix in ToolMixin::getUser. [gen] Bugfix in Mixin::getUrl. [shared] dav.py: method 'get' can now accept parameters. [shared] xml_parser: changes to the XmlMarshaller (due to XML-related changes). --- fields/__init__.py | 17 +++++++++++++++-- fields/action.py | 4 ++-- fields/boolean.py | 4 ++-- fields/calendar.py | 6 +++--- fields/computed.py | 24 +++++++++++++++--------- fields/date.py | 4 ++-- fields/file.py | 4 ++-- fields/float.py | 5 +++-- fields/info.py | 5 +++-- fields/integer.py | 4 ++-- fields/list.py | 5 +++-- fields/ogone.py | 4 ++-- fields/pod.py | 5 +++-- fields/ref.py | 10 ++++++++-- fields/string.py | 4 ++-- gen/mixins/ToolMixin.py | 13 ++++++++----- gen/mixins/__init__.py | 19 +++++++++++-------- shared/dav.py | 27 +++++++++++++++++++-------- shared/xml_parser.py | 35 ++++++++++++++--------------------- 19 files changed, 119 insertions(+), 80 deletions(-) diff --git a/fields/__init__.py b/fields/__init__.py index 880ec72..7c0a886 100644 --- a/fields/__init__.py +++ b/fields/__init__.py @@ -141,7 +141,7 @@ class Field: layouts, move, indexed, searchable, specificReadPermission, specificWritePermission, width, height, maxChars, colspan, master, masterValue, focus, historized, mapping, label, - sdefault, scolspan, swidth, sheight, persist): + sdefault, scolspan, swidth, sheight, persist, xml): # The validator restricts which values may be defined. It can be an # interval (1,None), a list of string values ['choice1', 'choice2'], # a regular expression, a custom function, a Selection instance, etc. @@ -250,6 +250,13 @@ class Field: # For some fields it is not wanted (ie, fields used only as masters to # update slave's selectable values). self.persist = persist + # Standard marshallers are provided for converting values of this field + # into XML. If you want to customize the marshalling process, you can + # define a method in "xml" that will accept a field value and will + # return a possibly different value. Be careful: do not return a chunk + # of XML here! Simply return an alternate value, that will be + # XML-marshalled. + self.xml = xml def init(self, name, klass, appName): '''When the application server starts, this secondary constructor is @@ -351,7 +358,7 @@ class Field: for r in res: if r == layoutType: return True return - elif res in ('view', 'edit', 'result', 'buttons'): + elif res in ('view', 'edit', 'result', 'buttons', 'xml'): return res == layoutType # For showing a field on layout "buttons", the "buttons" layout must # explicitly be returned by the show method. @@ -570,6 +577,12 @@ class Field: method in string.py).''' return self.getFormattedValue(obj, value, showChanges) + def getXmlValue(self, obj, value): + '''This method allows a developer to customize the value that will be + marshalled into XML. It makes use of attribute "xml".''' + if not self.xml: return value + return self.xml(obj, value) + def getIndexType(self): '''Returns the name of the technical, Zope-level index type for this field.''' diff --git a/fields/action.py b/fields/action.py index c3534c6..ff31b36 100644 --- a/fields/action.py +++ b/fields/action.py @@ -59,7 +59,7 @@ class Action(Field): width=None, height=None, maxChars=None, colspan=1, action=None, result='computation', confirm=False, master=None, masterValue=None, focus=False, historized=False, mapping=None, - label=None, icon=None): + label=None, icon=None, xml=None): # Can be a single method or a list/tuple of methods self.action = action # For the 'result' param: @@ -80,7 +80,7 @@ class Action(Field): move, indexed, False, specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, historized, mapping, label, - None, None, None, None, False) + None, None, None, None, False, xml) self.validable = False self.renderLabel = False # Label is rendered directly within the button diff --git a/fields/boolean.py b/fields/boolean.py index e6acf64..93cb66e 100644 --- a/fields/boolean.py +++ b/fields/boolean.py @@ -84,7 +84,7 @@ class Boolean(Field): maxChars=None, colspan=1, master=None, masterValue=None, focus=False, historized=False, mapping=None, label=None, sdefault=False, scolspan=1, swidth=None, sheight=None, - persist=True, render='checkbox'): + persist=True, render='checkbox', xml=None): # By default, a boolean is edited via a checkbox. It can also be edited # via 2 radio buttons (p_render="radios"). self.render = render @@ -93,7 +93,7 @@ class Boolean(Field): specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, historized, mapping, label, sdefault, scolspan, swidth, - sheight, persist) + sheight, persist, xml) self.pythonType = bool def getDefaultLayouts(self): diff --git a/fields/calendar.py b/fields/calendar.py index b6db5c3..13d7907 100644 --- a/fields/calendar.py +++ b/fields/calendar.py @@ -210,19 +210,19 @@ class Calendar(Field): pxEdit = pxSearch = '' def __init__(self, eventTypes, eventNameMethod=None, validator=None, - default=None, show='view', page='main', group=None, + default=None, show=('view', 'xml'), page='main', group=None, layouts=None, move=0, specificReadPermission=False, specificWritePermission=False, width=None, height=300, colspan=1, master=None, masterValue=None, focus=False, mapping=None, label=None, maxEventLength=50, otherCalendars=None, additionalInfo=None, startDate=None, endDate=None, defaultDate=None, preCompute=None, - applicableEvents=None): + applicableEvents=None, xml=None): Field.__init__(self, validator, (0,1), default, show, page, group, layouts, move, False, False, specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, False, mapping, label, None, - None, None, None, True) + None, None, None, True, xml) # eventTypes can be a "static" list or tuple of strings that identify # the types of events that are supported by this calendar. It can also # be a method that computes such a "dynamic" list or tuple. When diff --git a/fields/computed.py b/fields/computed.py index 6a33d29..3af4d96 100644 --- a/fields/computed.py +++ b/fields/computed.py @@ -31,14 +31,13 @@ class Computed(Field): value=":field.sdefault"/>''') def __init__(self, validator=None, multiplicity=(0,1), default=None, - show=('view', 'result'), page='main', group=None, - layouts=None, move=0, indexed=False, searchable=False, - specificReadPermission=False, specificWritePermission=False, - width=None, height=None, maxChars=None, colspan=1, method=None, - formatMethod=None, plainText=False, master=None, - masterValue=None, focus=False, historized=False, mapping=None, - label=None, sdefault='', scolspan=1, swidth=None, sheight=None, - context=None): + show=None, page='main', group=None, layouts=None, move=0, + indexed=False, searchable=False, specificReadPermission=False, + specificWritePermission=False, width=None, height=None, + maxChars=None, colspan=1, method=None, formatMethod=None, + plainText=False, master=None, masterValue=None, focus=False, + historized=False, mapping=None, label=None, sdefault='', + scolspan=1, swidth=None, sheight=None, context=None, xml=None): # The Python method used for computing the field value, or a PX. self.method = method # A specific method for producing the formatted value of this field. @@ -55,6 +54,13 @@ class Computed(Field): if isinstance(method, Px): # When field computation is done with a PX, the result is XHTML. self.plainText = False + # Determine default value for "show" + if show == None: + # XHTML content in a Computed field generally corresponds to some + # custom XHTML widget. This is why, by default, we do not render it + # in the xml layout. + show = self.plainText and ('view', 'result', 'xml') or \ + ('view', 'result') # If method is a PX, its context can be given in p_context. self.context = context Field.__init__(self, None, multiplicity, default, show, page, group, @@ -62,7 +68,7 @@ class Computed(Field): specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, historized, mapping, label, sdefault, scolspan, swidth, - sheight, False) + sheight, False, xml) self.validable = False def getValue(self, obj): diff --git a/fields/date.py b/fields/date.py index e2d8562..f9f3214 100644 --- a/fields/date.py +++ b/fields/date.py @@ -170,7 +170,7 @@ class Date(Field): maxChars=None, colspan=1, master=None, masterValue=None, focus=False, historized=False, mapping=None, label=None, sdefault=None, scolspan=1, swidth=None, sheight=None, - persist=True): + persist=True, xml=None): self.format = format self.calendar = calendar self.startYear = startYear @@ -183,7 +183,7 @@ class Date(Field): specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, historized, mapping, label, sdefault, scolspan, swidth, - sheight, persist) + sheight, persist, xml) def getCss(self, layoutType, res): # CSS files are only required if the calendar must be shown. diff --git a/fields/file.py b/fields/file.py index 761e666..a702266 100644 --- a/fields/file.py +++ b/fields/file.py @@ -324,14 +324,14 @@ class File(Field): maxChars=None, colspan=1, master=None, masterValue=None, focus=False, historized=False, mapping=None, label=None, isImage=False, sdefault='', scolspan=1, swidth=None, - sheight=None): + sheight=None, xml=None): self.isImage = isImage Field.__init__(self, validator, multiplicity, default, show, page, group, layouts, move, indexed, False, specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, historized, mapping, label, sdefault, scolspan, swidth, - sheight, True) + sheight, True, xml) def getRequestValue(self, obj, requestName=None): name = requestName or self.name diff --git a/fields/float.py b/fields/float.py index 9a6d774..0908851 100644 --- a/fields/float.py +++ b/fields/float.py @@ -56,7 +56,8 @@ class Float(Field): maxChars=13, colspan=1, master=None, masterValue=None, focus=False, historized=False, mapping=None, label=None, sdefault=('',''), scolspan=1, swidth=None, sheight=None, - persist=True, precision=None, sep=(',', '.'), tsep=' '): + persist=True, precision=None, sep=(',', '.'), tsep=' ', + xml=None): # The precision is the number of decimal digits. This number is used # for rendering the float, but the internal float representation is not # rounded. @@ -78,7 +79,7 @@ class Float(Field): specificReadPermission, specificWritePermission, width, height, maxChars, colspan, master, masterValue, focus, historized, mapping, label, sdefault, scolspan, swidth, - sheight, persist) + sheight, persist, xml) self.pythonType = float def getFormattedValue(self, obj, value, showChanges=False, language=None): diff --git a/fields/info.py b/fields/info.py index 68f4169..2684f47 100644 --- a/fields/info.py +++ b/fields/info.py @@ -29,11 +29,12 @@ class Info(Field): indexed=False, searchable=False, specificReadPermission=False, specificWritePermission=False, width=None, height=None, maxChars=None, colspan=1, master=None, masterValue=None, - focus=False, historized=False, mapping=None, label=None): + focus=False, historized=False, mapping=None, label=None, + xml=None): Field.__init__(self, None, (0,1), default, show, page, group, layouts, move, indexed, False, specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, historized, mapping, label, - None, None, None, None, False) + None, None, None, None, False, xml) self.validable = False # ------------------------------------------------------------------------------ diff --git a/fields/integer.py b/fields/integer.py index 57c28a4..a8e7945 100644 --- a/fields/integer.py +++ b/fields/integer.py @@ -53,13 +53,13 @@ class Integer(Field): maxChars=13, colspan=1, master=None, masterValue=None, focus=False, historized=False, mapping=None, label=None, sdefault=('',''), scolspan=1, swidth=None, sheight=None, - persist=True): + persist=True, xml=None): Field.__init__(self, validator, multiplicity, default, show, page, group, layouts, move, indexed, searchable, specificReadPermission, specificWritePermission, width, height, maxChars, colspan, master, masterValue, focus, historized, mapping, label, sdefault, scolspan, swidth, - sheight, persist) + sheight, persist, xml) self.pythonType = long def validateValue(self, obj, value): diff --git a/fields/list.py b/fields/list.py index b41ad94..d612f85 100644 --- a/fields/list.py +++ b/fields/list.py @@ -78,12 +78,13 @@ class List(Field): specificWritePermission=False, width='', height=None, maxChars=None, colspan=1, master=None, masterValue=None, focus=False, historized=False, mapping=None, label=None, - subLayouts=Table('frv', width=None), widths=None): + subLayouts=Table('frv', width=None), widths=None, xml=None): Field.__init__(self, validator, multiplicity, default, show, page, group, layouts, move, indexed, False, specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, - historized, mapping, label, None, None, None, None, True) + historized, mapping, label, None, None, None, None, + True, xml) self.validable = True # Tuples of (names, Field instances) determining the format of every # element in the list. diff --git a/fields/ogone.py b/fields/ogone.py index b49c44d..c337a09 100644 --- a/fields/ogone.py +++ b/fields/ogone.py @@ -49,12 +49,12 @@ class Ogone(Field): group=None, layouts=None, move=0, specificReadPermission=False, specificWritePermission=False, width=None, height=None, colspan=1, master=None, masterValue=None, focus=False, - mapping=None, label=None): + mapping=None, label=None, xml=None): Field.__init__(self, None, (0,1), None, show, page, group, layouts, move, False, False,specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, False, mapping, label, None, - None, None, None, False) + None, None, None, False, xml) # orderMethod must contain a method returning a dict containing info # about the order. Following keys are mandatory: # * orderID An identifier for the order. Don't use the object UID diff --git a/fields/pod.py b/fields/pod.py index 80a4e2e..a1dc465 100644 --- a/fields/pod.py +++ b/fields/pod.py @@ -158,7 +158,8 @@ class Pod(Field): template=None, templateName=None, showTemplate=None, freezeTemplate=None, maxPerRow=5, context=None, stylesMapping={}, formats=None, getChecked=None, mailing=None, - mailingName=None, showMailing=None, mailingInfo=None): + mailingName=None, showMailing=None, mailingInfo=None, + xml=None): # Param "template" stores the path to the pod template(s). If there is # a single template, a string is expected. Else, a list or tuple of # strings is expected. Every such path must be relative to your @@ -275,7 +276,7 @@ class Pod(Field): move, indexed, searchable, specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, historized, mapping, label, - None, None, None, None, True) + None, None, None, None, True, xml) # Param "persist" is set to True but actually, persistence for a pod # field is determined by freezing. self.validable = False diff --git a/fields/ref.py b/fields/ref.py index 99e09c5..9dd7ccc 100644 --- a/fields/ref.py +++ b/fields/ref.py @@ -528,7 +528,7 @@ class Ref(Field): checkboxes=True, checkboxesDefault=None, sdefault='', scolspan=1, swidth=None, sheight=None, sselect=None, persist=True, render='list', menuIdMethod=None, - menuInfoMethod=None, menuUrlMethod=None): + menuInfoMethod=None, menuUrlMethod=None, xml=None): self.klass = klass self.attribute = attribute # May the user add new objects through this ref ? "add" may also contain @@ -708,7 +708,7 @@ class Ref(Field): specificReadPermission, specificWritePermission, width, height, None, colspan, master, masterValue, focus, historized, mapping, label, sdefault, scolspan, swidth, - sheight, persist) + sheight, persist, xml) self.validable = bool(self.link) self.checkParameters() @@ -794,6 +794,12 @@ class Ref(Field): # Return a copy: it can be dangerous to give the real database value. if res: return list(res) + def getXmlValue(self, obj, value): + '''The default XML value for a Ref is the list of tied object URLs.''' + # Bypass the default behaviour if a custom method is given + if self.xml: return self.xml(obj, value) + return ['%s/xml' % tied.o.absolute_url() for tied in value] + def getPossibleValues(self, obj, startNumber=None, someObjects=False, removeLinked=False): '''This method returns the list of all objects that can be selected diff --git a/fields/string.py b/fields/string.py index b243e3f..a02f41d 100644 --- a/fields/string.py +++ b/fields/string.py @@ -381,7 +381,7 @@ class String(Field): persist=True, transform='none', placeholder=None, styles=('p','h1','h2','h3','h4'), allowImageUpload=True, spellcheck=False, languages=('en',), languagesLayouts=None, - inlineEdit=False): + inlineEdit=False, xml=None): # According to format, the widget will be different: input field, # textarea, inline editor... Note that there can be only one String # field of format CAPTCHA by page, because the captcha challenge is @@ -425,7 +425,7 @@ class String(Field): specificReadPermission, specificWritePermission, width, height, maxChars, colspan, master, masterValue, focus, historized, mapping, label, sdefault, scolspan, swidth, - sheight, persist) + sheight, persist, xml) self.isSelect = self.isSelection() # If self.isSelect, self.sdefault must be a list of value(s). if self.isSelect and not sdefault: diff --git a/gen/mixins/ToolMixin.py b/gen/mixins/ToolMixin.py index 1efe024..7fb8c0a 100644 --- a/gen/mixins/ToolMixin.py +++ b/gen/mixins/ToolMixin.py @@ -920,9 +920,9 @@ class ToolMixin(BaseMixin): # to authentify the user, we ask to identify a user or, if impossible, # a special user. login, password = self.identifyUser(alsoSpecial=not authentify) - # Stop here if no user was found and authentication was required. + # Stop here if no user was found and authentication was required if authentify and not login: return - # Now, get the User instance. + # Now, get the User instance if source == 'zodb': # Get the User object, but only if it is a true local user. user = tool.search1('User', noSecurity=True, login=login) @@ -933,11 +933,14 @@ class ToolMixin(BaseMixin): # Get the user object, be it really local or a copy of a LDAP user. user = tool.search1('User', noSecurity=True, login=login) if not user: return - # Authentify the user if required. + # Authentify the user if required if authentify: if (user.state == 'inactive') or (not user.checkPassword(password)): - # Disable the authentication cookie. + # Disable the authentication cookie and remove credentials + # stored on the request. req.RESPONSE.expireCookie('_appy_', path='/') + k = 'HTTP_AUTHORIZATION' + req._auth = req[k] = req._orig_env[k] = None return # Create an authentication cookie for this user. gutils.writeCookie(login, password, req) @@ -957,7 +960,7 @@ class ToolMixin(BaseMixin): if jsEnabled and not cookiesEnabled: msg = self.translate('enable_cookies') return self.goto(urlBack, msg) - # Authenticate the user. + # Authenticate the user if self.getUser(authentify=True) or \ self.getUser(authentify=True, source='ldap'): msg = self.translate('login_ok') diff --git a/gen/mixins/__init__.py b/gen/mixins/__init__.py index ca50162..416cbd5 100644 --- a/gen/mixins/__init__.py +++ b/gen/mixins/__init__.py @@ -530,7 +530,7 @@ class BaseMixin: res = XmlMarshaller().marshall(methodRes, objectType='appy') except Exception, e: tb = sutils.Traceback.get() - res = XmlMarshaller().marshall(tb, objectType='appy') + res = XmlMarshaller(rootTag='exception').marshall(tb) return res def say(self, msg, type='info'): @@ -1430,10 +1430,12 @@ class BaseMixin: return layoutType in showValue getUrlDefaults = {'page':True, 'nav':True} - def getUrl(self, base=None, mode='view', inPopup=False, **kwargs): + def getUrl(self, base=None, mode='view', inPopup=False, relative=False, + **kwargs): '''Returns an URL for this object. * If p_base is None, it will be the base URL for this object - (ie, Zope self.absolute_url()). + (ie, Zope self.absolute_url() or an URL this is relative to the + root site if p_relative is True). * p_mode can be "edit", "view" or "raw" (a non-param, base URL) * If p_inPopup is True, the link will be opened in the Appy iframe. An additional param "popup=1" will be added to URL params, in order @@ -1447,19 +1449,20 @@ class BaseMixin: # Define the URL suffix suffix = '' if mode != 'raw': suffix = '/%s' % mode - # Define base URL if omitted + # Define the base URL if omitted if not base: - base = self.absolute_url() + suffix + base = relative and self.absolute_url_path() or self.absolute_url() + base += suffix existingParams = '' else: existingParams = urllib.splitquery(base)[1] - # If a raw URL is asked, remove any param and suffix. + # If a raw URL is asked, remove any param and suffix if mode == 'raw': if '?' in base: base = base[:base.index('?')] - base = base.strip('/') + base = base.rstrip('/') for mode in ('view', 'edit'): if base.endswith(mode): - base = base[:-len(mode)].strip('/') + base = base[:-len(mode)].rstrip('/') break return base # Manage default args diff --git a/shared/dav.py b/shared/dav.py index 1c123e7..42e63f6 100644 --- a/shared/dav.py +++ b/shared/dav.py @@ -1,6 +1,6 @@ # ------------------------------------------------------------------------------ import os, re, httplib, sys, stat, urlparse, time, socket, xml.sax -from urllib import quote +import urllib from StringIO import StringIO from mimetypes import guess_type from base64 import encodestring @@ -19,7 +19,7 @@ class FormDataEncoder: def marshalValue(self, name, value): if isinstance(value, basestring): - return '%s=%s' % (name, quote(str(value))) + return '%s=%s' % (name, urllib.quote(str(value))) elif isinstance(value, float): return '%s:float=%s' % (name, value) elif isinstance(value, int): @@ -109,7 +109,13 @@ class HttpResponse: # Return an unmarshalled version of the XML content, for # easy use in Python. try: - return XmlUnmarshaller(utf8=self.utf8).parse(self.body) + parser = XmlUnmarshaller(utf8=self.utf8) + res = parser.parse(self.body) + if parser.rootTag == 'exception': + # This is an exception: "res" contains the traceback + raise ResourceError('Distant server exception: ' \ + '%s' % res) + return res except xml.sax.SAXParseException, se: raise ResourceError('Invalid XML response (%s)'%str(se)) @@ -153,10 +159,10 @@ class Resource: # Add credentials if present if not (self.username and self.password): return if headers.has_key('Authorization'): return - credentials = '%s:%s' % (self.username,self.password) - credentials = credentials.replace('\012','') + credentials = '%s:%s' % (self.username, self.password) + credentials = credentials.replace('\012', '') headers['Authorization'] = "Basic %s" % encodestring(credentials) - headers['User-Agent'] = 'WebDAV.client' + headers['User-Agent'] = 'Appy' headers['Host'] = self.host headers['Connection'] = 'close' headers['Accept'] = '*/*' @@ -241,9 +247,14 @@ class Resource: if type =='fileName': body.close() return res - def get(self, uri=None, headers={}): - '''Perform a HTTP GET on the server.''' + def get(self, uri=None, headers={}, params=None): + '''Perform a HTTP GET on the server. Parameters can be given as a dict + in p_params.''' if not uri: uri = self.uri + # Encode and append params if given + if params: + sep = ('?' in uri) and '&' or '?' + uri = '%s%s%s' % (uri, sep, urllib.urlencode(params)) return self.send('GET', uri, headers=headers) rss = get diff --git a/shared/xml_parser.py b/shared/xml_parser.py index d3dce91..12010a7 100644 --- a/shared/xml_parser.py +++ b/shared/xml_parser.py @@ -322,6 +322,8 @@ class XmlUnmarshaller(XmlParser): # knowing that the value is a 'string' is not sufficient). self.conversionFunctions = conversionFunctions self.utf8 = utf8 + # Remember the name of the root tag + self.rootTag = None def encode(self, value): '''Depending on self.utf8 we may need to encode p_value.''' @@ -354,6 +356,9 @@ class XmlUnmarshaller(XmlParser): previousElem = None if self.env.currentElem: previousElem = self.env.currentElem.name + else: + # We are walking the root tag + self.rootTag = elem e = XmlParser.startElement(self, elem, attrs) # Determine the type of the element. elemType = 'unicode' # Default value @@ -668,15 +673,11 @@ class XmlMarshaller: elif fieldType == 'dict': self.dumpDict(res, value) elif isRef: if value: - if self.objectType == 'appy': - suffix = '/xml' - else: - suffix = '' if type(value) in sequenceTypes: for elem in value: - self.dumpField(res, 'url', elem.absolute_url()+suffix) + self.dumpField(res, 'url', elem.absolute_url()) else: - self.dumpField(res, 'url', value.absolute_url()+suffix) + self.dumpField(res, 'url', value.absolute_url()) elif fieldType in ('list', 'tuple'): # The previous condition must be checked before this one because # referred objects may be stored in lists or tuples, too. @@ -814,27 +815,19 @@ class XmlMarshaller: self.dumpField(res, field.getName(),field.get(instance), fieldType=fieldType) elif objectType == 'appy': - for field in instance.getAppyTypes('view', None): + # Dump base attributes + for name in ('created', 'creator', 'modified'): + self.dumpField(res, name, getattr(instance, name)) + for field in instance.getAppyTypes('xml', None): # Dump only needed fields - if (field.type == 'Computed') and not field.plainText: - # Ignore fields used for producing custom chunks of HTML - # within the web UI. - continue if field.name in self.fieldsToExclude: continue if (type(self.fieldsToMarshall) in sequenceTypes) \ and (field.name not in self.fieldsToMarshall): continue # Determine field type and value - fieldType = 'basic' - if field.type == 'File': - fieldType = 'file' - v = field.getValue(instance) - elif field.type == 'Ref': - fieldType = 'ref' - v = field.getValue(instance, appy=False) - else: - v = field.getValue(instance) + fieldType = (field.type == 'File') and 'file' or 'basic' + v = field.getXmlValue(instance, field.getValue(instance)) self.dumpField(res, field.name, v, fieldType=fieldType) - # Dump the object history. + # Dump the object history if hasattr(instance.aq_base, 'workflow_history'): histTag = self.getTagName('history') eventTag = self.getTagName('event')