Skip to content

Commit a18566d

Browse files
ThibautBorngotcha
authored andcommitted
Problem: customizing tabular view is hard
Solution: use views for table cells such that they can be overridden
1 parent 4e0aa66 commit a18566d

File tree

7 files changed

+192
-86
lines changed

7 files changed

+192
-86
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
@@ -223,4 +223,36 @@
223223
name="plone.app.contenttypes.migration.changed_base_classes"
224224
/>
225225

226+
<browser:page
227+
name="tabular-cell-Title"
228+
for="plone.app.contentlisting.interfaces.IContentListingObject"
229+
class=".tabular.TitleCell"
230+
permission="zope2.View"
231+
layer="plone.app.contenttypes.interfaces.IPloneAppContenttypesLayer"
232+
/>
233+
234+
<browser:page
235+
name="tabular-cell-Creator"
236+
for="plone.app.contentlisting.interfaces.IContentListingObject"
237+
class=".tabular.CreatorCell"
238+
permission="zope2.View"
239+
layer="plone.app.contenttypes.interfaces.IPloneAppContenttypesLayer"
240+
/>
241+
242+
<browser:page
243+
name="tabular-cell-icon"
244+
for="plone.app.contentlisting.interfaces.IContentListingObject"
245+
class=".tabular.IconCell"
246+
permission="zope2.View"
247+
layer="plone.app.contenttypes.interfaces.IPloneAppContenttypesLayer"
248+
/>
249+
250+
<browser:page
251+
name="tabular-cell-image"
252+
for="plone.app.contentlisting.interfaces.IContentListingObject"
253+
class=".tabular.ImageCell"
254+
permission="zope2.View"
255+
layer="plone.app.contenttypes.interfaces.IPloneAppContenttypesLayer"
256+
/>
257+
226258
</configure>

plone/app/contenttypes/browser/folder.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@
1212
from plone.event.interfaces import IEvent
1313
from plone.memoize.view import memoize
1414
from plone.registry.interfaces import IRegistry
15+
from Products.CMFPlone import PloneMessageFactory
1516
from Products.Five import BrowserView
1617
from zope.component import getMultiAdapter
1718
from zope.component import getUtility
19+
from zope.component import queryMultiAdapter
1820
from zope.contentprovider.interfaces import IContentProvider
21+
from zope.i18n import translate
22+
from zope.i18nmessageid import Message
1923

2024
import random
2125

@@ -25,6 +29,7 @@ class FolderView(BrowserView):
2529
_plone_view = None
2630
_portal_state = None
2731
_pas_member = None
32+
_image_scale = None
2833

2934
@property
3035
def plone_view(self):
@@ -42,6 +47,15 @@ def portal_state(self):
4247
)
4348
return self._portal_state
4449

50+
@property
51+
def image_scale(self):
52+
if not self._image_scale:
53+
portal = self.portal_state.portal()
54+
self._image_scale = getMultiAdapter(
55+
(portal, self.request), name="image_scale"
56+
)
57+
return self._image_scale
58+
4559
@property
4660
def pas_member(self):
4761
if not self._pas_member:
@@ -142,7 +156,6 @@ def text(self):
142156
)
143157
return text
144158

145-
@property
146159
def tabular_fields(self):
147160
ret = []
148161
ret.append("Title")
@@ -157,7 +170,10 @@ def tabular_field_label(self, field):
157170
"""Return the internationalized label (Message object) corresponding
158171
to the field.
159172
"""
160-
return get_field_label(field)
173+
label = get_field_label(field)
174+
if not isinstance(label, Message):
175+
return PloneMessageFactory(label)
176+
return label
161177

162178
def tabular_fielddata(self, item, fieldname):
163179
value = getattr(item, fieldname, "")
@@ -184,6 +200,35 @@ def tabular_fielddata(self, item, fieldname):
184200
"value": value
185201
}
186202

203+
def render_cells(self, item):
204+
result = []
205+
icon_cell_view = queryMultiAdapter(
206+
(item, self.request), name="tabular-cell-icon"
207+
)
208+
if icon_cell_view is not None:
209+
icon_cell_view.table_view = self
210+
result.append(icon_cell_view())
211+
for field in self.tabular_fields():
212+
if field == "getIcon":
213+
continue
214+
cell_view = queryMultiAdapter(
215+
(item, self.request), name=f"tabular-cell-{field}"
216+
)
217+
if cell_view is not None:
218+
cell_view.table_view = self
219+
result.append(cell_view())
220+
else:
221+
field_data = self.tabular_fielddata(item, field)
222+
value = translate(field_data["value"], context=self.request)
223+
result.append('<td class="text-nowrap">%s</td>' % value)
224+
image_cell_view = queryMultiAdapter(
225+
(item, self.request), name="tabular-cell-image"
226+
)
227+
if image_cell_view is not None:
228+
image_cell_view.table_view = self
229+
result.append(image_cell_view())
230+
return "".join(result)
231+
187232
def is_event(self, obj):
188233
if getattr(obj, "getObject", False):
189234
obj = obj.getObject()
@@ -248,6 +293,10 @@ def get_thumb_scale_table(self):
248293
return None
249294
return settings.thumb_scale_table
250295

296+
@property
297+
def img_class(self):
298+
return "thumb-%s pull-end" % self.get_thumb_scale_table()
299+
251300
@memoize
252301
def get_thumb_scale_list(self):
253302
if getattr(self.context, "suppress_thumbs", False):
@@ -276,3 +325,7 @@ def get_thumb_scale_summary(self):
276325

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

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

Lines changed: 5 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
tal:define="
4444
tabular_fields view/tabular_fields;
4545
thumb_scale_table python:view.get_thumb_scale_table();
46-
img_class python:'thumb-%s float-end' % thumb_scale_table;
4746
showicons python:view.show_icons();
4847
"
4948
i18n:attributes="summary summary_content_listing;"
@@ -62,93 +61,15 @@
6261
>Image</th>
6362
</tr>
6463
</thead>
65-
6664
<tbody tal:define="
6765
portal python:portal_state.portal();
6866
image_scale portal/@@image_scale;
6967
">
70-
<tal:entries tal:repeat="item python:batch">
71-
<tal:block tal:define="
72-
item_url python:item.getURL();
73-
item_id python:item.getId();
74-
item_title python:item.Title();
75-
item_title python:item_title or item_id;
76-
item_type python:item.PortalType();
77-
item_type_class python:'contenttype/' + view.normalizeString(item_type) if showicons else '';
78-
item_wf_state python:item.review_state();
79-
item_wf_state_class python:'state-' + view.normalizeString(item_wf_state);
80-
item_creator python:item.Creator();
81-
item_has_image python:item.getIcon;
82-
item_link python:item_type in view.use_view_action and item_url+'/view' or item_url;
83-
item_mime_type python:item.mime_type;
84-
item_mime_type_icon python: 'mimetype-' + item_mime_type;
85-
">
86-
87-
<tr metal:define-macro="listitem">
88-
89-
<td>
90-
<tal:icon tal:condition="python: item_type == 'File'"
91-
tal:replace="structure python:icons.tag(item_mime_type_icon)"
92-
/>
93-
<tal:icon tal:condition="python: item_type != 'File'"
94-
tal:replace="structure python:icons.tag(item_type_class)"
95-
/>
96-
</td>
97-
98-
<tal:block tal:repeat="field python:tabular_fields">
99-
100-
<td class="text-nowrap"
101-
tal:define="
102-
field_data python:view.tabular_fielddata(item, field);
103-
"
104-
tal:condition="python:field not in ['Title', 'Creator', 'getIcon']"
105-
>
106-
<tal:block tal:replace="python: field_data.get('value')" />
107-
</td>
108-
109-
<td class="text-nowrap"
110-
tal:condition="python:field == 'Title'"
111-
>
112-
<a tal:content="python: item_title"
113-
tal:attributes="
114-
href python:item_link;
115-
class string:$item_type_class $item_wf_state_class url;
116-
title python:item_type;
117-
"
118-
>
119-
Item Title
120-
</a>
121-
</td>
122-
123-
<td class="text-nowrap"
124-
tal:define="
125-
author python:view.pas_member.info(item_creator);
126-
name python:author['fullname'] or author['username'];
127-
"
128-
tal:condition="python:field == 'Creator'"
129-
>
130-
<a href="${view/navigation_root_url}/author/${item_creator}"
131-
tal:condition="python: author"
132-
>
133-
${name}
134-
</a>
135-
</td>
136-
137-
</tal:block>
138-
139-
<td>
140-
<a tal:condition="python:item_has_image and thumb_scale_table">
141-
<img tal:replace="structure python:image_scale.tag(item, 'image', scale=thumb_scale_table, css_class=img_class, loading='lazy')"
142-
tal:attributes="
143-
href python: item_link;
144-
"
145-
/>
146-
</a>
147-
</td>
148-
149-
</tr>
150-
151-
</tal:block>
68+
<tal:entries tal:repeat="item batch">
69+
<tr metal:define-macro="listitem"
70+
tal:content="structure python:view.render_cells(item)"
71+
>
72+
</tr>
15273
</tal:entries>
15374
</tbody>
15475
</table>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<td class="text-nowrap">
2+
<a tal:content="view/title"
3+
tal:attributes="
4+
href view/link;
5+
class string:${view/type_class} ${view/wf_state_class} url;
6+
title context/PortalType;
7+
"
8+
>Item Title
9+
</a>
10+
</td>

0 commit comments

Comments
 (0)