[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,
focus=False, historized=False, mapping=None, label=None,
queryable=False, queryFields=None, queryNbCols=1,
navigable=False):
navigable=False, searchSelect=None):
self.klass = klass
self.attribute = attribute
# May the user add new objects through this ref ?
@ -1830,6 +1830,11 @@ class Ref(Type):
self.queryNbCols = queryNbCols
# Within the portlet, will referred elements appear ?
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,
editDefault, show, page, group, layouts, move, indexed,
False, specificReadPermission, specificWritePermission,
@ -1911,6 +1916,23 @@ class Ref(Type):
def getFormattedValue(self, obj, 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):
if not self.link: return None
# 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.
if not appyTool.count('User', noSecurity=True, login='admin'):
print 'No admin!'
appyTool.create('users', noSecurity=True, login='admin',
password1='admin', password2='admin',
email='admin@appyframework.org', roles=['Manager'])

View file

@ -1138,4 +1138,13 @@ class ToolMixin(BaseMixin):
sendMail(appyTool, email, subject, body)
os.remove(tokenFile)
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())
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,
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 information to display may only be the object title or more if
field.shownInfo is used.'''
appyType = self.getAppyType(name)
res = refObject.title
if 'title' in appyType.shownInfo:
# We may place it at another place
appyType = self.getAppyType(name, className=className)
res = ''
for fieldName in appyType.shownInfo:
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;
background-color: #F9EDBE; text-align: center;
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;}
.focus td { padding: 4px 0px 4px 4px }
.discreet { font-size: 90%; }

View file

@ -254,4 +254,26 @@
</metal:cell>
<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]
return self._callCustom('onEdit', created)
def mayEdit(self): return self._callCustom('mayEdit')
def mayDelete(self): return self._callCustom('mayDelete')
def mayEdit(self):
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):
'''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)
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):
'''This wrapper implements some methods like "validate" and "onEdit".
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
user versions.'''
if len(self.__class__.__bases__) > 1:
# There is a custom user class
customUser = self.__class__.__bases__[-1]
if customUser.__dict__.has_key(methodName):
return customUser.__dict__[methodName](self, *args, **kwargs)
custom = self._getCustomMethod(methodName)
if custom: return custom(self, *args, **kwargs)
def getField(self, name): return self.o.getAppyType(name)
def isEmpty(self, name):