[gen] Searches can now be grouped into groups of groups of..., creating a tree structure in the portlet.

This commit is contained in:
Gaetan Delannay 2012-11-14 11:36:48 +01:00
parent 6cf29778b6
commit 4872e5d8b8
6 changed files with 92 additions and 78 deletions

Binary file not shown.

View file

@ -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

View file

@ -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)

View file

@ -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 }

View file

@ -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>

View file

@ -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