[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 \
not self.group.label:
# 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
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
# p_groupDescrs.
if self.name not in groupDescrs:
groupDescr = groupDescrs[self.name] = GroupDescr(self, page,
metaType).get()
groupDescr = groupDescrs[self.name] = \
GroupDescr(self, page, metaType, forSearch=forSearch).get()
# Insert the group at the higher level (ie, directly in p_widgets)
# if the group is not itself in a group.
if not self.group:
widgets.append(groupDescr)
else:
outerGroupDescr = self.group.insertInto(widgets, groupDescrs,
page, metaType)
page, metaType, forSearch=forSearch)
GroupDescr.addWidget(outerGroupDescr, groupDescr)
else:
groupDescr = groupDescrs[self.name]
@ -291,7 +293,7 @@ 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, **fields):
default=False, colspan=1, **fields):
self.name = name
# Searches may be visually grouped in the portlet.
self.group = Group.get(group)
@ -301,6 +303,7 @@ class Search:
# If this search is the default one, it will be triggered by clicking
# on main link.
self.default = default
self.colspan = colspan
# In the dict below, keys are indexed field names and values are
# search values.
self.fields = fields

View file

@ -2,8 +2,8 @@
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
from appy.gen.utils import SomeObjects, getClassName
from appy.gen import Type, Search, Selection, String, Page
from appy.gen.utils import SomeObjects, getClassName, GroupDescr
from appy.gen.mixins import BaseMixin
from appy.gen.wrappers import AbstractWrapper
from appy.gen.descriptors import ClassDescriptor
@ -672,7 +672,7 @@ class ToolMixin(BaseMixin):
obj = self.getObject(objectUid)
return obj, fieldName
def getSearches(self, contentType):
def getGroupedSearches(self, contentType):
'''Returns an object with 2 attributes:
* "searches" stores the searches that are defined for p_contentType;
* "default" stores the search defined as the default one.
@ -680,37 +680,27 @@ class ToolMixin(BaseMixin):
search or about a group of searches.
'''
appyClass = self.getAppyClass(contentType)
searches = []
res = []
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):
# Determine first group label, we will need it.
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
# Compute the dict representing this search
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),
'descr': self.translate('%s_descr' % searchLabel)}
if groupName:
visitedGroups[groupName]['searches'].append(dSearch)
if not search.group:
# Insert the search at the highest level, not in any group.
res.append(dSearch)
else:
searches.append(dSearch)
if search.default:
default = dSearch
return Object(searches=searches, default=default).__dict__
groupDescr = search.group.insertInto(res, groups, page,
contentType, forSearch=True)
GroupDescr.addWidget(groupDescr, dSearch)
# 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):
'''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; }
.portletGroup { font-variant: small-caps; font-weight: bold; font-style: normal;
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;}
.phaseSelected { background-color: #F4F5F6; }
.content { padding: 14px 14px 9px 15px; background-color: #f1f1f1 }

View file

@ -1,9 +1,51 @@
<tal:comment replace="nothing">
This macro displays the content of the application portlet.
</tal:comment>
<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;
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"
tal:define="queryUrl python: '%s/ui/query' % tool.absolute_url();
toolUrl tool/absolute_url;
app tool/getApp;
appUrl app/absolute_url;
currentSearch req/search|nothing;
currentClass req/className|nothing;
contextObj tool/getPublishedObject;
@ -16,7 +58,7 @@
<img tal:attributes="src string: $appUrl/ui/gotoSource.png"/>
</a>
</div>
<metal:phases use-macro="here/ui/portlet/macros/phases"/>
<metal:phases use-macro="app/ui/portlet/macros/phases"/>
</div>
<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"
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>
<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);
@ -56,40 +98,17 @@
<img tal:attributes="src string: $appUrl/ui/search.gif"/>
</a>
</span>
<tal:comment replace="nothing">Searches for this content type.</tal:comment>
<tal:searchOrGroup repeat="searchOrGroup searchInfo/searches">
<tal:group condition="searchOrGroup/isGroup">
<tal:expanded define="group searchOrGroup;
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:comment replace="nothing">Searches for this content type</tal:comment>
<tal:widget repeat="widget searchInfo/searches">
<tal:group condition="python: widget['type'] == 'group'">
<metal:s use-macro="app/ui/portlet/macros/group"/>
</tal:group>
<dt tal:define="search searchOrGroup" tal:condition="not: searchOrGroup/isGroup"
class="portletAppyItem portletSearch">
<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>
</dt>
</tal:searchOrGroup>
<tal:search condition="python: widget['type'] != 'group'">
<tal:s define="search widget">
<metal:s use-macro="app/ui/portlet/macros/search"/>
</tal:s>
</tal:search>
</tal:widget>
</div>
</tal:section>
</metal:portlet>

View file

@ -59,8 +59,8 @@ class Descr:
def get(self): return self.__dict__
class GroupDescr(Descr):
def __init__(self, group, page, metaType):
'''Creates the data structure manipulated in ZPTs from p_group, the
def __init__(self, group, page, metaType, forSearch=False):
'''Creates the data structure manipulated in ZPTs for p_group, the
Group instance used in the field definition.'''
self.type = 'group'
# All p_group attributes become self attributes.
@ -77,7 +77,9 @@ class GroupDescr(Descr):
else: # It is a tuple (metaType, name)
if group.label[1]: labelName = group.label[1]
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.helpId = self.labelId + '_help'
# The name of the page where the group lies