[gen] Added field appyclass.breadcrumb, allowing to show/hide the breadcrumb when displaying instances of this class; added field appyclass.resultMode, allowing to choose between 'list' or 'grid' mode (previously, only list mode was enabled) when showing instances of this class as a result of some query.

This commit is contained in:
Gaetan Delannay 2013-03-09 16:06:12 +01:00
parent 46f5b8e464
commit c5ec54f0e5
4 changed files with 182 additions and 129 deletions

View file

@ -9,7 +9,8 @@ from appy.gen.wrappers import AbstractWrapper
from appy.gen.descriptors import ClassDescriptor
from appy.gen.mail import sendMail
from appy.shared import mimeTypes
from appy.shared.utils import getOsTempFolder, sequenceTypes, normalizeString
from appy.shared.utils import getOsTempFolder, sequenceTypes, normalizeString, \
splitList
from appy.shared.data import languages
try:
from AccessControl.ZopeSecurityPolicy import _noroles
@ -224,6 +225,13 @@ class ToolMixin(BaseMixin):
for key in self.queryParamNames])
return res
def getResultMode(self, className):
'''Must we show, on result.pt, instances of p_className as a list or
as a grid?'''
klass = self.getAppyClass(className)
if hasattr(klass, 'resultMode'): return klass.resultMode
return 'list' # The default mode
def getImportElements(self, contentType):
'''Returns the list of elements that can be imported from p_path for
p_contentType.'''
@ -416,6 +424,11 @@ class ToolMixin(BaseMixin):
return '<acronym title="%s">%s</acronym>' % \
(text, uText[:width].encode('utf-8') + '...')
def splitList(self, l, sub):
'''Returns a list made of the same elements as p_l, but grouped into
sub-lists of p_sub elements.'''
return splitList(l, sub)
def getLayoutType(self):
'''Guess the current layout type, according to actual URL.'''
actualUrl = self.REQUEST['ACTUAL_URL']

View file

@ -1606,7 +1606,12 @@ class BaseMixin:
if parent.meta_type not in ('Folder', 'Temporary Folder'): return parent
def getBreadCrumb(self):
'''Gets breadcrumb info about this object and its parents.'''
'''Gets breadcrumb info about this object and its parents (if it must
be shown).'''
# Return an empty breadcrumb if it must not be shown.
klass = self.getClass()
if hasattr(klass, 'breadcrumb') and not klass.breadcrumb: return ()
# Compute the breadcrumb
res = [{'url': self.absolute_url(),
'title': self.getFieldValue('title', layoutType='view')}]
parent = self.getParent()
@ -1746,7 +1751,8 @@ class BaseMixin:
appyType = self.getAppyType(name)
else:
appyType = self.getAppyType(name.split('_img_')[0])
if not appyType.isShowable(self, 'view'):
if (not appyType.isShowable(self, 'view')) and \
(not appyType.isShowable(self, 'result')):
from zExceptions import NotFound
raise NotFound()
theFile = getattr(self.aq_base, name, None)

View file

@ -1,141 +1,141 @@
body { font: 75% Helvetica,Arial,sans-serif; background-color: #EAEAEA;
margin-top: 18px}
pre { font: 100% Helvetica,Arial,sans-serif; margin: 0}
h1 { font-size: 14pt; margin:6px 0 6px 0}
margin-top: 18px }
pre { font: 100% Helvetica,Arial,sans-serif; margin: 0 }
h1 { font-size: 14pt; margin:6px 0 6px 0 }
h2 { font-size: 13pt; margin:6px 0 6px 0; font-style: italic;
font-weight: normal}
h3 { font-size: 12pt; margin:4px 0 4px 0; font-weight: bold;}
h4 { font-size: 11pt; margin:4px 0 4px 0}
font-weight: normal }
h3 { font-size: 12pt; margin:4px 0 4px 0; font-weight: bold }
h4 { font-size: 11pt; margin:4px 0 4px 0 }
h5 { font-size: 10pt; margin:0; font-style: italic; font-weight: normal;
background-color: #d7dee4}
h6 { font-size: 9pt; margin:0; font-weight: bold;}
a { text-decoration: none; color: #436976;}
a:visited { color: #436976;}
background-color: #d7dee4 }
h6 { font-size: 9pt; margin:0; font-weight: bold }
a { text-decoration: none; color: #436976 }
a:visited { color: #436976 }
table { font-size: 100%; border-spacing: 0px; border-collapse:collapse;}
form { margin: 0; padding: 0;}
p { margin: 0;}
acronym {cursor: help;}
form { margin: 0; padding: 0 }
p { margin: 0 }
acronym { cursor: help }
input { font: 92% Helvetica,Arial,sans-serif }
input[type=image] { border: 0; background: none; cursor: pointer; }
input[type=checkbox] { border: 0; background: none; cursor: pointer;}
input[type=radio] { border: 0; background: none; cursor: pointer;}
input[type=image] { border: 0; background: none; cursor: pointer }
input[type=checkbox] { border: 0; background: none; cursor: pointer }
input[type=radio] { border: 0; background: none; cursor: pointer }
input[type=file] { border: 0px solid #d0d0d0;
background-color: #f8f8f8; cursor: pointer;}
background-color: #f8f8f8; cursor: pointer }
input[type=button] { border: 1px solid #d0d0d0;
background-color: #f8f8f8; cursor: pointer;}
background-color: #f8f8f8; cursor: pointer }
input[type=submit] { border: 1px solid #d0d0d0; background-color: #f8f8f8;
cursor: pointer; }
cursor: pointer }
input[type=password] { border: 1px solid #d0d0d0; background-color: #f8f8f8;
font-family: Helvetica,Arial,sans-serif;}
font-family: Helvetica,Arial,sans-serif }
input[type=text] { border: 1px solid #d0d0d0; background-color: #f8f8f8;
font-family: Helvetica,Arial,sans-serif;
margin-bottom: 1px}
select { border: 1px solid #d0d0d0; background-color: #f8f8f8;}
margin-bottom: 1px }
select { border: 1px solid #d0d0d0; background-color: #f8f8f8 }
textarea { width: 99%; font: 100% Helvetica,Arial,sans-serif;
border: 1px solid #d0d0d0; background-color: #f8f8f8;}
label { font-weight: 600; font-style: italic; line-height: 1.4em;}
legend { padding-bottom: 2px; padding-right: 3px; color: black;}
border: 1px solid #d0d0d0; background-color: #f8f8f8 }
label { font-weight: 600; font-style: italic; line-height: 1.4em }
legend { padding-bottom: 2px; padding-right: 3px; color: black }
ul { line-height: 1.2em; margin: 0 0 0.2em 0.6em; padding: 0;
list-style: none outside none;}
list-style: none outside none }
ul li { margin: 0; background-image: url("ui/li.gif"); padding-left: 10px;
background-repeat: no-repeat; background-position: 0 4px;}
img { border: 0; vertical-align: middle}
background-repeat: no-repeat; background-position: 0 4px }
img { border: 0; vertical-align: middle }
/* Styles that apply when viewing content of XHTML fields, that mimic styles
that ckeditor uses for displaying XHTML content in the edit view. */
.xhtml { margin-top: 5px; background-color: white;
padding: 6px; border: 1px dashed grey; border-radius: 0.3em }
.xhtml img { margin-right: 5px }
.xhtml p { margin: 3px 0 7px 0}
.xhtml p { margin: 3px 0 7px 0 }
.main { width: 900px; background-color: white; box-shadow: 3px 3px 3px #A9A9A9;
border-style: solid; border-width: 1px; border-color: grey}
.top { height: 89px; margin-left: 3em; vertical-align: top;}
.lang { margin-right: 6px; }
border-style: solid; border-width: 1px; border-color: grey }
.top { height: 89px; margin-left: 3em; vertical-align: top }
.lang { margin-right: 6px }
.userStrip { background-color: #6282B3; height: 35px;
border-top: 3px solid #034984; border-bottom: 2px solid #034984; }
border-top: 3px solid #034984; border-bottom: 2px solid #034984 }
.userStripText { padding: 0 0.3em 0 0.3em; color: white }
.userStrip a { color: #e7e7e7 }
.userStrip a:visited { color: #e7e7e7 }
.navigate { border-bottom: 1px solid #5F7983;
background-color: #dbdde1; font-weight: bold }
.navigate td { padding: 4px 9px }
.login { margin-top: 2px; margin-bottom: 2px; color: black;}
.buttons { margin-left: 4px;}
.login { margin-top: 2px; margin-bottom: 2px; color: black }
.buttons { margin-left: 4px }
.fakeButton { border: 1px solid #D7DEE4; background-color: #fde8e0;
padding: 0px 8px 2px; font: italic 92% Helvetica,Arial,sans-serif}
.message { position: absolute; top: -40px; left: 150px; font-size: 90%;
width: 600px; border: 1px #F0C36D solid; padding: 6px;
background-color: #F9EDBE; text-align: center;
border-radius: 2px 2px 2px 2px; box-shadow: 0 2px 4px #A9A9A9;}
border-radius: 2px 2px 2px 2px; box-shadow: 0 2px 4px #A9A9A9 }
.focus { font-size: 90%; margin: 7px 0 7px 0; padding: 7px;
background-color: #d7dee4; border-radius: 2px 2px 2px 2px;
box-shadow: 0 2px 4px #A9A9A9 }
.focus td { padding: 4px 0px 4px 4px }
.discreet { font-size: 90%; color: grey }
.lostPassword a { font-size: 90%; color: white; padding-left: 1em;}
.lostPassword a { font-size: 90%; color: white; padding-left: 1em }
.portlet { width: 150px; border-right: 1px solid #5F7983;
background-color: #ededed}
background-color: #ededed }
.portletContent { margin: 4px 9px }
.portletTitle { font-weight: bold; font-size: 110%; margin-bottom: 4px;}
.portletCurrent { font-weight: bold; }
.portletSep { border-top: 1px solid #5F7983; margin-top: 2px;}
.portletTitle { font-weight: bold; font-size: 110%; margin-bottom: 4px }
.portletCurrent { font-weight: bold }
.portletSep { border-top: 1px solid #5F7983; margin-top: 2px }
.portletPage { font-style: italic }
.portletGroup { font-variant: small-caps; font-weight: bold; font-size: 105%;
margin: 0.1em 0 0.3em ; border-bottom: 1px dashed grey}
margin: 0.1em 0 0.3em ; border-bottom: 1px dashed grey }
.portletSearch { font-size: 90%; font-style: italic }
.content { padding: 14px 14px 9px 15px; background-color: #f1f1f1 }
.grey { display: none; position: absolute; left: 0px; top: 0px;
background:grey; opacity:0.5; -moz-opacity:0.5; -khtml-opacity:0.5;
filter:alpha(Opacity=50);}
filter:alpha(Opacity=50) }
.popup { display: none; position: absolute; top: 30%; left: 35%;
width: 350px; z-index : 100; background: white; padding: 8px;
border: 1px solid grey; }
.list { border: 1px solid grey; margin-bottom: 3px;}
border: 1px solid grey }
.list { border: 1px solid grey; margin-bottom: 3px }
.list td, .list th { border: 1px solid grey;
padding-left: 5px; padding-right: 5px; padding-top: 3px;}
padding-left: 5px; padding-right: 5px; padding-top: 3px }
.list th { background-color: #d7dee4; font-style: italic; font-weight: normal }
.grid th { font-style: italic; font-weight: normal;
border-bottom: 2px solid grey; padding: 2px 2px }
.grid td { padding-right: 5px }
.cellGap { padding-right: 0.4em }
.cellDashed { border: 1px dashed grey !important }
.noStyle { border: 0 !important; padding: 0 !important; margin: 0 !important; }
.noStyle td { border:0 !important; padding:0 !important; margin:0 !important; }
.noStyle { border: 0 !important; padding: 0 !important; margin: 0 !important }
.noStyle td { border:0 !important; padding:0 !important; margin:0 !important }
.translationLabel { background-color: #EAEAEA; border-bottom: 1px dashed grey;
margin-top: 0.5em; margin-bottom: 0.5em; }
margin-top: 0.5em; margin-bottom: 0.5em }
.section1 { font-size: 120%; margin: 0.45em 0em 0.1em 0;
padding: 0.3em 0em 0.2em 0.1em; background-color: #eef3f5;
border-top: 1px solid #8CACBB;border-bottom: 1px solid #8CACBB; }
border-top: 1px solid #8CACBB;border-bottom: 1px solid #8CACBB }
.section2 { font-size: 110%; font-style: italic; margin: 0.45em 0em 0.2em 0;
border-bottom: 2px solid grey; }
border-bottom: 2px solid grey }
.section3 { font-size: 100%; font-style: italic; font-weight: bold;
margin: 0.45em 0em 0.1em 0; background-color: #95a1b3;
text-align: center; color: white; }
.odd { background-color: #f9f9f9; }
.even { background-color: #f4f4f4; }
text-align: center; color: white }
.odd { background-color: #f9f9f9 }
.even { background-color: #f4f4f4 }
.summary { margin-bottom: 5px; background-color: #e9e9e9;
border: 1px dashed grey }
.objectTitle { font-size: 11pt; border-bottom: 3px solid grey;
font-weight: bold;}
font-weight: bold }
.by { padding: 5px; color: grey; font-size: 97% }
.underline { border-bottom: 1px dotted grey;}
.state { font-weight: bold; border-bottom: 1px dashed grey;}
.historyLabel { font-variant: small-caps; font-weight: bold;}
.underline { border-bottom: 1px dotted grey }
.state { font-weight: bold; border-bottom: 1px dashed grey }
.historyLabel { font-variant: small-caps; font-weight: bold }
.history td { border-top: 1px solid grey; padding: 0 5px 0 5px }
.history th { font-style: italic; text-align: left; padding: 0 5px 0 5px }
.topSpace { margin-top: 15px;}
.bottomSpace { margin-bottom: 15px;}
.topSpace { margin-top: 15px }
.bottomSpace { margin-bottom: 15px }
.pageLink { padding-left: 8px }
.footer { font-size: 95% }
.footer td { background-color: #CBCBC9; border-top: 1px solid grey;
padding: 0.4em 1em 0.5em }
.code { font-family: "Lucida Console","Courier New";}
.code { font-family: "Lucida Console","Courier New" }
.codePara { background-color: #EEFFCC; border-color: grey;
border-style: solid none; border-width: 1px medium;
color: #333333; line-height: 120%;
padding: 10px; margin: 10px 0 10px 0}
.homeTable { background-color: #E3E3E3; border-top: 1px solid grey}
.homeTable td { padding: 10px 5px 10px 10px}
padding: 10px; margin: 10px 0 10px 0 }
.homeTable { background-color: #E3E3E3; border-top: 1px solid grey }
.homeTable td { padding: 10px 5px 10px 10px }
.homeTable th { padding-top: 5px; font-size: 105% }

View file

@ -1,3 +1,87 @@
<tal:comment replace="nothing">Show field content of a given object.</tal:comment>
<metal:field define-macro="field">
<tal:comment replace="nothing">Title</tal:comment>
<tal:title condition="python: widget['name'] == 'title'">
<tal:icons replace="structure obj/getIcons"/>
<a tal:define="navInfo python:'search.%s.%s.%d.%d' % (className, searchName, repeat['obj'].number()+startNumber, totalNumber);"
tal:content="obj/Title" tal:attributes="href python: obj.getUrl(nav=navInfo, page=obj.getDefaultViewPage())"></a>
<div name="subTitle" tal:content="structure obj/getSubTitle"
tal:attributes="style python: showSubTitles and 'display:block' or 'display:none'"></div>
<tal:comment replace="nothing">Actions: edit, delete</tal:comment>
<div tal:attributes="align dright" tal:condition="obj/mayAct">
<a tal:define="navInfo python:'search.%s.%s.%d.%d' % (className, searchName, repeat['obj'].number()+startNumber, totalNumber);"
tal:attributes="href python: obj.getUrl(mode='edit', page=obj.getDefaultEditPage(), nav=navInfo)"
tal:condition="obj/mayEdit">
<img tal:attributes="src string: $appUrl/ui/edit.png;
title python: _('object_edit')"/></a><img
tal:condition="obj/mayDelete" style="cursor:pointer"
tal:attributes="src string: $appUrl/ui/delete.png;
title python: _('object_delete');
onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/>
</div>
</tal:title>
<tal:comment replace="nothing">Any other field</tal:comment>
<tal:other condition="python: widget['name'] != 'title'">
<tal:field define="contextObj python:obj;
layoutType python:'cell';
innerRef python:True"
condition="python: contextObj.showField(widget['name'], 'result')">
<metal:f use-macro="context/ui/widgets/show/macros/field"/>
</tal:field>
</tal:other>
</metal:field>
<tal:comment replace="nothing">Show query results as a list.</tal:comment>
<table metal:define-macro="list" class="list" width="100%">
<tal:comment replace="nothing">Headers, with filters and sort arrows</tal:comment>
<tr>
<tal:header repeat="column columns">
<th tal:define="widget column/field;
sortable python: tool.isSortable(widget['name'], className, 'search');
filterable widget/filterable|nothing"
tal:attributes="width column/width; align column/align">
<span tal:replace="structure python: tool.truncateText(_(widget['labelId']))"/>
<metal:icons use-macro="context/ui/navigate/macros/sortAndFilter"/>
<metal:details use-macro="context/ui/navigate/macros/showDetails"/>
</th>
</tal:header>
</tr>
<tal:comment replace="nothing">Results</tal:comment>
<tal:row repeat="obj objs">
<tr id="query_row" valign="top" tal:define="odd repeat/obj/odd"
tal:attributes="class python:test(odd, 'even', 'odd')">
<tal:fields repeat="column columns">
<td tal:define="widget column/field"
tal:attributes="id python:'field_%s' % widget['name'];
width column/width; align column/align">
<metal:f use-macro="context/ui/result/macros/field"/>
</td>
</tal:fields>
</tr>
</tal:row>
</table>
<tal:comment replace="nothing">Show query results as a grid.</tal:comment>
<table metal:define-macro="grid" width="100%"
tal:define="modeElems python: resultMode.split('_');
cols python: (len(modeElems)==2) and int(modeElems[1]) or 4;
rows python: tool.splitList(objs, cols)">
<tr tal:repeat="row rows" valign="middle">
<td tal:repeat="obj row" tal:attributes="width python: '%d%%'%(100/cols)"
align="center" style="padding-top: 8px">
<tal:col repeat="column columns">
<tal:field define="widget column/field">
<metal:f use-macro="context/ui/result/macros/field"/>
</tal:field>
</tal:col>
</td>
</tr>
</table>
<tal:comment replace="nothing">Show paginated query results as a list or grid.</tal:comment>
<div id="queryResult" metal:define-macro="queryResult"
tal:define="_ python: tool.translate;
className request/className;
@ -20,7 +104,8 @@
ajaxHookId python:'queryResult';
navBaseCall python: 'askQueryResult(\'%s\',\'%s\',\'%s\',\'%s\',**v**)' % (ajaxHookId, tool.absolute_url(), className, searchName);
newSearchUrl python: '%s/ui/search?className=%s%s' % (tool.absolute_url(), className, refUrlPart);
showSubTitles python: request.get('showSubTitles', 'true') == 'true'">
showSubTitles python: request.get('showSubTitles', 'true') == 'true';
resultMode python: tool.getResultMode(className)">
<tal:result condition="objs">
<tal:comment replace="nothing">Display here POD templates if required.</tal:comment>
@ -56,68 +141,17 @@
</td>
</tr>
</table>
<table tal:define="columnLayouts python: tool.getResultColumnsLayouts(className, refInfo);
columns python: objs[0].getColumnsSpecifiers(columnLayouts, dir)"
class="list" width="100%">
<tal:comment replace="nothing">Headers, with filters and sort arrows</tal:comment>
<tr>
<tal:header repeat="column columns">
<th tal:define="widget column/field;
sortable python: tool.isSortable(widget['name'], className, 'search');
filterable widget/filterable|nothing"
tal:attributes="width column/width; align column/align">
<span tal:replace="structure python: tool.truncateText(_(widget['labelId']))"/>
<metal:icons use-macro="context/ui/navigate/macros/sortAndFilter"/>
<metal:details use-macro="context/ui/navigate/macros/showDetails"/>
</th>
</tal:header>
</tr>
<tal:comment replace="nothing">Results</tal:comment>
<tal:row repeat="obj objs">
<tr id="query_row" valign="top" tal:define="odd repeat/obj/odd"
tal:attributes="class python:test(odd, 'even', 'odd')">
<tal:fields repeat="column columns">
<td tal:define="widget column/field"
tal:attributes="id python:'field_%s' % widget['name'];
width column/width; align column/align">
<tal:comment replace="nothing">Title</tal:comment>
<tal:title condition="python: widget['name'] == 'title'">
<tal:icons replace="structure obj/getIcons"/>
<a tal:define="navInfo python:'search.%s.%s.%d.%d' % (className, searchName, repeat['obj'].number()+startNumber, totalNumber);"
tal:content="obj/Title" tal:attributes="href python: obj.getUrl(nav=navInfo, page=obj.getDefaultViewPage())"></a>
<div name="subTitle" tal:content="structure obj/getSubTitle"
tal:attributes="style python: showSubTitles and 'display:block' or 'display:none'"></div>
<tal:comment replace="nothing">Actions: edit, delete</tal:comment>
<div tal:attributes="align dright" tal:condition="obj/mayAct">
<a tal:define="navInfo python:'search.%s.%s.%d.%d' % (className, searchName, repeat['obj'].number()+startNumber, totalNumber);"
tal:attributes="href python: obj.getUrl(mode='edit', page=obj.getDefaultEditPage(), nav=navInfo)"
tal:condition="obj/mayEdit">
<img tal:attributes="src string: $appUrl/ui/edit.png;
title python: _('object_edit')"/></a><img
tal:condition="obj/mayDelete" style="cursor:pointer"
tal:attributes="src string: $appUrl/ui/delete.png;
title python: _('object_delete');
onClick python:'onDeleteObject(\'%s\')' % obj.UID()"/>
</div>
</tal:title>
<tal:comment replace="nothing">Any other field</tal:comment>
<tal:other condition="python: widget['name'] != 'title'">
<tal:field define="contextObj python:obj;
layoutType python:'cell';
innerRef python:True"
condition="python: contextObj.showField(widget['name'], 'result')">
<metal:field use-macro="context/ui/widgets/show/macros/field"/>
</tal:field>
</tal:other>
</td>
</tal:fields>
</tr>
</tal:row>
</table>
<tal:comment replace="nothing">Results, as a list or grid</tal:comment>
<tal:res define="columnLayouts python: tool.getResultColumnsLayouts(className, refInfo);
columns python: objs[0].getColumnsSpecifiers(columnLayouts, dir)">
<tal:asList condition="python: resultMode == 'list'">
<metal:list use-macro="context/ui/result/macros/list"/>
</tal:asList>
<tal:asGrid condition="python: resultMode != 'list'">
<metal:grid use-macro="context/ui/result/macros/grid"/>
</tal:asGrid>
</tal:res>
<tal:comment replace="nothing">Appy (bottom) navigation</tal:comment>
<metal:nav use-macro="here/ui/navigate/macros/appyNavigate"/>