[gen] Now it is possible to index and search Ref fields.

This commit is contained in:
Gaetan Delannay 2012-09-17 21:11:54 +02:00
parent bdaf1b4bbd
commit 5f530d9f9e
8 changed files with 78 additions and 17 deletions

View file

@ -1762,7 +1762,7 @@ class Ref(Type):
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,
queryable=False, queryFields=None, queryNbCols=1, queryable=False, queryFields=None, queryNbCols=1,
navigable=False): navigable=False, searchSelect=None):
self.klass = klass self.klass = klass
self.attribute = attribute self.attribute = attribute
# May the user add new objects through this ref ? # May the user add new objects through this ref ?
@ -1830,6 +1830,11 @@ class Ref(Type):
self.queryNbCols = queryNbCols self.queryNbCols = queryNbCols
# Within the portlet, will referred elements appear ? # Within the portlet, will referred elements appear ?
self.navigable = navigable self.navigable = navigable
# The search select method is used if self.indexed is True. In this
# case, we need to know among which values we can search on this field,
# in the search screen. Those values are returned by self.searchSelect,
# which must be a static method accepting the tool as single arg.
self.searchSelect = searchSelect
Type.__init__(self, validator, multiplicity, index, default, optional, Type.__init__(self, validator, multiplicity, index, default, optional,
editDefault, show, page, group, layouts, move, indexed, editDefault, show, page, group, layouts, move, indexed,
False, specificReadPermission, specificWritePermission, False, specificReadPermission, specificWritePermission,
@ -1911,6 +1916,23 @@ class Ref(Type):
def getFormattedValue(self, obj, value): def getFormattedValue(self, obj, value):
return value return value
def getIndexType(self): return 'ZCTextIndex'
def getIndexValue(self, obj, forSearch=False):
'''Value for indexing is the list of UIDs of linked objects. If
p_forSearch is True, it will return a "string" version made of the
titles of linked objects.'''
if not forSearch:
res = getattr(obj.aq_base, self.name, '')
if res: res = ' '.join(res)
return res
else:
# For the global search: concatenate titles of linked objects
titles = []
for obj in self.getValue(type='objects'):
titles.append(obj.title)
return ' '.join(titles)
def validateValue(self, obj, value): def validateValue(self, obj, value):
if not self.link: return None if not self.link: return None
# We only check "link" Refs because in edit views, "add" Refs are # We only check "link" Refs because in edit views, "add" Refs are

View file

@ -250,7 +250,6 @@ class ZopeInstaller:
# Create the admin user if it does not exist. # Create the admin user if it does not exist.
if not appyTool.count('User', noSecurity=True, login='admin'): if not appyTool.count('User', noSecurity=True, login='admin'):
print 'No admin!'
appyTool.create('users', noSecurity=True, login='admin', appyTool.create('users', noSecurity=True, login='admin',
password1='admin', password2='admin', password1='admin', password2='admin',
email='admin@appyframework.org', roles=['Manager']) email='admin@appyframework.org', roles=['Manager'])

View file

@ -1138,4 +1138,13 @@ class ToolMixin(BaseMixin):
sendMail(appyTool, email, subject, body) sendMail(appyTool, email, subject, body)
os.remove(tokenFile) os.remove(tokenFile)
return self.goto(siteUrl, self.translate('new_password_sent')) return self.goto(siteUrl, self.translate('new_password_sent'))
def getSearchValues(self, name, className):
'''Gets the possible values for selecting a value for searching field
p_name belonging to class p_className.'''
klass = self.getAppyClass(className, wrapper=True)
method = getattr(klass, name).searchSelect
tool = self.appy()
objects = method.__get__(tool)(tool)
return [(o.uid, o) for o in objects]
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -518,16 +518,13 @@ class BaseMixin:
return appyType.select(self.appy()) return appyType.select(self.appy())
xhtmlToText = re.compile('<.*?>', re.S) xhtmlToText = re.compile('<.*?>', re.S)
def getReferenceLabel(self, name, refObject): def getReferenceLabel(self, name, refObject, className=None):
'''p_name is the name of a Ref field with link=True. I need to display, '''p_name is the name of a Ref field with link=True. I need to display,
on an edit view, the p_refObject in the listbox that will allow on an edit view, the p_refObject in the listbox that will allow
the user to choose which object(s) to link through the Ref. the user to choose which object(s) to link through the Ref.
The information to display may only be the object title or more if The information to display may only be the object title or more if
field.shownInfo is used.''' field.shownInfo is used.'''
appyType = self.getAppyType(name) appyType = self.getAppyType(name, className=className)
res = refObject.title
if 'title' in appyType.shownInfo:
# We may place it at another place
res = '' res = ''
for fieldName in appyType.shownInfo: for fieldName in appyType.shownInfo:
refType = refObject.o.getAppyType(fieldName) refType = refObject.o.getAppyType(fieldName)

View file

@ -65,7 +65,7 @@ img { border: 0; vertical-align: middle}
width: 600px; border: 1px #F0C36D solid; padding: 6px 16px; width: 600px; border: 1px #F0C36D solid; padding: 6px 16px;
background-color: #F9EDBE; text-align: center; background-color: #F9EDBE; text-align: center;
border-radius: 2px 2px 2px 2px; box-shadow: 0 2px 4px #A9A9A9;} border-radius: 2px 2px 2px 2px; box-shadow: 0 2px 4px #A9A9A9;}
.focus { font-size: 90%; padding: 6px 16px; background-color: #d7dee4; .focus { font-size: 90%; margin: 7px; background-color: #d7dee4;
border-radius: 2px 2px 2px 2px; box-shadow: 0 2px 4px #A9A9A9;} border-radius: 2px 2px 2px 2px; box-shadow: 0 2px 4px #A9A9A9;}
.focus td { padding: 4px 0px 4px 4px } .focus td { padding: 4px 0px 4px 4px }
.discreet { font-size: 90%; } .discreet { font-size: 90%; }

View file

@ -254,4 +254,26 @@
</metal:cell> </metal:cell>
<tal:comment replace="nothing">Search macro for a Ref.</tal:comment> <tal:comment replace="nothing">Search macro for a Ref.</tal:comment>
<metal:search define-macro="search"></metal:search> <metal:search define-macro="search">
<label tal:attributes="for widgetName" tal:content="python: _(widget['labelId'])"></label><br>&nbsp;&nbsp;
<tal:comment replace="nothing">The "and" / "or" radio buttons</tal:comment>
<tal:operator define="operName python: 'o_%s' % name;
orName python: '%s_or' % operName;
andName python: '%s_and' % operName;"
condition="python: widget['multiplicity'][1]!=1">
<input type="radio" tal:attributes="name operName; id orName" checked="checked" value="or"/>
<label tal:attributes="for orName" tal:content="python: _('search_or')"></label>
<input type="radio" tal:attributes="name operName; id andName" value="and"/>
<label tal:attributes="for andName" tal:content="python: _('search_and')"></label><br/>
</tal:operator>
<tal:comment replace="nothing">The list of values</tal:comment>
<select tal:attributes="name widgetName; size widget/height" multiple="multiple">
<tal:option repeat="v python: tool.getSearchValues(name, className)">
<option tal:define="uid python: v[0];
title python: tool.getReferenceLabel(name, v[1], className)"
tal:attributes="value uid; title title"
tal:content="python: tool.truncateValue(title, widget)">
</option>
</tal:option>
</select>
</metal:search>

View file

@ -169,8 +169,15 @@ class UserWrapper(AbstractWrapper):
del self.o.__ac_local_roles__[None] del self.o.__ac_local_roles__[None]
return self._callCustom('onEdit', created) return self._callCustom('onEdit', created)
def mayEdit(self): return self._callCustom('mayEdit') def mayEdit(self):
def mayDelete(self): return self._callCustom('mayDelete') custom = self._getCustomMethod('mayEdit')
if custom: return self._callCustom('mayEdit')
else: return True
def mayDelete(self):
custom = self._getCustomMethod('mayDelete')
if custom: return self._callCustom('mayDelete')
else: return True
def getZopeUser(self): def getZopeUser(self):
'''Gets the Zope user corresponding to this user.''' '''Gets the Zope user corresponding to this user.'''

View file

@ -132,16 +132,21 @@ class AbstractWrapper(object):
if other: return cmp(self.o, other.o) if other: return cmp(self.o, other.o)
return 1 return 1
def _getCustomMethod(self, methodName):
'''See docstring of _callCustom below.'''
if len(self.__class__.__bases__) > 1:
# There is a custom user class
custom = self.__class__.__bases__[-1]
if custom.__dict__.has_key(methodName):
return custom.__dict__[methodName]
def _callCustom(self, methodName, *args, **kwargs): def _callCustom(self, methodName, *args, **kwargs):
'''This wrapper implements some methods like "validate" and "onEdit". '''This wrapper implements some methods like "validate" and "onEdit".
If the user has defined its own wrapper, its methods will not be If the user has defined its own wrapper, its methods will not be
called. So this method allows, from the methods here, to call the called. So this method allows, from the methods here, to call the
user versions.''' user versions.'''
if len(self.__class__.__bases__) > 1: custom = self._getCustomMethod(methodName)
# There is a custom user class if custom: return custom(self, *args, **kwargs)
customUser = self.__class__.__bases__[-1]
if customUser.__dict__.has_key(methodName):
return customUser.__dict__[methodName](self, *args, **kwargs)
def getField(self, name): return self.o.getAppyType(name) def getField(self, name): return self.o.getAppyType(name)
def isEmpty(self, name): def isEmpty(self, name):