Skip to content

Commit d043e6a

Browse files
Thibaut Borngotcha
Thibaut Born
authored andcommitted
Problem: customizing tabular view is hard
Solution: use views for table cells such that they can be overridden
1 parent 9744eae commit d043e6a

File tree

7 files changed

+187
-51
lines changed

7 files changed

+187
-51
lines changed

news/customize-tabular.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Enable customization of tabular_view via views for fields of contentlisting items.

plone/app/contenttypes/browser/configure.zcml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,36 @@
246246
<browser:page name="thumbnail_view" template="templates/listing_album.pt"/>
247247
</browser:pages>
248248

249+
<browser:page
250+
name="tabular-cell-Title"
251+
for="plone.app.contentlisting.interfaces.IContentListingObject"
252+
class=".tabular.TitleCell"
253+
layer="plone.app.contenttypes.interfaces.IPloneAppContenttypesLayer"
254+
permission="zope2.View"
255+
/>
256+
257+
<browser:page
258+
name="tabular-cell-Creator"
259+
for="plone.app.contentlisting.interfaces.IContentListingObject"
260+
class=".tabular.CreatorCell"
261+
layer="plone.app.contenttypes.interfaces.IPloneAppContenttypesLayer"
262+
permission="zope2.View"
263+
/>
264+
265+
<browser:page
266+
name="tabular-cell-icon"
267+
for="plone.app.contentlisting.interfaces.IContentListingObject"
268+
class=".tabular.IconCell"
269+
layer="plone.app.contenttypes.interfaces.IPloneAppContenttypesLayer"
270+
permission="zope2.View"
271+
/>
272+
273+
<browser:page
274+
name="tabular-cell-image"
275+
for="plone.app.contentlisting.interfaces.IContentListingObject"
276+
class=".tabular.ImageCell"
277+
layer="plone.app.contenttypes.interfaces.IPloneAppContenttypesLayer"
278+
permission="zope2.View"
279+
/>
280+
249281
</configure>

plone/app/contenttypes/browser/folder.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@
1212
from Products.CMFPlone.interfaces import ISiteSchema
1313
from Products.CMFPlone.PloneBatch import Batch
1414
from Products.CMFPlone.utils import safe_callable
15+
from Products.CMFPlone import PloneMessageFactory
1516
from Products.Five import BrowserView
1617
from zope.component import getMultiAdapter
1718
from zope.component import getUtility
1819
from zope.contentprovider.interfaces import IContentProvider
20+
from zope.component import queryMultiAdapter
21+
from zope.i18n import translate
22+
from zope.i18nmessageid import Message
1923

2024
import random
2125

@@ -33,6 +37,7 @@ class FolderView(BrowserView):
3337
_plone_view = None
3438
_portal_state = None
3539
_pas_member = None
40+
_image_scale = None
3641

3742
@property
3843
def plone_view(self):
@@ -52,6 +57,16 @@ def portal_state(self):
5257
)
5358
return self._portal_state
5459

60+
@property
61+
def image_scale(self):
62+
if not self._image_scale:
63+
portal = self.portal_state.portal()
64+
self._image_scale = getMultiAdapter(
65+
(portal, self.request),
66+
name=u'image_scale'
67+
)
68+
return self._image_scale
69+
5570
@property
5671
def pas_member(self):
5772
if not self._pas_member:
@@ -172,7 +187,10 @@ def tabular_field_label(self, field):
172187
"""Return the internationalized label (Message object) corresponding
173188
to the field.
174189
"""
175-
return get_field_label(field)
190+
label = get_field_label(field)
191+
if not isinstance(label, Message):
192+
return PloneMessageFactory(label)
193+
return label
176194

177195
def tabular_fielddata(self, item, fieldname):
178196
value = getattr(item, fieldname, '')
@@ -198,6 +216,38 @@ def tabular_fielddata(self, item, fieldname):
198216
'value': value
199217
}
200218

219+
def render_cells(self, item):
220+
result = []
221+
icon_cell_view = queryMultiAdapter(
222+
(item, self.request),
223+
name=f"tabular-cell-icon"
224+
)
225+
if icon_cell_view is not None:
226+
icon_cell_view.table_view = self
227+
result.append(icon_cell_view())
228+
for field in self.tabular_fields:
229+
if field == 'getIcon':
230+
continue
231+
cell_view = queryMultiAdapter(
232+
(item, self.request),
233+
name=f"tabular-cell-{field}"
234+
)
235+
if cell_view is not None:
236+
cell_view.table_view = self
237+
result.append(cell_view())
238+
else:
239+
field_data = self.tabular_fielddata(item, field)
240+
value = translate(field_data['value'], context=self.request)
241+
result.append('<td class="text-nowrap">%s</td>' % value)
242+
image_cell_view = queryMultiAdapter(
243+
(item, self.request),
244+
name=f"tabular-cell-image"
245+
)
246+
if image_cell_view is not None:
247+
image_cell_view.table_view = self
248+
result.append(image_cell_view())
249+
return ''.join(result)
250+
201251
def is_event(self, obj):
202252
if getattr(obj, 'getObject', False):
203253
obj = obj.getObject()
@@ -274,6 +324,10 @@ def get_thumb_scale_table(self):
274324
return None
275325
return settings.thumb_scale_table
276326

327+
@property
328+
def img_class(self):
329+
return 'thumb-%s pull-right' % self.get_thumb_scale_table()
330+
277331
@memoize
278332
def get_thumb_scale_list(self):
279333
if getattr(self.context, 'suppress_thumbs', False):
@@ -304,3 +358,7 @@ def get_thumb_scale_summary(self):
304358

305359
def show_icons(self):
306360
return not getattr(self.context, 'suppress_icons', False)
361+
362+
@property
363+
def iconresolver(self):
364+
return self.context.restrictedTraverse("@@iconresolver")
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from Products.Five import BrowserView
2+
3+
# BEWARE: the cell views are registered for ContentListingObject
4+
# which are not acquisition aware.
5+
# That precludes using Products.Five.ViewPageTemplateFile
6+
# and imposes to use zope.browserpage.viewpagetemplatefile.
7+
from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
8+
# BEWARE
9+
10+
class TitleCell(BrowserView):
11+
__call__ = ViewPageTemplateFile("templates/titlecell.pt")
12+
13+
@property
14+
def title(self):
15+
return self.context.Title() or self.context.getId()
16+
17+
@property
18+
def link(self):
19+
suffix = (
20+
"/view"
21+
if self.context.PortalType in self.table_view.use_view_action
22+
else ""
23+
)
24+
return self.context.getURL() + suffix
25+
26+
@property
27+
def type_class(self):
28+
return ("contenttype/" +
29+
self.table_view.normalizeString(self.context.PortalType())
30+
if self.table_view.show_icons else '')
31+
32+
@property
33+
def wf_state_class(self):
34+
return ("state-" +
35+
self.table_view.normalizeString(self.context.review_state()))
36+
37+
38+
class CreatorCell(BrowserView):
39+
__call__ = ViewPageTemplateFile("templates/creatorcell.pt")
40+
41+
@property
42+
def author(self):
43+
return self.table_view.pas_member.info(self.context.Creator)
44+
45+
@property
46+
def author_name(self):
47+
return self.author["fullname"] or self.author["username"]
48+
49+
50+
class IconCell(BrowserView):
51+
def __call__(self):
52+
item = self.context
53+
item_type = item.PortalType()
54+
if item_type == "File":
55+
icon_type = "mimetype-" + item.mime_type
56+
elif self.table_view.show_icons:
57+
icon_type = ("contenttype/"
58+
+ self.table_view.normalizeString(item_type))
59+
else:
60+
icon_type = ""
61+
icon = self.table_view.iconresolver.tag(icon_type).decode("utf8")
62+
return "<td>" + icon + "</td>"
63+
64+
65+
class ImageCell(BrowserView):
66+
67+
def render_image(self):
68+
thumb_scale_table = self.table_view.get_thumb_scale_table()
69+
if thumb_scale_table and self.context.getIcon:
70+
img_class = self.table_view.img_class
71+
return self.table_view.image_scale.tag(
72+
self.context, "image",
73+
scale=thumb_scale_table, css_class=img_class
74+
)
75+
else:
76+
return ""
77+
78+
def __call__(self):
79+
image = self.render_image()
80+
return "<td>" + image + "</td>"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<td class="text-nowrap">
2+
<a tal:condition="view/author"
3+
tal:attributes="href string:${view/table_view/navigation_root_url}/author/${context/Creator}"
4+
tal:content="view/author_name">Jos Henken</a>
5+
</td>

plone/app/contenttypes/browser/templates/listing_tabular.pt

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -31,58 +31,11 @@
3131
<th class="text-nowrap" i18n:translate="image" tal:condition="python:thumb_scale_table">Image</th>
3232
</tr>
3333
</thead>
34-
3534
<tbody tal:define="portal python:portal_state.portal(); image_scale portal/@@image_scale">
36-
<tal:entries tal:repeat="item python:batch">
37-
<tal:block tal:define="item_url python:item.getURL();
38-
item_id python:item.getId();
39-
item_title python:item.Title();
40-
item_title python:item_title or item_id;
41-
item_type python:item.PortalType();
42-
item_type_class python:'contenttype/' + view.normalizeString(item_type) if showicons else '';
43-
item_wf_state python:item.review_state();
44-
item_wf_state_class python:'state-' + view.normalizeString(item_wf_state);
45-
item_creator python:item.Creator();
46-
item_has_image python:item.getIcon;
47-
item_link python:item_type in view.use_view_action and item_url+'/view' or item_url;
48-
item_mime_type python:item.mime_type;
49-
item_mime_type_icon python: 'mimetype-' + item_mime_type;
50-
">
51-
52-
<tr metal:define-macro="listitem">
53-
54-
<td>
55-
<tal:icon tal:condition="python: item_type == 'File'" tal:replace="structure python:icons.tag(item_mime_type_icon)" />
56-
<tal:icon tal:condition="python: item_type != 'File'" tal:replace="structure python:icons.tag(item_type_class)" />
57-
</td>
58-
59-
<tal:block tal:repeat="field python:tabular_fields">
60-
61-
<td class="text-nowrap" tal:condition="python:field not in ['Title', 'Creator', 'getIcon']" tal:define="field_data python:view.tabular_fielddata(item, field)">
62-
<tal:block tal:replace="python: field_data.get('value')" />
63-
</td>
64-
65-
<td class="text-nowrap" tal:condition="python:field == 'Title'">
66-
<a tal:attributes="href python:item_link; class string:$item_type_class $item_wf_state_class url; title python:item_type" tal:content="python: item_title">
67-
Item Title
68-
</a>
69-
</td>
70-
71-
<td class="text-nowrap" tal:condition="python:field == 'Creator'" tal:define="author python:view.pas_member.info(item_creator); name python:author['fullname'] or author['username']">
72-
<a tal:condition="python: author" tal:attributes="href string:${view.navigation_root_url}/author/${item_creator}" tal:content="python: name">Jos Henken</a>
73-
</td>
74-
75-
</tal:block>
76-
77-
<td>
78-
<a tal:condition="python:item_has_image and thumb_scale_table">
79-
<img tal:attributes="href python: item_link" tal:replace="structure python:image_scale.tag(item, 'image', scale=thumb_scale_table, css_class=img_class)" />
80-
</a>
81-
</td>
82-
35+
<tal:entries tal:repeat="item batch">
36+
<tr metal:define-macro="listitem"
37+
tal:content="structure python:view.render_cells(item)">
8338
</tr>
84-
85-
</tal:block>
8639
</tal:entries>
8740
</tbody>
8841
</table>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<td class="text-nowrap">
2+
<a tal:attributes="href view/link;
3+
class string:${view/type_class} ${view/wf_state_class} url;
4+
title context/PortalType"
5+
tal:content="view/title">Item Title
6+
</a>
7+
</td>

0 commit comments

Comments
 (0)