[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).

This commit is contained in:
Gaetan Delannay 2014-12-08 14:52:04 +01:00
parent f055ec1754
commit c53654a1a1
19 changed files with 119 additions and 80 deletions

View file

@ -141,7 +141,7 @@ class Field:
layouts, move, indexed, searchable, specificReadPermission, layouts, move, indexed, searchable, specificReadPermission,
specificWritePermission, width, height, maxChars, colspan, specificWritePermission, width, height, maxChars, colspan,
master, masterValue, focus, historized, mapping, label, 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 # The validator restricts which values may be defined. It can be an
# interval (1,None), a list of string values ['choice1', 'choice2'], # interval (1,None), a list of string values ['choice1', 'choice2'],
# a regular expression, a custom function, a Selection instance, etc. # 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 # For some fields it is not wanted (ie, fields used only as masters to
# update slave's selectable values). # update slave's selectable values).
self.persist = persist 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): def init(self, name, klass, appName):
'''When the application server starts, this secondary constructor is '''When the application server starts, this secondary constructor is
@ -351,7 +358,7 @@ class Field:
for r in res: for r in res:
if r == layoutType: return True if r == layoutType: return True
return return
elif res in ('view', 'edit', 'result', 'buttons'): elif res in ('view', 'edit', 'result', 'buttons', 'xml'):
return res == layoutType return res == layoutType
# For showing a field on layout "buttons", the "buttons" layout must # For showing a field on layout "buttons", the "buttons" layout must
# explicitly be returned by the show method. # explicitly be returned by the show method.
@ -570,6 +577,12 @@ class Field:
method in string.py).''' method in string.py).'''
return self.getFormattedValue(obj, value, showChanges) 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): def getIndexType(self):
'''Returns the name of the technical, Zope-level index type for this '''Returns the name of the technical, Zope-level index type for this
field.''' field.'''

View file

@ -59,7 +59,7 @@ class Action(Field):
width=None, height=None, maxChars=None, colspan=1, action=None, width=None, height=None, maxChars=None, colspan=1, action=None,
result='computation', confirm=False, master=None, result='computation', confirm=False, master=None,
masterValue=None, focus=False, historized=False, mapping=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 # Can be a single method or a list/tuple of methods
self.action = action self.action = action
# For the 'result' param: # For the 'result' param:
@ -80,7 +80,7 @@ class Action(Field):
move, indexed, False, specificReadPermission, move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, mapping, label, master, masterValue, focus, historized, mapping, label,
None, None, None, None, False) None, None, None, None, False, xml)
self.validable = False self.validable = False
self.renderLabel = False # Label is rendered directly within the button self.renderLabel = False # Label is rendered directly within the button

View file

@ -84,7 +84,7 @@ class Boolean(Field):
maxChars=None, colspan=1, master=None, masterValue=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,
sdefault=False, scolspan=1, swidth=None, sheight=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 # By default, a boolean is edited via a checkbox. It can also be edited
# via 2 radio buttons (p_render="radios"). # via 2 radio buttons (p_render="radios").
self.render = render self.render = render
@ -93,7 +93,7 @@ class Boolean(Field):
specificReadPermission, specificWritePermission, width, specificReadPermission, specificWritePermission, width,
height, None, colspan, master, masterValue, focus, height, None, colspan, master, masterValue, focus,
historized, mapping, label, sdefault, scolspan, swidth, historized, mapping, label, sdefault, scolspan, swidth,
sheight, persist) sheight, persist, xml)
self.pythonType = bool self.pythonType = bool
def getDefaultLayouts(self): def getDefaultLayouts(self):

View file

@ -210,19 +210,19 @@ class Calendar(Field):
pxEdit = pxSearch = '' pxEdit = pxSearch = ''
def __init__(self, eventTypes, eventNameMethod=None, validator=None, 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, layouts=None, move=0, specificReadPermission=False,
specificWritePermission=False, width=None, height=300, specificWritePermission=False, width=None, height=300,
colspan=1, master=None, masterValue=None, focus=False, colspan=1, master=None, masterValue=None, focus=False,
mapping=None, label=None, maxEventLength=50, mapping=None, label=None, maxEventLength=50,
otherCalendars=None, additionalInfo=None, startDate=None, otherCalendars=None, additionalInfo=None, startDate=None,
endDate=None, defaultDate=None, preCompute=None, endDate=None, defaultDate=None, preCompute=None,
applicableEvents=None): applicableEvents=None, xml=None):
Field.__init__(self, validator, (0,1), default, show, page, group, Field.__init__(self, validator, (0,1), default, show, page, group,
layouts, move, False, False, specificReadPermission, layouts, move, False, False, specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, False, mapping, label, None, 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 # 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 # 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 # be a method that computes such a "dynamic" list or tuple. When

View file

@ -31,14 +31,13 @@ class Computed(Field):
value=":field.sdefault"/>''') value=":field.sdefault"/>''')
def __init__(self, validator=None, multiplicity=(0,1), default=None, def __init__(self, validator=None, multiplicity=(0,1), default=None,
show=('view', 'result'), page='main', group=None, show=None, page='main', group=None, layouts=None, move=0,
layouts=None, move=0, indexed=False, searchable=False, indexed=False, searchable=False, specificReadPermission=False,
specificReadPermission=False, specificWritePermission=False, specificWritePermission=False, width=None, height=None,
width=None, height=None, maxChars=None, colspan=1, method=None, maxChars=None, colspan=1, method=None, formatMethod=None,
formatMethod=None, plainText=False, master=None, plainText=False, master=None, masterValue=None, focus=False,
masterValue=None, focus=False, historized=False, mapping=None, historized=False, mapping=None, label=None, sdefault='',
label=None, sdefault='', scolspan=1, swidth=None, sheight=None, scolspan=1, swidth=None, sheight=None, context=None, xml=None):
context=None):
# The Python method used for computing the field value, or a PX. # The Python method used for computing the field value, or a PX.
self.method = method self.method = method
# A specific method for producing the formatted value of this field. # A specific method for producing the formatted value of this field.
@ -55,6 +54,13 @@ class Computed(Field):
if isinstance(method, Px): if isinstance(method, Px):
# When field computation is done with a PX, the result is XHTML. # When field computation is done with a PX, the result is XHTML.
self.plainText = False 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. # If method is a PX, its context can be given in p_context.
self.context = context self.context = context
Field.__init__(self, None, multiplicity, default, show, page, group, Field.__init__(self, None, multiplicity, default, show, page, group,
@ -62,7 +68,7 @@ class Computed(Field):
specificReadPermission, specificWritePermission, width, specificReadPermission, specificWritePermission, width,
height, None, colspan, master, masterValue, focus, height, None, colspan, master, masterValue, focus,
historized, mapping, label, sdefault, scolspan, swidth, historized, mapping, label, sdefault, scolspan, swidth,
sheight, False) sheight, False, xml)
self.validable = False self.validable = False
def getValue(self, obj): def getValue(self, obj):

View file

@ -170,7 +170,7 @@ class Date(Field):
maxChars=None, colspan=1, master=None, masterValue=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,
sdefault=None, scolspan=1, swidth=None, sheight=None, sdefault=None, scolspan=1, swidth=None, sheight=None,
persist=True): persist=True, xml=None):
self.format = format self.format = format
self.calendar = calendar self.calendar = calendar
self.startYear = startYear self.startYear = startYear
@ -183,7 +183,7 @@ class Date(Field):
specificReadPermission, specificWritePermission, width, specificReadPermission, specificWritePermission, width,
height, None, colspan, master, masterValue, focus, height, None, colspan, master, masterValue, focus,
historized, mapping, label, sdefault, scolspan, swidth, historized, mapping, label, sdefault, scolspan, swidth,
sheight, persist) sheight, persist, xml)
def getCss(self, layoutType, res): def getCss(self, layoutType, res):
# CSS files are only required if the calendar must be shown. # CSS files are only required if the calendar must be shown.

View file

@ -324,14 +324,14 @@ class File(Field):
maxChars=None, colspan=1, master=None, masterValue=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,
isImage=False, sdefault='', scolspan=1, swidth=None, isImage=False, sdefault='', scolspan=1, swidth=None,
sheight=None): sheight=None, xml=None):
self.isImage = isImage self.isImage = isImage
Field.__init__(self, validator, multiplicity, default, show, page, Field.__init__(self, validator, multiplicity, default, show, page,
group, layouts, move, indexed, False, group, layouts, move, indexed, False,
specificReadPermission, specificWritePermission, width, specificReadPermission, specificWritePermission, width,
height, None, colspan, master, masterValue, focus, height, None, colspan, master, masterValue, focus,
historized, mapping, label, sdefault, scolspan, swidth, historized, mapping, label, sdefault, scolspan, swidth,
sheight, True) sheight, True, xml)
def getRequestValue(self, obj, requestName=None): def getRequestValue(self, obj, requestName=None):
name = requestName or self.name name = requestName or self.name

View file

@ -56,7 +56,8 @@ class Float(Field):
maxChars=13, colspan=1, master=None, masterValue=None, maxChars=13, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None, focus=False, historized=False, mapping=None, label=None,
sdefault=('',''), scolspan=1, swidth=None, sheight=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 # The precision is the number of decimal digits. This number is used
# for rendering the float, but the internal float representation is not # for rendering the float, but the internal float representation is not
# rounded. # rounded.
@ -78,7 +79,7 @@ class Float(Field):
specificReadPermission, specificWritePermission, width, specificReadPermission, specificWritePermission, width,
height, maxChars, colspan, master, masterValue, focus, height, maxChars, colspan, master, masterValue, focus,
historized, mapping, label, sdefault, scolspan, swidth, historized, mapping, label, sdefault, scolspan, swidth,
sheight, persist) sheight, persist, xml)
self.pythonType = float self.pythonType = float
def getFormattedValue(self, obj, value, showChanges=False, language=None): def getFormattedValue(self, obj, value, showChanges=False, language=None):

View file

@ -29,11 +29,12 @@ class Info(Field):
indexed=False, searchable=False, specificReadPermission=False, indexed=False, searchable=False, specificReadPermission=False,
specificWritePermission=False, width=None, height=None, specificWritePermission=False, width=None, height=None,
maxChars=None, colspan=1, master=None, masterValue=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, Field.__init__(self, None, (0,1), default, show, page, group, layouts,
move, indexed, False, specificReadPermission, move, indexed, False, specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, mapping, label, master, masterValue, focus, historized, mapping, label,
None, None, None, None, False) None, None, None, None, False, xml)
self.validable = False self.validable = False
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -53,13 +53,13 @@ class Integer(Field):
maxChars=13, colspan=1, master=None, masterValue=None, maxChars=13, colspan=1, master=None, masterValue=None,
focus=False, historized=False, mapping=None, label=None, focus=False, historized=False, mapping=None, label=None,
sdefault=('',''), scolspan=1, swidth=None, sheight=None, sdefault=('',''), scolspan=1, swidth=None, sheight=None,
persist=True): persist=True, xml=None):
Field.__init__(self, validator, multiplicity, default, show, page, Field.__init__(self, validator, multiplicity, default, show, page,
group, layouts, move, indexed, searchable, group, layouts, move, indexed, searchable,
specificReadPermission, specificWritePermission, width, specificReadPermission, specificWritePermission, width,
height, maxChars, colspan, master, masterValue, focus, height, maxChars, colspan, master, masterValue, focus,
historized, mapping, label, sdefault, scolspan, swidth, historized, mapping, label, sdefault, scolspan, swidth,
sheight, persist) sheight, persist, xml)
self.pythonType = long self.pythonType = long
def validateValue(self, obj, value): def validateValue(self, obj, value):

View file

@ -78,12 +78,13 @@ class List(Field):
specificWritePermission=False, width='', height=None, specificWritePermission=False, width='', height=None,
maxChars=None, colspan=1, master=None, masterValue=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,
subLayouts=Table('frv', width=None), widths=None): subLayouts=Table('frv', width=None), widths=None, xml=None):
Field.__init__(self, validator, multiplicity, default, show, page, Field.__init__(self, validator, multiplicity, default, show, page,
group, layouts, move, indexed, False, group, layouts, move, indexed, False,
specificReadPermission, specificWritePermission, width, specificReadPermission, specificWritePermission, width,
height, None, colspan, master, masterValue, focus, 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 self.validable = True
# Tuples of (names, Field instances) determining the format of every # Tuples of (names, Field instances) determining the format of every
# element in the list. # element in the list.

View file

@ -49,12 +49,12 @@ class Ogone(Field):
group=None, layouts=None, move=0, specificReadPermission=False, group=None, layouts=None, move=0, specificReadPermission=False,
specificWritePermission=False, width=None, height=None, specificWritePermission=False, width=None, height=None,
colspan=1, master=None, masterValue=None, focus=False, 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, Field.__init__(self, None, (0,1), None, show, page, group, layouts,
move, False, False,specificReadPermission, move, False, False,specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, False, mapping, label, None, 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 # orderMethod must contain a method returning a dict containing info
# about the order. Following keys are mandatory: # about the order. Following keys are mandatory:
# * orderID An identifier for the order. Don't use the object UID # * orderID An identifier for the order. Don't use the object UID

View file

@ -158,7 +158,8 @@ class Pod(Field):
template=None, templateName=None, showTemplate=None, template=None, templateName=None, showTemplate=None,
freezeTemplate=None, maxPerRow=5, context=None, freezeTemplate=None, maxPerRow=5, context=None,
stylesMapping={}, formats=None, getChecked=None, mailing=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 # 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 # a single template, a string is expected. Else, a list or tuple of
# strings is expected. Every such path must be relative to your # strings is expected. Every such path must be relative to your
@ -275,7 +276,7 @@ class Pod(Field):
move, indexed, searchable, specificReadPermission, move, indexed, searchable, specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
master, masterValue, focus, historized, mapping, label, 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 # Param "persist" is set to True but actually, persistence for a pod
# field is determined by freezing. # field is determined by freezing.
self.validable = False self.validable = False

View file

@ -528,7 +528,7 @@ class Ref(Field):
checkboxes=True, checkboxesDefault=None, sdefault='', checkboxes=True, checkboxesDefault=None, sdefault='',
scolspan=1, swidth=None, sheight=None, sselect=None, scolspan=1, swidth=None, sheight=None, sselect=None,
persist=True, render='list', menuIdMethod=None, persist=True, render='list', menuIdMethod=None,
menuInfoMethod=None, menuUrlMethod=None): menuInfoMethod=None, menuUrlMethod=None, xml=None):
self.klass = klass self.klass = klass
self.attribute = attribute self.attribute = attribute
# May the user add new objects through this ref ? "add" may also contain # May the user add new objects through this ref ? "add" may also contain
@ -708,7 +708,7 @@ class Ref(Field):
specificReadPermission, specificWritePermission, width, specificReadPermission, specificWritePermission, width,
height, None, colspan, master, masterValue, focus, height, None, colspan, master, masterValue, focus,
historized, mapping, label, sdefault, scolspan, swidth, historized, mapping, label, sdefault, scolspan, swidth,
sheight, persist) sheight, persist, xml)
self.validable = bool(self.link) self.validable = bool(self.link)
self.checkParameters() self.checkParameters()
@ -794,6 +794,12 @@ class Ref(Field):
# Return a copy: it can be dangerous to give the real database value. # Return a copy: it can be dangerous to give the real database value.
if res: return list(res) 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, def getPossibleValues(self, obj, startNumber=None, someObjects=False,
removeLinked=False): removeLinked=False):
'''This method returns the list of all objects that can be selected '''This method returns the list of all objects that can be selected

View file

@ -381,7 +381,7 @@ class String(Field):
persist=True, transform='none', placeholder=None, persist=True, transform='none', placeholder=None,
styles=('p','h1','h2','h3','h4'), allowImageUpload=True, styles=('p','h1','h2','h3','h4'), allowImageUpload=True,
spellcheck=False, languages=('en',), languagesLayouts=None, spellcheck=False, languages=('en',), languagesLayouts=None,
inlineEdit=False): inlineEdit=False, xml=None):
# According to format, the widget will be different: input field, # According to format, the widget will be different: input field,
# textarea, inline editor... Note that there can be only one String # textarea, inline editor... Note that there can be only one String
# field of format CAPTCHA by page, because the captcha challenge is # field of format CAPTCHA by page, because the captcha challenge is
@ -425,7 +425,7 @@ class String(Field):
specificReadPermission, specificWritePermission, width, specificReadPermission, specificWritePermission, width,
height, maxChars, colspan, master, masterValue, focus, height, maxChars, colspan, master, masterValue, focus,
historized, mapping, label, sdefault, scolspan, swidth, historized, mapping, label, sdefault, scolspan, swidth,
sheight, persist) sheight, persist, xml)
self.isSelect = self.isSelection() self.isSelect = self.isSelection()
# If self.isSelect, self.sdefault must be a list of value(s). # If self.isSelect, self.sdefault must be a list of value(s).
if self.isSelect and not sdefault: if self.isSelect and not sdefault:

View file

@ -920,9 +920,9 @@ class ToolMixin(BaseMixin):
# to authentify the user, we ask to identify a user or, if impossible, # to authentify the user, we ask to identify a user or, if impossible,
# a special user. # a special user.
login, password = self.identifyUser(alsoSpecial=not authentify) 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 if authentify and not login: return
# Now, get the User instance. # Now, get the User instance
if source == 'zodb': if source == 'zodb':
# Get the User object, but only if it is a true local user. # Get the User object, but only if it is a true local user.
user = tool.search1('User', noSecurity=True, login=login) 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. # Get the user object, be it really local or a copy of a LDAP user.
user = tool.search1('User', noSecurity=True, login=login) user = tool.search1('User', noSecurity=True, login=login)
if not user: return if not user: return
# Authentify the user if required. # Authentify the user if required
if authentify: if authentify:
if (user.state == 'inactive') or (not user.checkPassword(password)): 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='/') req.RESPONSE.expireCookie('_appy_', path='/')
k = 'HTTP_AUTHORIZATION'
req._auth = req[k] = req._orig_env[k] = None
return return
# Create an authentication cookie for this user. # Create an authentication cookie for this user.
gutils.writeCookie(login, password, req) gutils.writeCookie(login, password, req)
@ -957,7 +960,7 @@ class ToolMixin(BaseMixin):
if jsEnabled and not cookiesEnabled: if jsEnabled and not cookiesEnabled:
msg = self.translate('enable_cookies') msg = self.translate('enable_cookies')
return self.goto(urlBack, msg) return self.goto(urlBack, msg)
# Authenticate the user. # Authenticate the user
if self.getUser(authentify=True) or \ if self.getUser(authentify=True) or \
self.getUser(authentify=True, source='ldap'): self.getUser(authentify=True, source='ldap'):
msg = self.translate('login_ok') msg = self.translate('login_ok')

View file

@ -530,7 +530,7 @@ class BaseMixin:
res = XmlMarshaller().marshall(methodRes, objectType='appy') res = XmlMarshaller().marshall(methodRes, objectType='appy')
except Exception, e: except Exception, e:
tb = sutils.Traceback.get() tb = sutils.Traceback.get()
res = XmlMarshaller().marshall(tb, objectType='appy') res = XmlMarshaller(rootTag='exception').marshall(tb)
return res return res
def say(self, msg, type='info'): def say(self, msg, type='info'):
@ -1430,10 +1430,12 @@ class BaseMixin:
return layoutType in showValue return layoutType in showValue
getUrlDefaults = {'page':True, 'nav':True} 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. '''Returns an URL for this object.
* If p_base is None, it will be the base 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) * 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. * 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 An additional param "popup=1" will be added to URL params, in order
@ -1447,19 +1449,20 @@ class BaseMixin:
# Define the URL suffix # Define the URL suffix
suffix = '' suffix = ''
if mode != 'raw': suffix = '/%s' % mode if mode != 'raw': suffix = '/%s' % mode
# Define base URL if omitted # Define the base URL if omitted
if not base: if not base:
base = self.absolute_url() + suffix base = relative and self.absolute_url_path() or self.absolute_url()
base += suffix
existingParams = '' existingParams = ''
else: else:
existingParams = urllib.splitquery(base)[1] 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 mode == 'raw':
if '?' in base: base = base[:base.index('?')] if '?' in base: base = base[:base.index('?')]
base = base.strip('/') base = base.rstrip('/')
for mode in ('view', 'edit'): for mode in ('view', 'edit'):
if base.endswith(mode): if base.endswith(mode):
base = base[:-len(mode)].strip('/') base = base[:-len(mode)].rstrip('/')
break break
return base return base
# Manage default args # Manage default args

View file

@ -1,6 +1,6 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import os, re, httplib, sys, stat, urlparse, time, socket, xml.sax import os, re, httplib, sys, stat, urlparse, time, socket, xml.sax
from urllib import quote import urllib
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
@ -19,7 +19,7 @@ class FormDataEncoder:
def marshalValue(self, name, value): def marshalValue(self, name, value):
if isinstance(value, basestring): if isinstance(value, basestring):
return '%s=%s' % (name, quote(str(value))) return '%s=%s' % (name, urllib.quote(str(value)))
elif isinstance(value, float): elif isinstance(value, float):
return '%s:float=%s' % (name, value) return '%s:float=%s' % (name, value)
elif isinstance(value, int): elif isinstance(value, int):
@ -109,7 +109,13 @@ class HttpResponse:
# Return an unmarshalled version of the XML content, for # Return an unmarshalled version of the XML content, for
# easy use in Python. # easy use in Python.
try: 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: except xml.sax.SAXParseException, se:
raise ResourceError('Invalid XML response (%s)'%str(se)) raise ResourceError('Invalid XML response (%s)'%str(se))
@ -156,7 +162,7 @@ class Resource:
credentials = '%s:%s' % (self.username, self.password) credentials = '%s:%s' % (self.username, self.password)
credentials = credentials.replace('\012', '') credentials = credentials.replace('\012', '')
headers['Authorization'] = "Basic %s" % encodestring(credentials) headers['Authorization'] = "Basic %s" % encodestring(credentials)
headers['User-Agent'] = 'WebDAV.client' headers['User-Agent'] = 'Appy'
headers['Host'] = self.host headers['Host'] = self.host
headers['Connection'] = 'close' headers['Connection'] = 'close'
headers['Accept'] = '*/*' headers['Accept'] = '*/*'
@ -241,9 +247,14 @@ class Resource:
if type =='fileName': body.close() if type =='fileName': body.close()
return res return res
def get(self, uri=None, headers={}): def get(self, uri=None, headers={}, params=None):
'''Perform a HTTP GET on the server.''' '''Perform a HTTP GET on the server. Parameters can be given as a dict
in p_params.'''
if not uri: uri = self.uri 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) return self.send('GET', uri, headers=headers)
rss = get rss = get

View file

@ -322,6 +322,8 @@ class XmlUnmarshaller(XmlParser):
# knowing that the value is a 'string' is not sufficient). # knowing that the value is a 'string' is not sufficient).
self.conversionFunctions = conversionFunctions self.conversionFunctions = conversionFunctions
self.utf8 = utf8 self.utf8 = utf8
# Remember the name of the root tag
self.rootTag = None
def encode(self, value): def encode(self, value):
'''Depending on self.utf8 we may need to encode p_value.''' '''Depending on self.utf8 we may need to encode p_value.'''
@ -354,6 +356,9 @@ class XmlUnmarshaller(XmlParser):
previousElem = None previousElem = None
if self.env.currentElem: if self.env.currentElem:
previousElem = self.env.currentElem.name previousElem = self.env.currentElem.name
else:
# We are walking the root tag
self.rootTag = elem
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
@ -668,15 +673,11 @@ class XmlMarshaller:
elif fieldType == 'dict': self.dumpDict(res, value) elif fieldType == 'dict': self.dumpDict(res, value)
elif isRef: elif isRef:
if value: if value:
if self.objectType == 'appy':
suffix = '/xml'
else:
suffix = ''
if type(value) in sequenceTypes: if type(value) in sequenceTypes:
for elem in value: for elem in value:
self.dumpField(res, 'url', elem.absolute_url()+suffix) self.dumpField(res, 'url', elem.absolute_url())
else: else:
self.dumpField(res, 'url', value.absolute_url()+suffix) self.dumpField(res, 'url', value.absolute_url())
elif fieldType in ('list', 'tuple'): elif fieldType in ('list', 'tuple'):
# The previous condition must be checked before this one because # The previous condition must be checked before this one because
# referred objects may be stored in lists or tuples, too. # referred objects may be stored in lists or tuples, too.
@ -814,27 +815,19 @@ class XmlMarshaller:
self.dumpField(res, field.getName(),field.get(instance), self.dumpField(res, field.getName(),field.get(instance),
fieldType=fieldType) fieldType=fieldType)
elif objectType == 'appy': 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 # 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 field.name in self.fieldsToExclude: continue
if (type(self.fieldsToMarshall) in sequenceTypes) \ if (type(self.fieldsToMarshall) in sequenceTypes) \
and (field.name not in self.fieldsToMarshall): continue and (field.name not in self.fieldsToMarshall): continue
# Determine field type and value # Determine field type and value
fieldType = 'basic' fieldType = (field.type == 'File') and 'file' or 'basic'
if field.type == 'File': v = field.getXmlValue(instance, field.getValue(instance))
fieldType = 'file'
v = field.getValue(instance)
elif field.type == 'Ref':
fieldType = 'ref'
v = field.getValue(instance, appy=False)
else:
v = field.getValue(instance)
self.dumpField(res, field.name, v, fieldType=fieldType) self.dumpField(res, field.name, v, fieldType=fieldType)
# Dump the object history. # Dump the object history
if hasattr(instance.aq_base, 'workflow_history'): if hasattr(instance.aq_base, 'workflow_history'):
histTag = self.getTagName('history') histTag = self.getTagName('history')
eventTag = self.getTagName('event') eventTag = self.getTagName('event')