diff --git a/gen/__init__.py b/gen/__init__.py index 4c448a3..fb3aeb3 100644 --- a/gen/__init__.py +++ b/gen/__init__.py @@ -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 diff --git a/gen/mixins/ToolMixin.py b/gen/mixins/ToolMixin.py index 2313e75..a4cd8e2 100644 --- a/gen/mixins/ToolMixin.py +++ b/gen/mixins/ToolMixin.py @@ -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? diff --git a/gen/ui/navigate.pt b/gen/ui/navigate.pt index cba4bf0..63a8f00 100644 --- a/gen/ui/navigate.pt +++ b/gen/ui/navigate.pt @@ -64,8 +64,9 @@ Go to the source URL (search or referred object) + 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"/> Go to the first page Macro for displaying a search
+ tal:content="search/translated"> +
Macro for displaying a group of searches @@ -16,7 +17,8 @@ src python:test(expanded, 'ui/collapse.gif', 'ui/expand.gif'); onClick python:'toggleCookie(\'%s\')' % widget['labelId']; align dleft"/> - + + Group content
The title of the search.

- + ()  —  - - -
-
+ Search description +
+
Appy (top) navigation
- diff --git a/gen/utils.py b/gen/utils.py index a13bae7..42bb73e 100644 --- a/gen/utils.py +++ b/gen/utils.py @@ -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):