Improved management of indexes; always provide str and not unicode strings as index values; search screen may now include javascripts and css like those required by the date chooser; removed CSS parser, basic XmlUnmarshaller can do it by itself.

This commit is contained in:
Gaetan Delannay 2010-12-17 14:46:55 +01:00
parent 2124cffa5e
commit a30949a621
15 changed files with 213 additions and 132 deletions

View file

@ -688,7 +688,13 @@ class Type:
if not self.editDefault:
# Return self.default, of self.default() if it is a method
if type(self.default) == types.FunctionType:
return self.default(obj.appy())
appyObj = obj.appy()
try:
return self.default(appyObj)
except Exception, e:
appyObj.log('Exception while getting default value ' \
'of field "%s": %s.' % (self.name, str(e)))
return None
else:
return self.default
# If value is editable, get the default value from the tool
@ -704,6 +710,11 @@ class Type:
if self.isEmptyValue(value): return ''
return value
def getIndexType(self):
'''Returns the name of the technical, Zope-level index type for this
field.'''
return 'FieldIndex'
def getIndexValue(self, obj, forSearch=False):
'''This method returns a version for this field value on p_obj that is
ready for indexing purposes. Needs to be overridden by some child
@ -1130,6 +1141,13 @@ class String(Type):
res = str(value)
return res
def getIndexValue(self, obj, forSearch=False):
'''For indexing purposes, we return only strings, not unicodes.'''
res = Type.getIndexValue(self, obj, forSearch)
if isinstance(res, unicode):
res = res.encode('utf-8')
return res
def getPossibleValues(self,obj,withTranslations=False,withBlankValue=False):
'''Returns the list of possible values for this field if it is a
selection field. If p_withTranslations is True,
@ -1236,6 +1254,14 @@ class String(Type):
value = [value]
exec 'obj.%s = value' % self.name
def getIndexType(self):
'''Index type varies depending on String parameters.'''
# If String.isSelect, be it multivalued or not, we define a ZCTextIndex:
# this way we can use AND/OR operator.
if self.isSelect or (self.format in (String.TEXT, String.XHTML)):
return 'ZCTextIndex'
return Type.getIndexType(self)
class Boolean(Type):
def __init__(self, validator=None, multiplicity=(0,1), index=None,
default=None, optional=False, editDefault=False, show=True,
@ -1291,10 +1317,11 @@ class Date(Type):
masterValue, focus, historized, True)
def getCss(self, layoutType):
if layoutType == 'edit': return ('jscalendar/calendar-system.css',)
if (layoutType == 'edit') and self.calendar:
return ('jscalendar/calendar-system.css',)
def getJs(self, layoutType):
if layoutType == 'edit':
if (layoutType == 'edit') and self.calendar:
return ('jscalendar/calendar_stripped.js',
'jscalendar/calendar-en.js')

View file

@ -247,6 +247,8 @@ class PloneInstaller:
appyType.template)
if os.path.exists(fileName):
setattr(self.appyTool, attrName, fileName)
self.appyTool.log('Imported "%s" in the tool in ' \
'attribute "%s"'% (fileName,attrName))
else:
self.appyTool.log('Template "%s" was not found!' % \
fileName, type='error')
@ -353,7 +355,7 @@ class PloneInstaller:
workflow = wfMethod(self, workflowName)
wfTool._setObject(workflowName, workflow)
else:
self.log('%s already in workflows.' % workflowName)
self.appyTool.log('%s already in workflows.' % workflowName)
# Link the workflow to the current content type
wfTool.setChainForPortalTypes([contentType], workflowName)
return wfTool
@ -404,14 +406,10 @@ class PloneInstaller:
indexInfo = {}
for className, appyTypes in self.attributes.iteritems():
for appyType in appyTypes:
if appyType.indexed:
if not appyType.indexed or (appyType.name == 'title'): continue
n = appyType.name
indexName = 'get%s%s' % (n[0].upper(), n[1:])
indexType = 'FieldIndex'
if (appyType.type == 'String') and appyType.isSelect and \
appyType.isMultiValued():
indexType = 'ZCTextIndex'
indexInfo[indexName] = indexType
indexInfo[indexName] = appyType.getIndexType()
if indexInfo:
PloneInstaller.updateIndexes(self.ploneSite, indexInfo, self)
@ -454,11 +452,9 @@ class PloneInstaller:
frontPageName = self.productName + 'FrontPage'
site.manage_changeProperties(default_page=frontPageName)
def log(self, msg): print msg
def info(self, msg): return self.log(msg)
def info(self, msg): return self.appyTool.log(msg)
def install(self):
self.log("Installation of %s:" % self.productName)
if self.minimalistPlone: self.customizePlone()
self.installRootFolder()
self.installTypes()
@ -470,7 +466,7 @@ class PloneInstaller:
self.manageIndexes()
self.manageLanguages()
self.finalizeInstallation()
self.log("Installation of %s done." % self.productName)
self.appyTool.log("Installation of %s done." % self.productName)
def uninstallTool(self):
site = self.ploneSite
@ -496,10 +492,8 @@ class PloneInstaller:
nvProps.manage_changeProperties(**{'idsNotToList': current})
def uninstall(self):
self.log("Uninstallation of %s:" % self.productName)
self.uninstallTool()
self.log("Uninstallation of %s done." % self.productName)
return self.toLog.getvalue()
return 'Done.'
# Stuff for tracking user activity ---------------------------------------------
loggedUsers = {}

View file

@ -165,10 +165,12 @@ class ToolMixin(BaseMixin):
return res
def getSearchInfo(self, contentType, refInfo=None):
'''Returns a tuple:
1) The list of searchable fields (ie fields among all indexed fields)
2) The number of columns for layouting those fields.'''
'''Returns, as a dict:
- the list of searchable fields (= some fields among all indexed
fields);
- the number of columns for layouting those fields.'''
fields = []
fieldDicts = []
if refInfo:
# The search is triggered from a Ref field.
refField = self.getRefInfo(refInfo)[1]
@ -180,9 +182,12 @@ class ToolMixin(BaseMixin):
fieldNames = getattr(at, 'searchFieldsFor%s' % contentType,())
nbOfColumns = getattr(at, 'numberOfSearchColumnsFor%s' %contentType)
for name in fieldNames:
appyType = self.getAppyType(name, asDict=True,className=contentType)
appyType = self.getAppyType(name,asDict=False,className=contentType)
appyDict = self.getAppyType(name, asDict=True,className=contentType)
fields.append(appyType)
return fields, nbOfColumns
fieldDicts.append(appyDict)
return {'fields': fields, 'nbOfColumns': nbOfColumns,
'fieldDicts': fieldDicts}
def getImportElements(self, contentType):
'''Returns the list of elements that can be imported from p_path for
@ -336,13 +341,12 @@ class ToolMixin(BaseMixin):
if not searchName:
# It is the global search for all objects pf p_contentType
searchName = contentType
s = self.REQUEST.SESSION
uids = {}
i = -1
for obj in res.objects:
i += 1
uids[startNumber+i] = obj.UID()
s['search_%s' % searchName] = uids
self.REQUEST.SESSION['search_%s' % searchName] = uids
return res.__dict__
def getResultColumnsNames(self, contentType, refField):
@ -452,15 +456,6 @@ class ToolMixin(BaseMixin):
return pythonClass.maySearch(self.appy())
return True
def userMayNavigate(self, obj):
'''This method checks if the currently logged user can display the
navigation panel within the portlet. This is done by calling method
"mayNavigate" on the currently shown object. If no such method
exists, we return True.'''
appyObj = obj.appy()
if hasattr(appyObj, 'mayNavigate'): return appyObj.mayNavigate()
return True
def onImportObjects(self):
'''This method is called when the user wants to create objects from
external data.'''
@ -614,7 +609,7 @@ class ToolMixin(BaseMixin):
field, this method returns information about this reference: the
source content type and the Ref field (Appy type). If p_refInfo is
not given, we search it among search criteria in the session.'''
if not refInfo:
if not refInfo and (self.REQUEST.get('search', None) == '_advanced'):
criteria = self.REQUEST.SESSION.get('searchCriteria', None)
if criteria and criteria.has_key('_ref'): refInfo = criteria['_ref']
if not refInfo: return ('', None)
@ -847,6 +842,8 @@ class ToolMixin(BaseMixin):
session.invalidate()
from Products.CMFPlone import transaction_note
transaction_note('Logged out')
self.getProductConfig().logger.info('User "%s" has been logged out.' % \
userId)
# Remove user from variable "loggedUsers"
from appy.gen.plone25.installer import loggedUsers
if loggedUsers.has_key(userId): del loggedUsers[userId]

View file

@ -276,15 +276,21 @@ class BaseMixin:
res[appyType.name] = appyType.getValue(self)
return res
def addDataChange(self, changes):
def addDataChange(self, changes, notForPreviouslyEmptyValues=False):
'''This method allows to add "manually" a data change into the objet's
history. Indeed, data changes are "automatically" recorded only when
a HTTP form is uploaded, not if, in the code, a setter is called on
a field. The method is also called by the method historizeData below,
that performs "automatic" recording when a HTTP form is uploaded.'''
a field. The method is also called by m_historizeData below, that
performs "automatic" recording when a HTTP form is uploaded. Field
changes for which the previous value was empty are not recorded into
the history if p_notForPreviouslyEmptyValues is True.'''
# Add to the p_changes dict the field labels
for fieldName in changes.iterkeys():
for fieldName in changes.keys():
appyType = self.getAppyType(fieldName)
if notForPreviouslyEmptyValues and \
appyType.isEmptyValue(changes[fieldName], self):
del changes[fieldName]
else:
changes[fieldName] = (changes[fieldName], appyType.labelId)
# Create the event to record in the history
DateTime = self.getProductConfig().DateTime
@ -474,21 +480,23 @@ class BaseMixin:
res.append(appyType)
return res
def getCssAndJs(self, layoutType, page):
'''Returns the CSS and Javascript files that need to be loaded by the
p_page for the given p_layoutType.'''
def getCssAndJs(self, fields, layoutType):
'''Gets the list of Javascript and CSS files required by Appy types
p_fields when shown on p_layoutType.'''
# lists css and js below are not sets, because order of Javascript
# inclusion can be important, and this could be losed by using sets.
css = []
js = []
for appyType in self.getAppyTypes(layoutType, page):
typeCss = appyType.getCss(layoutType)
if typeCss:
for tcss in typeCss:
if tcss not in css: css.append(tcss)
typeJs = appyType.getJs(layoutType)
if typeJs:
for tjs in typeJs:
if tjs not in js: js.append(tjs)
return css, js
for field in fields:
fieldCss = field.getCss(layoutType)
if fieldCss:
for fcss in fieldCss:
if fcss not in css: css.append(fcss)
fieldJs = field.getJs(layoutType)
if fieldJs:
for fjs in fieldJs:
if fjs not in js: js.append(fjs)
return {'css':css, 'js':js}
def getAppyTypesFromNames(self, fieldNames, asDict=True, addTitle=True):
'''Gets the Appy types named p_fieldNames. If 'title' is not among
@ -766,12 +774,27 @@ class BaseMixin:
res = True
return res
def mayNavigate(self):
'''May the currently logged user see the navigation panel linked to
this object?'''
appyObj = self.appy()
if hasattr(appyObj, 'mayNavigate'): return appyObj.mayNavigate()
return True
def mayDelete(self):
'''May the currently logged user delete this object? This condition
comes as an addition/refinement to the corresponding workflow
permission.'''
appyObj = self.appy()
if hasattr(appyObj, 'mayDelete'): return appyObj.mayDelete()
return True
def executeAppyAction(self, actionName, reindex=True):
'''Executes action with p_fieldName on this object.'''
appyType = self.getAppyType(actionName)
actionRes = appyType(self.appy())
if self.getParentNode().get(self.id):
# Else, it means that the action has led to self's removal.
# Else, it means that the action has led to self's deletion.
self.reindexObject()
return appyType.result, actionRes
@ -869,7 +892,17 @@ class BaseMixin:
blank value is prepended to the list. If no p_className is defined,
the field is supposed to belong to self's class'''
appyType = self.getAppyType(name, className=className)
return appyType.getPossibleValues(self,withTranslations,withBlankValue)
if className:
# We need an instance of className, but self can be an instance of
# another class. So here we will search such an instance.
brains = self.executeQuery(className, maxResults=1, brainsOnly=True)
if brains:
obj = brains[0].getObject()
else:
obj = self
else:
obj = self
return appyType.getPossibleValues(obj, withTranslations, withBlankValue)
def appy(self):
'''Returns a wrapper object allowing to manipulate p_self the Appy

View file

@ -84,7 +84,7 @@ class User(ModelClass):
validator=validatePassword, **gm)
password2 = String(format=String.PASSWORD, show=showPassword, **gm)
gm['multiplicity'] = (0, None)
roles = String(validator=Selection('getGrantableRoles'), **gm)
roles = String(validator=Selection('getGrantableRoles'), indexed=True, **gm)
# The Tool class ---------------------------------------------------------------

View file

@ -6,12 +6,10 @@
tool contextObj/getTool;
appFolder tool/getAppFolder;
appName appFolder/getId;
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='edit');
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType=layoutType);
phase phaseInfo/name;
page request/page|python:'main';
cssAndJs python: contextObj.getCssAndJs(layoutType, page);
css python: cssAndJs[0];
js python: cssAndJs[1];
cssJs python: contextObj.getCssAndJs(contextObj.getAppyTypes(layoutType, page), layoutType);
confirmMsg request/confirmMsg | nothing;">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
@ -28,7 +26,7 @@
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
<metal:js fill-slot="javascript_head_slot">
<tal:js condition="js" repeat="jsFile js">
<tal:js condition="cssJs/js" repeat="jsFile cssJs/js">
<script type="text/javascript" charset="iso-8859-1"
tal:condition="python:exists('portal/%s' % jsFile)"
tal:attributes="src string:$portal_url/$jsFile">
@ -36,7 +34,7 @@
</tal:js>
</metal:js>
<metal:css fill-slot="css_slot">
<tal:css condition="css" repeat="cssFile css">
<tal:css condition="cssJs/css" repeat="cssFile cssJs/css">
<style type="text/css" media="all"
tal:condition="python:exists('portal/%s' % cssFile)"
tal:content="structure string:<!-- @import url($portal_url/$cssFile); -->">

View file

@ -114,7 +114,7 @@
</a></td>
<tal:comment replace="nothing">Delete the element</tal:comment>
<td class="noPadding">
<img tal:condition="python: member.has_permission('Delete objects', obj)"
<img tal:condition="python: member.has_permission('Delete objects', obj) and obj.mayDelete()"
title="Delete" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
tal:attributes="src string: $portal_url/skyn/delete.png;
onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/>

View file

@ -28,7 +28,7 @@
</td>
</dt>
<tal:publishedObject condition="python: contextObj and tool.userMayNavigate(contextObj)">
<tal:publishedObject condition="python: contextObj and contextObj.mayNavigate()">
<dt class="portletAppyItem portletCurrent"><b tal:content="contextObj/Title"></b></dt>
<dt class="portletAppyItem"><metal:phases use-macro="here/skyn/portlet/macros/phases"/></dt>
</tal:publishedObject>

View file

@ -16,7 +16,7 @@
appName appFolder/id;
tool python: portal.get('portal_%s' % appName.lower());
contentType request/type_name;
searchName request/search|python:'';">
searchName request/search|python:''">
<div metal:use-macro="here/skyn/page/macros/prologue"/>
<tal:comment replace="nothing">Query result</tal:comment>

View file

@ -1,3 +1,11 @@
<tal:search metal:define-macro="master"
define="appFolder context/getParentNode;
contentType request/type_name;
refInfo request/ref|nothing;
tool python: here.portal_url.getPortalObject().get('portal_%s' % appFolder.id.lower());
searchInfo python: tool.getSearchInfo(contentType, refInfo);
cssJs python: tool.getCssAndJs(searchInfo['fields'], 'edit')">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
@ -10,16 +18,26 @@
<div tal:define="dummy python:request.set('disable_border', 1)" />
</div>
<tal:comment replace="nothing">Fill main slot of Plone main_template</tal:comment>
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
<metal:js fill-slot="javascript_head_slot">
<tal:js condition="cssJs/js" repeat="jsFile cssJs/js">
<script type="text/javascript" charset="iso-8859-1"
tal:condition="python:exists('portal/%s' % jsFile)"
tal:attributes="src string:$portal_url/$jsFile">
</script>
</tal:js>
</metal:js>
<metal:css fill-slot="css_slot">
<tal:css condition="cssJs/css" repeat="cssFile cssJs/css">
<style type="text/css" media="all"
tal:condition="python:exists('portal/%s' % cssFile)"
tal:content="structure string:<!-- @import url($portal_url/$cssFile); -->">
</style>
</tal:css>
</metal:css>
<body>
<metal:fill fill-slot="main"
tal:define="appFolder context/getParentNode;
contentType request/type_name;
refInfo request/ref|nothing;
tool python: portal.get('portal_%s' % appFolder.id.lower());
searchInfo python: tool.getSearchInfo(contentType, refInfo);
searchableFields python: searchInfo[0];
numberOfColumns python: searchInfo[1]">
<metal:fill fill-slot="main">
<tal:comment replace="nothing">Search title</tal:comment>
<h1><span tal:replace="python: tool.translate('%s_plural' % contentType)"/> —
@ -32,8 +50,9 @@
<input tal:condition="refInfo" type="hidden" name="ref" tal:attributes="value refInfo"/>
<table class="no-style-table" cellpadding="0" cellspacing="0" width="100%">
<tr tal:repeat="searchRow python: tool.tabularize(searchableFields, numberOfColumns)" valign="top">
<td tal:repeat="widget searchRow" tal:attributes="width python:'%d%%' % (100/numberOfColumns)">
<tr tal:repeat="searchRow python: tool.tabularize(searchInfo['fieldDicts'], searchInfo['nbOfColumns'])"
valign="top">
<td tal:repeat="widget searchRow" tal:attributes="width python:'%d%%' % (100/searchInfo['nbOfColumns'])">
<tal:field condition="widget">
<tal:show define="name widget/name;
widgetName python: 'w_%s' % name;
@ -53,3 +72,4 @@
</metal:fill>
</body>
</html>
</tal:search>

View file

@ -78,55 +78,71 @@
</metal:cell>
<tal:comment replace="nothing">Search macro for an Date.</tal:comment>
<metal:search define-macro="search">
<metal:search define-macro="search"
tal:define="years python:range(widget['startYear'], widget['endYear']+1)">
<label tal:content="python: tool.translate(widget['labelId'])"></label>
<table cellpadding="0" cellspacing="0">
<tal:comment replace="nothing">From</tal:comment>
<tr tal:define="fromName python: '%s*date' % widgetName">
<tr tal:define="yearFromName python: '%s*date' % widgetName;
monthFromName python: '%s_from_month' % name;
dayFromName python: '%s_from_day' % name;
dummyFromName python: '_d_ummy_from_%s' % name;">
<td width="10px">&nbsp;</td>
<td>
<label tal:content="python: tool.translate('search_from')"></label>
</td>
<td>
<select tal:attributes="name fromName">
<input type="hidden" tal:attributes="id dummyFromName; name dummyFromName" originalvalue=""/>
<select tal:attributes="id dayFromName; name dayFromName">
<option value="">--</option>
<option tal:repeat="value python:range(widget['startYear'], widget['endYear']+1)"
<option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 32)]"
tal:content="value" tal:attributes="value value"></option>
</select> /
<select tal:attributes="name python: '%s_from_month' % name">
<select tal:attributes="id monthFromName; name monthFromName">
<option value="">--</option>
<option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 13)]"
tal:content="python:tool.getMonthName(value)" tal:attributes="value value"></option>
</select> /
<select tal:attributes="name python: '%s_from_day' % name">
<select tal:attributes="id yearFromName; name yearFromName">
<option value="">--</option>
<option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 32)]"
<option tal:repeat="value python:range(widget['startYear'], widget['endYear']+1)"
tal:content="value" tal:attributes="value value"></option>
</select>
<tal:comment replace="nothing">The icon for displaying the calendar (=date chooser)</tal:comment>
<a tal:condition="widget/calendar"
tal:attributes="onclick python: 'return showJsCalendar(\'%s\', \'%s\', \'%s\', \'%s\', \'%s\', null, null, %d, %d)' % (monthFromName, dummyFromName, yearFromName, monthFromName, dayFromName, years[0], years[-1])">
<img tal:attributes="src string: $portal_url/popup_calendar.gif"/></a>
</td>
</tr>
<tal:comment replace="nothing">To</tal:comment>
<tr>
<tr tal:define="dummyToName python: '_d_ummy_to_%s' % name;
yearToName python: '%s_to_year' % name;
monthToName python: '%s_to_month' % name;
dayToName python: '%s_to_day' % name">
<td></td>
<td>
<label tal:content="python: tool.translate('search_to')"></label>
<label tal:content="python: tool.translate('search_to')"></label>&nbsp;&nbsp;&nbsp;&nbsp;
</td>
<td>
<select tal:attributes="name python: '%s_to_year' % name">
<td height="20px;">
<input type="hidden" tal:attributes="id dummyToName; name dummyToName" originalvalue=""/>
<select tal:attributes="id dayToName; name dayToName">
<option value="">--</option>
<option tal:repeat="value python:range(widget['startYear'], widget['endYear']+1)"
<option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 32)]"
tal:content="value" tal:attributes="value value"></option>
</select> /
<select tal:attributes="name python: '%s_to_month' % name">
<select tal:attributes="id monthToName; name monthToName">
<option value="">--</option>
<option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 13)]"
tal:content="python:tool.getMonthName(value)" tal:attributes="value value"></option>
</select> /
<select tal:attributes="name python: '%s_to_day' % name">
<select tal:attributes="id yearToName; name yearToName">
<option value="">--</option>
<option tal:repeat="value python: [str(v).zfill(2) for v in range(1, 32)]"
<option tal:repeat="value python:range(widget['startYear'], widget['endYear']+1)"
tal:content="value" tal:attributes="value value"></option>
</select>
<a tal:condition="widget/calendar"
tal:attributes="onclick python: 'return showJsCalendar(\'%s\', \'%s\', \'%s\', \'%s\', \'%s\', null, null, %d, %d)' % (monthToName, dummyToName, yearToName, monthToName, dayToName, years[0], years[-1])">
<img tal:attributes="src string: $portal_url/popup_calendar.gif"/></a>
</td>
</tr>
</table>

View file

@ -22,7 +22,7 @@
<table class="no-style-table" cellpadding="0" cellspacing="0">
<tr>
<tal:comment replace="nothing">Arrows for moving objects up or down</tal:comment>
<td class="noPadding" tal:condition="python: (len(objs)&gt;1) and member.has_permission('Modify portal content', contextObj)">
<td class="noPadding" tal:condition="python: not appyType['isBack'] and (len(objs)&gt;1) and member.has_permission('Modify portal content', contextObj)">
<tal:moveRef define="objectIndex python: contextObj.getAppyRefIndex(fieldName, obj);
ajaxBaseCall python: navBaseCall.replace('**v**', '\'%s\',\'ChangeRefOrder\', {\'refObjectUid\':\'%s\', \'move\':\'**v**\'}' % (startNumber, obj.UID()))">
<tal:comment replace="nothing">Move up</tal:comment>
@ -49,7 +49,7 @@
</td>
<tal:comment replace="nothing">Delete the element</tal:comment>
<td class="noPadding">
<img tal:condition="python: member.has_permission('Delete objects', obj)"
<img tal:condition="python: not appyType['isBack'] and member.has_permission('Delete objects', obj) and obj.mayDelete()"
title="Delete" i18n:domain="plone" i18n:attributes="title" style="cursor:pointer"
tal:attributes="src string: $portal_url/skyn/delete.png;
onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/>
@ -179,19 +179,10 @@
tal:attributes="class python:test(innerRef, 'innerAppyTable', '')">
<tr valign="bottom"><td>
<tal:comment replace="nothing">Show backward reference(s)</tal:comment>
<table class="no-style-table" cellspacing="0" cellpadding="0"
tal:condition="python: appyType['isBack'] and objs">
<tr tal:repeat="obj objs">
<td><metal:showObjectTitle use-macro="portal/skyn/widgets/ref/macros/objectTitle" />
</td>
</tr>
</table>
<tal:comment replace="nothing">Show forward reference(s)</tal:comment>
<tal:comment replace="nothing">Show forward or backward reference(s)</tal:comment>
<table tal:attributes="class python:test(innerRef, '', 'listing nosort');
width python:test(innerRef, '100%', appyType['layouts']['view']['width']);"
align="right" tal:condition="python: not appyType['isBack'] and objs" cellpadding="0" cellspacing="0">
align="right" tal:condition="python: objs" cellpadding="0" cellspacing="0">
<tal:widgets define="widgets python: objs[0].getAppyTypesFromNames(appyType['shownInfo'])">
<tr tal:condition="appyType/showHeaders">
<th tal:repeat="widget widgets">

View file

@ -101,7 +101,7 @@
<label tal:attributes="for andName" tal:content="python: tool.translate('search_and')"></label><br/>
</tal:operator>
<tal:comment replace="nothing">The list of values</tal:comment>
<select tal:attributes="name widgetName" multiple="multiple" size="5">
<select tal:attributes="name widgetName; size widget/height" multiple="multiple">
<option tal:repeat="v python:tool.getPossibleValues(name, withTranslations=True, withBlankValue=False, className=contentType)"
tal:attributes="value python:v[0]; title python: v[1]"
tal:content="python: tool.truncateValue(v[1], widget)">

View file

@ -1,5 +1,5 @@
# ------------------------------------------------------------------------------
import os, re, httplib, sys, stat, urlparse, time
import os, re, httplib, sys, stat, urlparse, time, socket
from urllib import quote
from StringIO import StringIO
from mimetypes import guess_type
@ -9,6 +9,9 @@ from appy.shared.utils import copyData
from appy.gen.utils import sequenceTypes
from appy.shared.xml_parser import XmlUnmarshaller, XmlMarshaller
# ------------------------------------------------------------------------------
class ResourceError(Exception): pass
# ------------------------------------------------------------------------------
class FormDataEncoder:
'''Allows to encode form data for sending it through a HTTP request.'''
@ -84,6 +87,7 @@ class HttpResponse:
if self.duration: duration = ', got in %.4f seconds' % self.duration
return '<HttpResponse %s (%s)%s>' % (self.code, self.text, duration)
xmlHeaders = ('text/xml', 'application/xml')
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
@ -91,10 +95,12 @@ class HttpResponse:
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.
elif self.headers.has_key('content-type'):
contentType = self.headers['content-type']
for xmlHeader in self.xmlHeaders:
if contentType.startswith(xmlHeader):
# Return an unmarshalled version of the XML content, for
# easy use in Python.
return XmlUnmarshaller().parse(self.body)
# ------------------------------------------------------------------------------
@ -109,9 +115,6 @@ class Resource:
self.username = username
self.password = password
self.url = url
# If some headers must be sent with any request sent through this
# resource (like a cookie), you can store them in the following dict.
self.headers = {}
# If p_measure is True, we will measure, for every request sent, the
# time we wait until we receive the response.
self.measure = measure
@ -127,6 +130,9 @@ class Resource:
self.port = port and int(port[1:]) or 80
self.uri = uri or '/'
else: raise 'Wrong URL: %s' % str(url)
# If some headers must be sent with any request sent through this
# resource (like a cookie), you can store them in the following dict.
self.headers = {'Host': self.host}
def __repr__(self):
return '<Dav resource at %s>' % self.url
@ -147,7 +153,12 @@ class Resource:
def send(self, method, uri, body=None, headers={}, bodyType=None):
'''Sends a HTTP request with p_method, for p_uri.'''
conn = httplib.HTTP()
try:
conn.connect(self.host, self.port)
except socket.gaierror, sge:
raise ResourceError('Check your Internet connection (%s)'% str(sge))
except socket.error, se:
raise ResourceError('Connection error (%s)'% str(sge))
# Tell what kind of HTTP request it will be.
conn.putrequest(method, uri)
# Add HTTP headers
@ -222,6 +233,7 @@ class Resource:
'''Perform a HTTP GET on the server.'''
if not uri: uri = self.uri
return self.send('GET', uri, headers=headers)
rss = get
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
@ -230,7 +242,6 @@ class Resource:
body of the HTTP request.'''
if not uri: uri = self.uri
# Prepare the data to send
headers['Host'] = self.host
if encode == 'form':
# Format the form data and prepare headers
body = FormDataEncoder(data).encode()

View file

@ -342,12 +342,6 @@ class XmlUnmarshaller(XmlParser):
# Alias: "unmarshall" -> "parse"
unmarshall = XmlParser.parse
class CssParser(XmlUnmarshaller):
cssTags = {'rss': 'object', 'channel': 'object', 'item': 'object'}
def startDocument(self):
XmlUnmarshaller.startDocument(self)
self.tagTypes.update(self.cssTags)
# ------------------------------------------------------------------------------
class XmlMarshaller:
'''This class allows to produce a XML version of a Python object, which