[gen] Searches can now be grouped into groups of groups of..., creating a tree structure in the portlet.
This commit is contained in:
parent
6cf29778b6
commit
4872e5d8b8
Binary file not shown.
|
@ -233,24 +233,26 @@ class Group:
|
||||||
if self.group and (self.group not in walkedGroups) and \
|
if self.group and (self.group not in walkedGroups) and \
|
||||||
not self.group.label:
|
not self.group.label:
|
||||||
# We remember walked groups for avoiding infinite recursion.
|
# We remember walked groups for avoiding infinite recursion.
|
||||||
self.group.generateLabels(messages, classDescr, walkedGroups)
|
self.group.generateLabels(messages, classDescr, walkedGroups,
|
||||||
|
forSearch=forSearch)
|
||||||
|
|
||||||
def insertInto(self, widgets, groupDescrs, page, metaType):
|
def insertInto(self, widgets, groupDescrs, page, metaType, forSearch=False):
|
||||||
'''Inserts the GroupDescr instance corresponding to this Group instance
|
'''Inserts the GroupDescr instance corresponding to this Group instance
|
||||||
into p_widgets, the recursive structure used for displaying all
|
into p_widgets, the recursive structure used for displaying all
|
||||||
widgets in a given p_page, and returns this GroupDescr instance.'''
|
widgets in a given p_page (or all searches), and returns this
|
||||||
|
GroupDescr instance.'''
|
||||||
# First, create the corresponding GroupDescr if not already in
|
# First, create the corresponding GroupDescr if not already in
|
||||||
# p_groupDescrs.
|
# p_groupDescrs.
|
||||||
if self.name not in groupDescrs:
|
if self.name not in groupDescrs:
|
||||||
groupDescr = groupDescrs[self.name] = GroupDescr(self, page,
|
groupDescr = groupDescrs[self.name] = \
|
||||||
metaType).get()
|
GroupDescr(self, page, metaType, forSearch=forSearch).get()
|
||||||
# Insert the group at the higher level (ie, directly in p_widgets)
|
# Insert the group at the higher level (ie, directly in p_widgets)
|
||||||
# if the group is not itself in a group.
|
# if the group is not itself in a group.
|
||||||
if not self.group:
|
if not self.group:
|
||||||
widgets.append(groupDescr)
|
widgets.append(groupDescr)
|
||||||
else:
|
else:
|
||||||
outerGroupDescr = self.group.insertInto(widgets, groupDescrs,
|
outerGroupDescr = self.group.insertInto(widgets, groupDescrs,
|
||||||
page, metaType)
|
page, metaType, forSearch=forSearch)
|
||||||
GroupDescr.addWidget(outerGroupDescr, groupDescr)
|
GroupDescr.addWidget(outerGroupDescr, groupDescr)
|
||||||
else:
|
else:
|
||||||
groupDescr = groupDescrs[self.name]
|
groupDescr = groupDescrs[self.name]
|
||||||
|
@ -291,7 +293,7 @@ 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, **fields):
|
default=False, colspan=1, **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)
|
||||||
|
@ -301,6 +303,7 @@ class Search:
|
||||||
# If this search is the default one, it will be triggered by clicking
|
# If this search is the default one, it will be triggered by clicking
|
||||||
# on main link.
|
# on main link.
|
||||||
self.default = default
|
self.default = default
|
||||||
|
self.colspan = colspan
|
||||||
# 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
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
import os, os.path, sys, re, time, random, types, base64, urllib
|
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
|
from appy.gen import Type, Search, Selection, String, Page
|
||||||
from appy.gen.utils import SomeObjects, getClassName
|
from appy.gen.utils import SomeObjects, getClassName, GroupDescr
|
||||||
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
|
||||||
|
@ -672,7 +672,7 @@ class ToolMixin(BaseMixin):
|
||||||
obj = self.getObject(objectUid)
|
obj = self.getObject(objectUid)
|
||||||
return obj, fieldName
|
return obj, fieldName
|
||||||
|
|
||||||
def getSearches(self, contentType):
|
def getGroupedSearches(self, contentType):
|
||||||
'''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_contentType;
|
||||||
* "default" stores the search defined as the default one.
|
* "default" stores the search defined as the default one.
|
||||||
|
@ -680,37 +680,27 @@ class ToolMixin(BaseMixin):
|
||||||
search or about a group of searches.
|
search or about a group of searches.
|
||||||
'''
|
'''
|
||||||
appyClass = self.getAppyClass(contentType)
|
appyClass = self.getAppyClass(contentType)
|
||||||
searches = []
|
res = []
|
||||||
default = None # Also retrieve the default one here.
|
default = None # Also retrieve the default one here.
|
||||||
visitedGroups = {} # Names of already visited search groups
|
groups = {} # The already encountered groups
|
||||||
|
page = Page('main') # A dummy page required by class GroupDescr
|
||||||
for search in ClassDescriptor.getSearches(appyClass):
|
for search in ClassDescriptor.getSearches(appyClass):
|
||||||
# Determine first group label, we will need it.
|
# Compute the dict representing this search
|
||||||
groupLabel = None
|
|
||||||
groupName = None
|
|
||||||
if search.group:
|
|
||||||
groupName = search.group.name
|
|
||||||
groupLabel = '%s_searchgroup_%s' % (contentType, groupName)
|
|
||||||
# Add an item representing the search group if relevant
|
|
||||||
if groupName and (groupName not in visitedGroups):
|
|
||||||
group = {'name': groupName, 'isGroup': True,
|
|
||||||
'labelId': groupLabel, 'searches': [],
|
|
||||||
'label': self.translate(groupLabel),
|
|
||||||
'descr': self.translate('%s_descr' % groupLabel),
|
|
||||||
}
|
|
||||||
searches.append(group)
|
|
||||||
visitedGroups[groupName] = group
|
|
||||||
# Add the search itself
|
|
||||||
searchLabel = '%s_search_%s' % (contentType, search.name)
|
searchLabel = '%s_search_%s' % (contentType, search.name)
|
||||||
dSearch = {'name': search.name, 'isGroup': False,
|
dSearch = {'name': search.name, 'type': 'search',
|
||||||
|
'colspan': search.colspan,
|
||||||
'label': self.translate(searchLabel),
|
'label': self.translate(searchLabel),
|
||||||
'descr': self.translate('%s_descr' % searchLabel)}
|
'descr': self.translate('%s_descr' % searchLabel)}
|
||||||
if groupName:
|
if not search.group:
|
||||||
visitedGroups[groupName]['searches'].append(dSearch)
|
# Insert the search at the highest level, not in any group.
|
||||||
|
res.append(dSearch)
|
||||||
else:
|
else:
|
||||||
searches.append(dSearch)
|
groupDescr = search.group.insertInto(res, groups, page,
|
||||||
if search.default:
|
contentType, forSearch=True)
|
||||||
default = dSearch
|
GroupDescr.addWidget(groupDescr, dSearch)
|
||||||
return Object(searches=searches, default=default).__dict__
|
# Is this search the default search?
|
||||||
|
if search.default: default = dSearch
|
||||||
|
return Object(searches=res, default=default).__dict__
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
@ -80,7 +80,7 @@ img { border: 0; vertical-align: middle}
|
||||||
.portletPage { font-style: italic; }
|
.portletPage { font-style: italic; }
|
||||||
.portletGroup { font-variant: small-caps; font-weight: bold; font-style: normal;
|
.portletGroup { font-variant: small-caps; font-weight: bold; font-style: normal;
|
||||||
margin-top: 0.1em }
|
margin-top: 0.1em }
|
||||||
.portletSearch { font-size: 90%; font-style: italic; padding-left: 1em}
|
.portletSearch { font-size: 90%; font-style: italic }
|
||||||
.phase { border-style: dashed; border-width: thin; padding: 4px 0.6em 5px 1em;}
|
.phase { border-style: dashed; border-width: thin; padding: 4px 0.6em 5px 1em;}
|
||||||
.phaseSelected { background-color: #F4F5F6; }
|
.phaseSelected { background-color: #F4F5F6; }
|
||||||
.content { padding: 14px 14px 9px 15px; background-color: #f1f1f1 }
|
.content { padding: 14px 14px 9px 15px; background-color: #f1f1f1 }
|
||||||
|
|
|
@ -1,9 +1,51 @@
|
||||||
<tal:comment replace="nothing">
|
<tal:comment replace="nothing">Macro for displaying a search</tal:comment>
|
||||||
This macro displays the content of the application portlet.
|
<div metal:define-macro="search" class="portletSearch">
|
||||||
</tal:comment>
|
<a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']);
|
||||||
|
title search/descr;
|
||||||
|
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
|
||||||
|
tal:content="structure search/label"></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">Macro for displaying a group of searches</tal:comment>
|
||||||
|
<metal:group define-macro="group"
|
||||||
|
tal:define="expanded python: request.get(widget['labelId'], 'collapsed') == 'expanded'">
|
||||||
|
<tal:comment replace="nothing">Group name</tal:comment>
|
||||||
|
<div class="portletGroup">
|
||||||
|
<img style="cursor:pointer; margin-right: 3px"
|
||||||
|
tal:attributes="id python: '%s_img' % widget['labelId'];
|
||||||
|
src python:test(expanded, 'ui/collapse.gif', 'ui/expand.gif');
|
||||||
|
onClick python:'toggleCookie(\'%s\')' % widget['labelId'];
|
||||||
|
align dleft"/>
|
||||||
|
<span tal:replace="python: _(widget['labelId'])"/>
|
||||||
|
</div>
|
||||||
|
<tal:comment replace="nothing">Group content</tal:comment>
|
||||||
|
<div tal:define="display python:test(expanded, 'display:block', 'display:none')"
|
||||||
|
tal:attributes="id widget/labelId;
|
||||||
|
style python: 'padding-left: 10px;; %s' % display">
|
||||||
|
<tal:searchRows repeat="searches widget/widgets">
|
||||||
|
<tal:searchElem repeat="searchElem searches">
|
||||||
|
<tal:comment replace="nothing">An inner group within this group</tal:comment>
|
||||||
|
<tal:group condition="python: searchElem['type'] == 'group'">
|
||||||
|
<tal:g define="widget searchElem">
|
||||||
|
<metal:s use-macro="app/ui/portlet/macros/group"/>
|
||||||
|
</tal:g>
|
||||||
|
</tal:group>
|
||||||
|
<tal:search condition="python: searchElem['type'] != 'group'">
|
||||||
|
<tal:s define="search searchElem">
|
||||||
|
<metal:s use-macro="app/ui/portlet/macros/search"/>
|
||||||
|
</tal:s>
|
||||||
|
</tal:search>
|
||||||
|
</tal:searchElem>
|
||||||
|
</tal:searchRows>
|
||||||
|
</div>
|
||||||
|
</metal:group>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">Macro displaying the whole portlet</tal:comment>
|
||||||
<metal:portlet define-macro="portlet"
|
<metal:portlet define-macro="portlet"
|
||||||
tal:define="queryUrl python: '%s/ui/query' % tool.absolute_url();
|
tal:define="queryUrl python: '%s/ui/query' % tool.absolute_url();
|
||||||
toolUrl tool/absolute_url;
|
toolUrl tool/absolute_url;
|
||||||
|
app tool/getApp;
|
||||||
|
appUrl app/absolute_url;
|
||||||
currentSearch req/search|nothing;
|
currentSearch req/search|nothing;
|
||||||
currentClass req/className|nothing;
|
currentClass req/className|nothing;
|
||||||
contextObj tool/getPublishedObject;
|
contextObj tool/getPublishedObject;
|
||||||
|
@ -16,7 +58,7 @@
|
||||||
<img tal:attributes="src string: $appUrl/ui/gotoSource.png"/>
|
<img tal:attributes="src string: $appUrl/ui/gotoSource.png"/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<metal:phases use-macro="here/ui/portlet/macros/phases"/>
|
<metal:phases use-macro="app/ui/portlet/macros/phases"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<tal:comment replace="nothing">One section for every searchable root class.</tal:comment>
|
<tal:comment replace="nothing">One section for every searchable root class.</tal:comment>
|
||||||
|
@ -26,7 +68,7 @@
|
||||||
<div class="portletSep" tal:define="nb repeat/rootClass/number"
|
<div class="portletSep" tal:define="nb repeat/rootClass/number"
|
||||||
tal:condition="python: (nb == 1 and contextObj) or (nb != 1)"></div>
|
tal:condition="python: (nb == 1 and contextObj) or (nb != 1)"></div>
|
||||||
|
|
||||||
<div class="portletContent" tal:define="searchInfo python: tool.getSearches(rootClass)">
|
<div class="portletContent" tal:define="searchInfo python: tool.getGroupedSearches(rootClass)">
|
||||||
<tal:comment replace="nothing">Section title (link triggers the default search), with action icons</tal:comment>
|
<tal:comment replace="nothing">Section title (link triggers the default search), with action icons</tal:comment>
|
||||||
<a tal:define="queryParam python: searchInfo['default'] and ('&search=%s' % searchInfo['default']['name']) or ''"
|
<a tal:define="queryParam python: searchInfo['default'] and ('&search=%s' % searchInfo['default']['name']) or ''"
|
||||||
tal:attributes="href python: '%s?className=%s%s' % (queryUrl, rootClass, queryParam);
|
tal:attributes="href python: '%s?className=%s%s' % (queryUrl, rootClass, queryParam);
|
||||||
|
@ -56,40 +98,17 @@
|
||||||
<img tal:attributes="src string: $appUrl/ui/search.gif"/>
|
<img tal:attributes="src string: $appUrl/ui/search.gif"/>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<tal:comment replace="nothing">Searches for this content type.</tal:comment>
|
<tal:comment replace="nothing">Searches for this content type</tal:comment>
|
||||||
<tal:searchOrGroup repeat="searchOrGroup searchInfo/searches">
|
<tal:widget repeat="widget searchInfo/searches">
|
||||||
<tal:group condition="searchOrGroup/isGroup">
|
<tal:group condition="python: widget['type'] == 'group'">
|
||||||
<tal:expanded define="group searchOrGroup;
|
<metal:s use-macro="app/ui/portlet/macros/group"/>
|
||||||
expanded python: request.get(group['labelId'], 'collapsed') == 'expanded'">
|
|
||||||
<tal:comment replace="nothing">Group name</tal:comment>
|
|
||||||
<div class="portletGroup">
|
|
||||||
<img style="cursor:pointer; margin-right: 3px"
|
|
||||||
tal:attributes="id python: '%s_img' % group['labelId'];
|
|
||||||
src python:test(expanded, 'ui/collapse.gif', 'ui/expand.gif');
|
|
||||||
onClick python:'toggleCookie(\'%s\')' % group['labelId'];
|
|
||||||
align dleft"/>
|
|
||||||
<span tal:replace="group/label"/>
|
|
||||||
</div>
|
|
||||||
<tal:comment replace="nothing">Group searches</tal:comment>
|
|
||||||
<div tal:attributes="id group/labelId;
|
|
||||||
style python:test(expanded, 'display:block', 'display:none')">
|
|
||||||
<div class="portletSearch" tal:repeat="search group/searches">
|
|
||||||
<a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']);
|
|
||||||
title search/descr;
|
|
||||||
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
|
|
||||||
tal:content="structure search/label"></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</tal:expanded>
|
|
||||||
</tal:group>
|
</tal:group>
|
||||||
<dt tal:define="search searchOrGroup" tal:condition="not: searchOrGroup/isGroup"
|
<tal:search condition="python: widget['type'] != 'group'">
|
||||||
class="portletAppyItem portletSearch">
|
<tal:s define="search widget">
|
||||||
<a tal:attributes="href python: '%s?className=%s&search=%s' % (queryUrl, rootClass, search['name']);
|
<metal:s use-macro="app/ui/portlet/macros/search"/>
|
||||||
title search/descr;
|
</tal:s>
|
||||||
class python: test(search['name'] == currentSearch, 'portletCurrent', '');"
|
</tal:search>
|
||||||
tal:content="structure search/label"></a>
|
</tal:widget>
|
||||||
</dt>
|
|
||||||
</tal:searchOrGroup>
|
|
||||||
</div>
|
</div>
|
||||||
</tal:section>
|
</tal:section>
|
||||||
</metal:portlet>
|
</metal:portlet>
|
||||||
|
|
|
@ -59,8 +59,8 @@ class Descr:
|
||||||
def get(self): return self.__dict__
|
def get(self): return self.__dict__
|
||||||
|
|
||||||
class GroupDescr(Descr):
|
class GroupDescr(Descr):
|
||||||
def __init__(self, group, page, metaType):
|
def __init__(self, group, page, metaType, forSearch=False):
|
||||||
'''Creates the data structure manipulated in ZPTs from p_group, the
|
'''Creates the data structure manipulated in ZPTs for p_group, the
|
||||||
Group instance used in the field definition.'''
|
Group instance used in the field definition.'''
|
||||||
self.type = 'group'
|
self.type = 'group'
|
||||||
# All p_group attributes become self attributes.
|
# All p_group attributes become self attributes.
|
||||||
|
@ -77,7 +77,9 @@ class GroupDescr(Descr):
|
||||||
else: # It is a tuple (metaType, name)
|
else: # It is a tuple (metaType, name)
|
||||||
if group.label[1]: labelName = group.label[1]
|
if group.label[1]: labelName = group.label[1]
|
||||||
if group.label[0]: prefix = group.label[0]
|
if group.label[0]: prefix = group.label[0]
|
||||||
self.labelId = '%s_group_%s' % (prefix, labelName)
|
if forSearch: gp = 'searchgroup'
|
||||||
|
else: gp = 'group'
|
||||||
|
self.labelId = '%s_%s_%s' % (prefix, gp, labelName)
|
||||||
self.descrId = self.labelId + '_descr'
|
self.descrId = self.labelId + '_descr'
|
||||||
self.helpId = self.labelId + '_help'
|
self.helpId = self.labelId + '_help'
|
||||||
# The name of the page where the group lies
|
# The name of the page where the group lies
|
||||||
|
|
Loading…
Reference in a new issue