[gen] It is now possible to define, via method 'getDynamicSearches', dynamic searches for a class.

This commit is contained in:
Gaetan Delannay 2012-11-14 17:40:52 +01:00
parent 4872e5d8b8
commit 5269b278f7
6 changed files with 99 additions and 52 deletions

View file

@ -106,7 +106,8 @@ class Group:
hasLabel=True, hasDescr=False, hasHelp=False,
hasHeaders=False, group=None, colspan=1, align='center',
valign='top', css_class='', master=None, masterValue=None,
cellpadding=1, cellspacing=1, cellgap='0.6em', label=None):
cellpadding=1, cellspacing=1, cellgap='0.6em', label=None,
translated=None):
self.name = name
# In its simpler form, field "columns" below can hold a list or tuple
# of column widths expressed as strings, that will be given as is in
@ -168,6 +169,9 @@ class Group:
self.masterValue = initMasterValue(masterValue)
if master: master.slaves.append(self)
self.label = label # See similar attr of Type class.
# If a translated name is already given here, we will use it instead of
# trying to translate the group label.
self.translated = translated
def _setColumns(self):
'''Standardizes field "columns" as a list of Column instances. Indeed,
@ -293,7 +297,8 @@ class Import:
class Search:
'''Used for specifying a search for a given type.'''
def __init__(self, name, group=None, sortBy='', sortOrder='asc', limit=None,
default=False, colspan=1, **fields):
default=False, colspan=1, translated=None,
translatedDescr=None, **fields):
self.name = name
# Searches may be visually grouped in the portlet.
self.group = Group.get(group)
@ -304,6 +309,10 @@ class Search:
# on main link.
self.default = default
self.colspan = colspan
# If a translated name or description is already given here, we will
# use it instead of trying to translate from labels.
self.translated = translated
self.translatedDescr = translatedDescr
# In the dict below, keys are indexed field names and values are
# search values.
self.fields = fields

View file

@ -3,7 +3,7 @@ import os, os.path, sys, re, time, random, types, base64, urllib
from appy import Object
import appy.gen
from appy.gen import Type, Search, Selection, String, Page
from appy.gen.utils import SomeObjects, getClassName, GroupDescr
from appy.gen.utils import SomeObjects, getClassName, GroupDescr, SearchDescr
from appy.gen.mixins import BaseMixin
from appy.gen.wrappers import AbstractWrapper
from appy.gen.descriptors import ClassDescriptor
@ -288,9 +288,8 @@ class ToolMixin(BaseMixin):
maxResults=None, noSecurity=False, sortBy=None,
sortOrder='asc', filterKey=None, filterValue=None,
refObject=None, refField=None):
'''Executes a query on instances of a given p_className (or several,
separated with commas) in the catalog. If p_searchName is specified,
it corresponds to:
'''Executes a query on instances of a given p_className in the catalog.
If p_searchName is specified, it corresponds to:
1) a search defined on p_className: additional search criteria
will be added to the query, or;
2) "_advanced": in this case, additional search criteria will also
@ -328,22 +327,10 @@ class ToolMixin(BaseMixin):
If p_refObject and p_refField are given, the query is limited to the
objects that are referenced from p_refObject through p_refField.'''
# Is there one or several content types ?
if className.find(',') != -1:
classNames = className.split(',')
else:
classNames = className
params = {'ClassName': classNames}
params = {'ClassName': className}
if not brainsOnly: params['batch'] = True
# Manage additional criteria from a search when relevant
if searchName:
# In this case, className must contain a single content type.
appyClass = self.getAppyClass(className)
if searchName != '_advanced':
search = ClassDescriptor.getSearch(appyClass, searchName)
else:
fields = self.REQUEST.SESSION['searchCriteria']
search = Search('customSearch', **fields)
if searchName: search = self.getSearch(className, searchName)
if search:
# Add additional search criteria
for fieldName, fieldValue in search.fields.iteritems():
@ -672,36 +659,61 @@ class ToolMixin(BaseMixin):
obj = self.getObject(objectUid)
return obj, fieldName
def getGroupedSearches(self, contentType):
def getGroupedSearches(self, className):
'''Returns an object with 2 attributes:
* "searches" stores the searches that are defined for p_contentType;
* "searches" stores the searches that are defined for p_className;
* "default" stores the search defined as the default one.
Every item representing a search is a dict containing info about a
search or about a group of searches.
'''
appyClass = self.getAppyClass(contentType)
appyClass = self.getAppyClass(className)
res = []
default = None # Also retrieve the default one here.
groups = {} # The already encountered groups
page = Page('main') # A dummy page required by class GroupDescr
for search in ClassDescriptor.getSearches(appyClass):
# Compute the dict representing this search
searchLabel = '%s_search_%s' % (contentType, search.name)
dSearch = {'name': search.name, 'type': 'search',
'colspan': search.colspan,
'label': self.translate(searchLabel),
'descr': self.translate('%s_descr' % searchLabel)}
searches = ClassDescriptor.getSearches(appyClass)
# Add dynamic searches if defined on p_appyClass
if hasattr(appyClass, 'getDynamicSearches'):
searches += appyClass.getDynamicSearches(self.appy())
for search in searches:
# Create the search descriptor
searchDescr = SearchDescr(search, className, self).get()
if not search.group:
# Insert the search at the highest level, not in any group.
res.append(dSearch)
res.append(searchDescr)
else:
groupDescr = search.group.insertInto(res, groups, page,
contentType, forSearch=True)
GroupDescr.addWidget(groupDescr, dSearch)
className, forSearch=True)
GroupDescr.addWidget(groupDescr, searchDescr)
# Is this search the default search?
if search.default: default = dSearch
if search.default: default = searchDescr
return Object(searches=res, default=default).__dict__
def getSearch(self, className, name, descr=False):
'''Gets the Search instance (or a SearchDescr instance if p_descr is
True) corresponding to the search named p_name, on class
p_className.'''
if name == '_advanced':
# It is an advanced search whose parameters are in the session.
fields = self.REQUEST.SESSION['searchCriteria']
res = Search('customSearch', **fields)
elif name:
appyClass = self.getAppyClass(className)
# Search among static searches
res = ClassDescriptor.getSearch(appyClass, name)
if not res and hasattr(appyClass, 'getDynamicSearches'):
# Search among dynamic searches
for search in appyClass.getDynamicSearches(self.appy()):
if search.name == name:
res = search
break
else:
# It is the search for every instance of p_className
res = Search('allSearch')
# Return a SearchDescr if required.
if descr: res = SearchDescr(res, className, self).get()
return res
def getQueryUrl(self, contentType, searchName, startNumber=None):
'''This method creates the URL that allows to perform a (non-Ajax)
request for getting queried objects from a search named p_searchName
@ -753,11 +765,13 @@ class ToolMixin(BaseMixin):
# This is a named, predefined search.
label = '%s_search_%s' % (d1.split(':')[0], searchName)
res['backText'] = self.translate(label)
# If it is a dynamic search this label does not exist.
if ('_' in res['backText']): res['backText'] = ''
else:
fieldName, pageName = d2.split(':')
sourceObj = self.getObject(d1)
label = '%s_%s' % (sourceObj.meta_type, fieldName)
res['backText'] = '%s : %s' % (sourceObj.Title(),
res['backText'] = '%s - %s' % (sourceObj.Title(),
self.translate(label))
newNav = '%s.%s.%s.%%d.%s' % (t, d1, d2, totalNumber)
# Among, first, previous, next and last, which one do I need?

View file

@ -64,8 +64,9 @@
<tr valign="middle">
<tal:comment replace="nothing">Go to the source URL (search or referred object)</tal:comment>
<td tal:condition="sourceUrl"><a tal:attributes="href sourceUrl"><img
tal:attributes="src string: $appUrl/ui/gotoSource.png;
title python: backText + ' : ' + _('goto_source')"/></a></td>
tal:define="gotoSource python: _('goto_source');
goBack python: backText and ('%s - %s' % (backText, gotoSource)) or gotoSource"
tal:attributes="src string: $appUrl/ui/gotoSource.png; title goBack"/></a></td>
<tal:comment replace="nothing">Go to the first page</tal:comment>
<td tal:condition="firstUrl"><a tal:attributes="href firstUrl"><img
tal:attributes="src string: $appUrl/ui/arrowLeftDouble.png;

View file

@ -1,9 +1,10 @@
<tal:comment replace="nothing">Macro for displaying a search</tal:comment>
<div metal:define-macro="search" class="portletSearch">
<a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']);
title search/descr;
title search/translatedDescr;
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
tal:content="structure search/label"></a>
tal:content="search/translated">
</a>
</div>
<tal:comment replace="nothing">Macro for displaying a group of searches</tal:comment>
@ -16,7 +17,8 @@
src python:test(expanded, 'ui/collapse.gif', 'ui/expand.gif');
onClick python:'toggleCookie(\'%s\')' % widget['labelId'];
align dleft"/>
<span tal:replace="python: _(widget['labelId'])"/>
<span tal:condition="not: widget/translated" tal:replace="python: _(widget['labelId'])"/>
<span tal:condition="widget/translated" tal:replace="widget/translated"/>
</div>
<tal:comment replace="nothing">Group content</tal:comment>
<div tal:define="display python:test(expanded, 'display:block', 'display:none')"

View file

@ -7,15 +7,13 @@
refUrlPart python: refObject and ('&ref=%s:%s' % (refObject.UID(), refField)) or '';
startNumber request/startNumber|python:'0';
startNumber python: int(startNumber);
searchName request/search;
labelId python: searchName and ('%s_search_%s' % (className, searchName)) or '';
labelId python: (searchName == '_advanced') and 'search_results' or labelId;
searchLabel python: labelId and _(labelId) or '';
searchName request/search|python:'';
searchDescr python: tool.getSearch(className, searchName, descr=True);
sortKey request/sortKey| python:'';
sortOrder request/sortOrder| python:'asc';
filterKey request/filterKey| python:'';
filterValue request/filterValue | python:'';
queryResult python: tool.executeQuery(className, searchName, startNumber, remember=True, sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey, filterValue=filterValue, refObject=refObject, refField=refField);
queryResult python: tool.executeQuery(className, search=searchDescr['search'], startNumber=startNumber, remember=True, sortBy=sortKey, sortOrder=sortOrder, filterKey=filterKey, filterValue=filterValue, refObject=refObject, refField=refField);
objs queryResult/objects;
totalNumber queryResult/totalNumber;
batchSize queryResult/batchSize;
@ -39,7 +37,7 @@
<tal:comment replace="nothing">The title of the search.</tal:comment>
<p>
<span tal:replace="structure python: test(searchName, searchLabel, _('%s_plural' % className))"/>
<span tal:replace="searchDescr/translated"/>
(<span tal:replace="totalNumber"/>)
<tal:newSearch condition="python: searchName == '_advanced'">
&nbsp;&mdash;&nbsp;<i><a tal:attributes="href newSearchUrl"
@ -48,19 +46,16 @@
</p>
<table width="100%">
<tr>
<tal:descr condition="searchName">
<td tal:define="descr python: _('%s_descr' % labelId)"
tal:condition="descr/strip">
<span class="discreet" tal:content="descr"></span><br/>
<tal:comment replace="nothing">Search description</tal:comment>
<td tal:condition="searchDescr/translatedDescr">
<span class="discreet" tal:content="searchDescr/translatedDescr"></span><br/>
</td>
</tal:descr>
<td tal:attributes="align dright" width="25%">
<tal:comment replace="nothing">Appy (top) navigation</tal:comment>
<metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/>
</td>
</tr>
</table>
<table tal:define="columnLayouts python: tool.getResultColumnsLayouts(className, refInfo);
columns python: objs[0].getColumnsSpecifiers(columnLayouts, dir)"
class="list" width="100%">

View file

@ -185,6 +185,32 @@ class PhaseDescr(Descr):
self.nextPhase = allPhases[i+1]
self.phaseStatus = res
class SearchDescr(Descr):
'''Describes a Search.'''
def __init__(self, search, className, tool):
self.search = search
self.name = search.name
self.type = 'search'
self.colspan = search.colspan
if search.translated:
self.translated = search.translated
self.translatedDescr = search.translatedDescr
else:
# The label may be specific in some special cases.
labelDescr = ''
if search.name == 'allSearch':
label = '%s_plural' % className
elif search.name == 'customSearch':
label = 'search_results'
else:
label = '%s_search_%s' % (className, search.name)
labelDescr = label + '_descr'
self.translated = tool.translate(label)
if labelDescr:
self.translatedDescr = tool.translate(labelDescr)
else:
self.translatedDescr = ''
# ------------------------------------------------------------------------------
upperLetter = re.compile('[A-Z]')
def produceNiceMessage(msg):