[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, hasLabel=True, hasDescr=False, hasHelp=False,
hasHeaders=False, group=None, colspan=1, align='center', hasHeaders=False, group=None, colspan=1, align='center',
valign='top', css_class='', master=None, masterValue=None, 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 self.name = name
# In its simpler form, field "columns" below can hold a list or tuple # 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 # of column widths expressed as strings, that will be given as is in
@ -168,6 +169,9 @@ class Group:
self.masterValue = initMasterValue(masterValue) self.masterValue = initMasterValue(masterValue)
if master: master.slaves.append(self) if master: master.slaves.append(self)
self.label = label # See similar attr of Type class. 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): def _setColumns(self):
'''Standardizes field "columns" as a list of Column instances. Indeed, '''Standardizes field "columns" as a list of Column instances. Indeed,
@ -293,7 +297,8 @@ class Import:
class Search: class Search:
'''Used for specifying a search for a given type.''' '''Used for specifying a search for a given type.'''
def __init__(self, name, group=None, sortBy='', sortOrder='asc', limit=None, 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 self.name = name
# Searches may be visually grouped in the portlet. # Searches may be visually grouped in the portlet.
self.group = Group.get(group) self.group = Group.get(group)
@ -304,6 +309,10 @@ class Search:
# on main link. # on main link.
self.default = default self.default = default
self.colspan = colspan 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 # In the dict below, keys are indexed field names and values are
# search values. # search values.
self.fields = fields self.fields = fields

View file

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

View file

@ -64,8 +64,9 @@
<tr valign="middle"> <tr valign="middle">
<tal:comment replace="nothing">Go to the source URL (search or referred object)</tal:comment> <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 <td tal:condition="sourceUrl"><a tal:attributes="href sourceUrl"><img
tal:attributes="src string: $appUrl/ui/gotoSource.png; tal:define="gotoSource python: _('goto_source');
title python: backText + ' : ' + _('goto_source')"/></a></td> 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> <tal:comment replace="nothing">Go to the first page</tal:comment>
<td tal:condition="firstUrl"><a tal:attributes="href firstUrl"><img <td tal:condition="firstUrl"><a tal:attributes="href firstUrl"><img
tal:attributes="src string: $appUrl/ui/arrowLeftDouble.png; 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> <tal:comment replace="nothing">Macro for displaying a search</tal:comment>
<div metal:define-macro="search" class="portletSearch"> <div metal:define-macro="search" class="portletSearch">
<a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']); <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', '');" class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
tal:content="structure search/label"></a> tal:content="search/translated">
</a>
</div> </div>
<tal:comment replace="nothing">Macro for displaying a group of searches</tal:comment> <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'); src python:test(expanded, 'ui/collapse.gif', 'ui/expand.gif');
onClick python:'toggleCookie(\'%s\')' % widget['labelId']; onClick python:'toggleCookie(\'%s\')' % widget['labelId'];
align dleft"/> 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> </div>
<tal:comment replace="nothing">Group content</tal:comment> <tal:comment replace="nothing">Group content</tal:comment>
<div tal:define="display python:test(expanded, 'display:block', 'display:none')" <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 ''; refUrlPart python: refObject and ('&ref=%s:%s' % (refObject.UID(), refField)) or '';
startNumber request/startNumber|python:'0'; startNumber request/startNumber|python:'0';
startNumber python: int(startNumber); startNumber python: int(startNumber);
searchName request/search; searchName request/search|python:'';
labelId python: searchName and ('%s_search_%s' % (className, searchName)) or ''; searchDescr python: tool.getSearch(className, searchName, descr=True);
labelId python: (searchName == '_advanced') and 'search_results' or labelId;
searchLabel python: labelId and _(labelId) or '';
sortKey request/sortKey| python:''; sortKey request/sortKey| python:'';
sortOrder request/sortOrder| python:'asc'; sortOrder request/sortOrder| python:'asc';
filterKey request/filterKey| python:''; filterKey request/filterKey| python:'';
filterValue request/filterValue | 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; objs queryResult/objects;
totalNumber queryResult/totalNumber; totalNumber queryResult/totalNumber;
batchSize queryResult/batchSize; batchSize queryResult/batchSize;
@ -39,7 +37,7 @@
<tal:comment replace="nothing">The title of the search.</tal:comment> <tal:comment replace="nothing">The title of the search.</tal:comment>
<p> <p>
<span tal:replace="structure python: test(searchName, searchLabel, _('%s_plural' % className))"/> <span tal:replace="searchDescr/translated"/>
(<span tal:replace="totalNumber"/>) (<span tal:replace="totalNumber"/>)
<tal:newSearch condition="python: searchName == '_advanced'"> <tal:newSearch condition="python: searchName == '_advanced'">
&nbsp;&mdash;&nbsp;<i><a tal:attributes="href newSearchUrl" &nbsp;&mdash;&nbsp;<i><a tal:attributes="href newSearchUrl"
@ -48,19 +46,16 @@
</p> </p>
<table width="100%"> <table width="100%">
<tr> <tr>
<tal:descr condition="searchName"> <tal:comment replace="nothing">Search description</tal:comment>
<td tal:define="descr python: _('%s_descr' % labelId)" <td tal:condition="searchDescr/translatedDescr">
tal:condition="descr/strip"> <span class="discreet" tal:content="searchDescr/translatedDescr"></span><br/>
<span class="discreet" tal:content="descr"></span><br/>
</td> </td>
</tal:descr>
<td tal:attributes="align dright" width="25%"> <td tal:attributes="align dright" width="25%">
<tal:comment replace="nothing">Appy (top) navigation</tal:comment> <tal:comment replace="nothing">Appy (top) navigation</tal:comment>
<metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/> <metal:nav use-macro="context/ui/navigate/macros/appyNavigate"/>
</td> </td>
</tr> </tr>
</table> </table>
<table tal:define="columnLayouts python: tool.getResultColumnsLayouts(className, refInfo); <table tal:define="columnLayouts python: tool.getResultColumnsLayouts(className, refInfo);
columns python: objs[0].getColumnsSpecifiers(columnLayouts, dir)" columns python: objs[0].getColumnsSpecifiers(columnLayouts, dir)"
class="list" width="100%"> class="list" width="100%">

View file

@ -185,6 +185,32 @@ class PhaseDescr(Descr):
self.nextPhase = allPhases[i+1] self.nextPhase = allPhases[i+1]
self.phaseStatus = res 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]') upperLetter = re.compile('[A-Z]')
def produceNiceMessage(msg): def produceNiceMessage(msg):