Skip to content

Commit

Permalink
Problem: customizing tabular view is hard
Browse files Browse the repository at this point in the history
Solution: use views for table cells such that they can be overridden
  • Loading branch information
Thibaut Born authored and ThibautBorn committed Aug 29, 2023
1 parent 4e0aa66 commit db8e434
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 115 deletions.
1 change: 1 addition & 0 deletions news/customize-tabular.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Enable customization of tabular_view via views for fields of contentlisting items.
32 changes: 32 additions & 0 deletions plone/app/contenttypes/browser/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,36 @@
name="plone.app.contenttypes.migration.changed_base_classes"
/>

<browser:page
name="tabular-cell-Title"
for="plone.app.contentlisting.interfaces.IContentListingObject"
class=".tabular.TitleCell"
layer="plone.app.contenttypes.interfaces.IPloneAppContenttypesLayer"
permission="zope2.View"
/>

<browser:page
name="tabular-cell-Creator"
for="plone.app.contentlisting.interfaces.IContentListingObject"
class=".tabular.CreatorCell"
layer="plone.app.contenttypes.interfaces.IPloneAppContenttypesLayer"
permission="zope2.View"
/>

<browser:page
name="tabular-cell-icon"
for="plone.app.contentlisting.interfaces.IContentListingObject"
class=".tabular.IconCell"
layer="plone.app.contenttypes.interfaces.IPloneAppContenttypesLayer"
permission="zope2.View"
/>

<browser:page
name="tabular-cell-image"
for="plone.app.contentlisting.interfaces.IContentListingObject"
class=".tabular.ImageCell"
layer="plone.app.contenttypes.interfaces.IPloneAppContenttypesLayer"
permission="zope2.View"
/>

</configure>
59 changes: 58 additions & 1 deletion plone/app/contenttypes/browser/folder.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,17 @@
from plone.event.interfaces import IEvent
from plone.memoize.view import memoize
from plone.registry.interfaces import IRegistry
from Products.CMFPlone.interfaces import ISiteSchema
from Products.CMFPlone.PloneBatch import Batch
from Products.CMFPlone.utils import safe_callable
from Products.CMFPlone import PloneMessageFactory
from Products.Five import BrowserView
from zope.component import getMultiAdapter
from zope.component import getUtility
from zope.contentprovider.interfaces import IContentProvider
from zope.component import queryMultiAdapter
from zope.i18n import translate
from zope.i18nmessageid import Message

import random

Expand All @@ -25,6 +32,7 @@ class FolderView(BrowserView):
_plone_view = None
_portal_state = None
_pas_member = None
_image_scale = None

@property
def plone_view(self):
Expand All @@ -42,6 +50,15 @@ def portal_state(self):
)
return self._portal_state

@property
def image_scale(self):
if not self._image_scale:
portal = self.portal_state.portal()
self._image_scale = getMultiAdapter(
(portal, self.request), name="image_scale"
)
return self._image_scale

@property
def pas_member(self):
if not self._pas_member:
Expand Down Expand Up @@ -157,7 +174,10 @@ def tabular_field_label(self, field):
"""Return the internationalized label (Message object) corresponding
to the field.
"""
return get_field_label(field)
label = get_field_label(field)
if not isinstance(label, Message):
return PloneMessageFactory(label)
return label

def tabular_fielddata(self, item, fieldname):
value = getattr(item, fieldname, "")
Expand All @@ -184,6 +204,35 @@ def tabular_fielddata(self, item, fieldname):
"value": value
}

def render_cells(self, item):
result = []
icon_cell_view = queryMultiAdapter(
(item, self.request), name=f"tabular-cell-icon"
)
if icon_cell_view is not None:
icon_cell_view.table_view = self
result.append(icon_cell_view())
for field in self.tabular_fields():
if field == "getIcon":
continue
cell_view = queryMultiAdapter(
(item, self.request), name=f"tabular-cell-{field}"
)
if cell_view is not None:
cell_view.table_view = self
result.append(cell_view())
else:
field_data = self.tabular_fielddata(item, field)
value = translate(field_data["value"], context=self.request)
result.append('<td class="text-nowrap">%s</td>' % value)
image_cell_view = queryMultiAdapter(
(item, self.request), name=f"tabular-cell-image"
)
if image_cell_view is not None:
image_cell_view.table_view = self
result.append(image_cell_view())
return "".join(result)

def is_event(self, obj):
if getattr(obj, "getObject", False):
obj = obj.getObject()
Expand Down Expand Up @@ -248,6 +297,10 @@ def get_thumb_scale_table(self):
return None
return settings.thumb_scale_table

@property
def img_class(self):
return "thumb-%s pull-end" % self.get_thumb_scale_table()

@memoize
def get_thumb_scale_list(self):
if getattr(self.context, "suppress_thumbs", False):
Expand Down Expand Up @@ -276,3 +329,7 @@ def get_thumb_scale_summary(self):

def show_icons(self):
return not getattr(self.context, "suppress_icons", False)

@property
def iconresolver(self):
return self.context.restrictedTraverse("@@iconresolver")
80 changes: 80 additions & 0 deletions plone/app/contenttypes/browser/tabular.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from Products.Five import BrowserView

# BEWARE: the cell views are registered for ContentListingObject
# which are not acquisition aware.
# That precludes using Products.Five.ViewPageTemplateFile
# and imposes to use zope.browserpage.viewpagetemplatefile.
from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile

# BEWARE


class TitleCell(BrowserView):
__call__ = ViewPageTemplateFile("templates/titlecell.pt")

@property
def title(self):
return self.context.Title() or self.context.getId()

@property
def link(self):
suffix = (
"/view"
if self.context.portal_type in self.table_view.use_view_action
else ""
)
return self.context.getURL() + suffix

@property
def type_class(self):
return (
"contenttype/" + self.table_view.normalizeString(self.context.portal_type)
if self.table_view.show_icons
else ""
)

@property
def wf_state_class(self):
return "state-" + self.table_view.normalizeString(self.context.review_state())


class CreatorCell(BrowserView):
__call__ = ViewPageTemplateFile("templates/creatorcell.pt")

@property
def author(self):
return self.table_view.pas_member.info(self.context.Creator)

@property
def author_name(self):
return self.author["fullname"] or self.author["username"]


class IconCell(BrowserView):
def __call__(self):
item = self.context
item_type = item.portal_type
if item_type == "File":
icon_type = "mimetype-" + item.mime_type
elif self.table_view.show_icons:
icon_type = "contenttype/" + self.table_view.normalizeString(item_type)
else:
icon_type = ""
icon = self.table_view.iconresolver.tag(icon_type).decode("utf8")
return "<td>" + icon + "</td>"


class ImageCell(BrowserView):
def render_image(self):
thumb_scale_table = self.table_view.get_thumb_scale_table()
if thumb_scale_table and self.context.getIcon:
img_class = self.table_view.img_class
return self.table_view.image_scale.tag(
self.context, "image", scale=thumb_scale_table, css_class=img_class
)
else:
return ""

def __call__(self):
image = self.render_image()
return "<td>" + image + "</td>"
5 changes: 5 additions & 0 deletions plone/app/contenttypes/browser/templates/creatorcell.pt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<td class="text-nowrap">
<a tal:condition="view/author"
tal:attributes="href string:${view/table_view/navigation_root_url}/author/${context/Creator}"
tal:content="view/author_name">Jos Henken</a>
</td>
131 changes: 17 additions & 114 deletions plone/app/contenttypes/browser/templates/listing_tabular.pt
Original file line number Diff line number Diff line change
Expand Up @@ -38,120 +38,23 @@

<div class="table-responsive">

<table class="table table-striped"
summary="Content listing"
tal:define="
tabular_fields view/tabular_fields;
thumb_scale_table python:view.get_thumb_scale_table();
img_class python:'thumb-%s float-end' % thumb_scale_table;
showicons python:view.show_icons();
"
i18n:attributes="summary summary_content_listing;"
>

<thead>
<tr>
<th class="text-nowrap"></th>
<th class="text-nowrap"
tal:repeat="field tabular_fields"
tal:content="python:view.tabular_field_label(field)"
>Field name</th>
<th class="text-nowrap"
tal:condition="python:thumb_scale_table"
i18n:translate="image"
>Image</th>
</tr>
</thead>

<tbody tal:define="
portal python:portal_state.portal();
image_scale portal/@@image_scale;
">
<tal:entries tal:repeat="item python:batch">
<tal:block tal:define="
item_url python:item.getURL();
item_id python:item.getId();
item_title python:item.Title();
item_title python:item_title or item_id;
item_type python:item.PortalType();
item_type_class python:'contenttype/' + view.normalizeString(item_type) if showicons else '';
item_wf_state python:item.review_state();
item_wf_state_class python:'state-' + view.normalizeString(item_wf_state);
item_creator python:item.Creator();
item_has_image python:item.getIcon;
item_link python:item_type in view.use_view_action and item_url+'/view' or item_url;
item_mime_type python:item.mime_type;
item_mime_type_icon python: 'mimetype-' + item_mime_type;
">

<tr metal:define-macro="listitem">

<td>
<tal:icon tal:condition="python: item_type == 'File'"
tal:replace="structure python:icons.tag(item_mime_type_icon)"
/>
<tal:icon tal:condition="python: item_type != 'File'"
tal:replace="structure python:icons.tag(item_type_class)"
/>
</td>

<tal:block tal:repeat="field python:tabular_fields">

<td class="text-nowrap"
tal:define="
field_data python:view.tabular_fielddata(item, field);
"
tal:condition="python:field not in ['Title', 'Creator', 'getIcon']"
>
<tal:block tal:replace="python: field_data.get('value')" />
</td>

<td class="text-nowrap"
tal:condition="python:field == 'Title'"
>
<a tal:content="python: item_title"
tal:attributes="
href python:item_link;
class string:$item_type_class $item_wf_state_class url;
title python:item_type;
"
>
Item Title
</a>
</td>

<td class="text-nowrap"
tal:define="
author python:view.pas_member.info(item_creator);
name python:author['fullname'] or author['username'];
"
tal:condition="python:field == 'Creator'"
>
<a href="${view/navigation_root_url}/author/${item_creator}"
tal:condition="python: author"
>
${name}
</a>
</td>

</tal:block>

<td>
<a tal:condition="python:item_has_image and thumb_scale_table">
<img tal:replace="structure python:image_scale.tag(item, 'image', scale=thumb_scale_table, css_class=img_class, loading='lazy')"
tal:attributes="
href python: item_link;
"
/>
</a>
</td>

</tr>

</tal:block>
</tal:entries>
</tbody>
</table>
<table class="table table-striped" tal:define="tabular_fields view/tabular_fields; thumb_scale_table python:view.get_thumb_scale_table(); showicons python:view.show_icons();" summary="Content listing" i18n:attributes="summary summary_content_listing;">

<thead>
<tr>
<th class="text-nowrap"></th>
<th class="text-nowrap" tal:repeat="field tabular_fields" tal:content="python:view.tabular_field_label(field)">Field name</th>
<th class="text-nowrap" i18n:translate="image" tal:condition="python:thumb_scale_table">Image</th>
</tr>
</thead>
<tbody tal:define="portal python:portal_state.portal(); image_scale portal/@@image_scale">
<tal:entries tal:repeat="item batch">
<tr metal:define-macro="listitem"
tal:content="structure python:view.render_cells(item)">
</tr>
</tal:entries>
</tbody>
</table>

</div>

Expand Down
7 changes: 7 additions & 0 deletions plone/app/contenttypes/browser/templates/titlecell.pt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<td class="text-nowrap">
<a tal:attributes="href view/link;
class string:${view/type_class} ${view/wf_state_class} url;
title context/PortalType"
tal:content="view/title">Item Title
</a>
</td>

0 comments on commit db8e434

Please sign in to comment.