Skip to content

Commit ff38fb3

Browse files
committed
PARAF-512: Converted sub-templates usage view and viewlet to z3c.table and added the merge variable name column
1 parent 9776fc2 commit ff38fb3

9 files changed

Lines changed: 322 additions & 68 deletions

File tree

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Changelog
66

77
- Removed the duplicated "no value" option of the `style_template` field. (MOD-1080)
88
[chris-adam]
9+
- Converted sub-templates usage view and viewlet to z3c.table and added the merge variable name column. (PARAF-512)
10+
[chris-adam]
911

1012

1113
3.49 (2026-07-31)

src/collective/documentgenerator/browser/configure.zcml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,42 @@
198198
factory=".table.DownloadColumn"
199199
/>
200200

201+
<adapter
202+
name="SubTemplateColumn"
203+
for="zope.interface.Interface
204+
zope.interface.Interface
205+
.table.SubTemplatesUsageTable"
206+
provides="z3c.table.interfaces.IColumn"
207+
factory=".table.SubTemplateColumn"
208+
/>
209+
210+
<adapter
211+
name="SubTemplateUsagePathColumn"
212+
for="zope.interface.Interface
213+
zope.interface.Interface
214+
.table.SubTemplatesUsageTable"
215+
provides="z3c.table.interfaces.IColumn"
216+
factory=".table.SubTemplateUsagePathColumn"
217+
/>
218+
219+
<adapter
220+
name="SubTemplateUsageTemplatesColumn"
221+
for="zope.interface.Interface
222+
zope.interface.Interface
223+
.table.SubTemplatesUsageTable"
224+
provides="z3c.table.interfaces.IColumn"
225+
factory=".table.SubTemplateUsageTemplatesColumn"
226+
/>
227+
228+
<adapter
229+
name="SubTemplateUsageModelVariableColumn"
230+
for="zope.interface.Interface
231+
zope.interface.Interface
232+
.table.SubTemplatesUsageTable"
233+
provides="z3c.table.interfaces.IColumn"
234+
factory=".table.SubTemplateUsageModelVariableColumn"
235+
/>
236+
201237
<!-- batchactions -->
202238
<browser:viewlet
203239
zcml:condition="installed collective.eeafaceted.batchactions"

src/collective/documentgenerator/browser/sub_templates_usage.pt

Lines changed: 5 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,48 +12,11 @@
1212
<h1 class="documentFirstHeading" i18n:translate="sub_templates_usage_title">Sub-templates usages</h1>
1313

1414
<div id="content-core">
15-
<table class="listing" tal:define="entries view/sub_templates_usage">
16-
<thead>
17-
<tr>
18-
<th i18n:translate="sub_template_usage_sub_template">Sub-template</th>
19-
<th i18n:translate="sub_template_usage_path">Path</th>
20-
<th i18n:translate="sub_template_usage_templates">Using templates</th>
21-
</tr>
22-
</thead>
23-
<tbody>
24-
<tal:entry repeat="entry entries">
25-
<tr tal:condition="not:entry/groups">
26-
<td>
27-
<tal:path condition="entry/rel_path">
28-
<span tal:content="entry/rel_path">Folder / Sub folder</span> /
29-
</tal:path>
30-
<a tal:attributes="href entry/sub_template/getURL"
31-
tal:content="entry/sub_template/Title">Sub-template</a>
32-
</td>
33-
<td colspan="2"><em i18n:translate="sub_template_usage_not_used">not used</em></td>
34-
</tr>
35-
<tr tal:repeat="group entry/groups">
36-
<td tal:condition="repeat/group/start"
37-
tal:attributes="rowspan python:len(entry['groups'])">
38-
<tal:path condition="entry/rel_path">
39-
<span tal:content="entry/rel_path">Folder / Sub folder</span> /
40-
</tal:path>
41-
<a tal:attributes="href entry/sub_template/getURL"
42-
tal:content="entry/sub_template/Title">Sub-template</a>
43-
</td>
44-
<td tal:content="group/title_path">Folder / Sub folder</td>
45-
<td>
46-
<ul>
47-
<li tal:repeat="template group/templates">
48-
<a tal:attributes="href template/absolute_url"
49-
tal:content="template/Title">Template title</a>
50-
</li>
51-
</ul>
52-
</td>
53-
</tr>
54-
</tal:entry>
55-
</tbody>
56-
</table>
15+
<tal:block define="results view/table/values" tal:condition="results">
16+
<div id="dg-batch"><tal:batch replace="structure view/table/renderBatch" /></div>
17+
<tal:listing replace="structure view/table/render" />
18+
<div id="dg-batch"><tal:batch replace="structure view/table/renderBatch" /></div>
19+
</tal:block>
5720
</div>
5821

5922
</metal:main>

src/collective/documentgenerator/browser/table.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,3 +275,101 @@ def getLinkContent(self, item):
275275
safe_unicode(translate(PMF('Download'), context=self.request)),
276276
u'%s/++resource++collective.documentgenerator/download_icon.svg' % self.table.portal_url)
277277
return down_img
278+
279+
280+
class SubTemplatesUsageTable(Table):
281+
"""Table that displays sub-templates and the templates using them."""
282+
283+
cssClassEven = u'even'
284+
cssClassOdd = u'odd'
285+
cssClasses = {'table': 'listing nosort sub-templates-usage'}
286+
287+
batchSize = 200
288+
startBatchingAt = 200
289+
sortOn = None
290+
results = []
291+
292+
@CachedProperty
293+
def values(self):
294+
return self.results
295+
296+
297+
class SubTemplateUsageTable(SubTemplatesUsageTable):
298+
"""Usage table variant for the viewlet, where the sub-template is the context."""
299+
300+
hidden_columns = ('SubTemplateColumn', 'SubTemplateUsageModelVariableColumn')
301+
302+
def setUpColumns(self):
303+
columns = super(SubTemplateUsageTable, self).setUpColumns()
304+
return [col for col in columns if col.__name__ not in self.hidden_columns]
305+
306+
307+
class SubTemplateColumn(Column):
308+
"""Column that displays the sub-template."""
309+
310+
header = _(u'sub_template_usage_sub_template', default=u'Sub-template')
311+
weight = 10
312+
cssClasses = {'td': 'sub-template-column'}
313+
314+
def renderCell(self, item):
315+
if not item['first']:
316+
return u''
317+
brain = item['sub_template']
318+
link = u'<a href="{0}">{1}</a>'.format(brain.getURL(), escape(safe_unicode(brain.Title)))
319+
if item['rel_path']:
320+
return u'<span>{0}</span> / {1}'.format(escape(item['rel_path']), link)
321+
return link
322+
323+
324+
class SubTemplateUsagePathColumn(Column):
325+
"""Column that displays the path of the templates using the sub-template."""
326+
327+
header = _(u'sub_template_usage_path', default=u'Path')
328+
weight = 20
329+
cssClasses = {'td': 'usage-path-column'}
330+
331+
def renderCell(self, item):
332+
if item['group'] is None:
333+
return u'-'
334+
return escape(safe_unicode(item['group']['title_path']))
335+
336+
337+
class SubTemplateUsageTemplatesColumn(Column):
338+
"""Column that displays the templates using the sub-template."""
339+
340+
header = _(u'sub_template_usage_templates', default=u'Using templates')
341+
weight = 30
342+
cssClasses = {'td': 'using-templates-column'}
343+
344+
def render_template(self, template):
345+
"""Return the cell content for one template using the sub-template."""
346+
return u'<a href="{0}">{1}</a>'.format(template.absolute_url(), escape(safe_unicode(template.Title())))
347+
348+
def renderCell(self, item):
349+
if item['group'] is None:
350+
return u'<em>{0}</em>'.format(translate(
351+
_(u'sub_template_usage_not_used', default=u'not used'), context=self.request))
352+
return u'<ul>{0}</ul>'.format(u''.join(
353+
u'<li>{0}</li>'.format(self.render_template(tmpl)) for tmpl in item['group']['templates']))
354+
355+
356+
class SubTemplateUsageModelVariableColumn(Column):
357+
"""Column that displays the variable name used for the sub-template."""
358+
359+
header = _(u'sub_template_usage_variable', default=u'Model variable')
360+
weight = 40
361+
cssClasses = {'td': 'pod-context-name-column'}
362+
363+
def _get_pod_context_name(self, pod_template, sub_template_uid):
364+
"""Return the 'pod_context_name' under which sub_template_uid is merged in pod_template."""
365+
for line in getattr(pod_template, 'merge_templates', None) or []:
366+
if line.get('template') == sub_template_uid:
367+
return line.get('pod_context_name') or u''
368+
return u''
369+
370+
def renderCell(self, item):
371+
if item['group'] is None:
372+
return u''
373+
return u'<ul>{0}</ul>'.format(u''.join(
374+
u'<li>{0}</li>'.format(escape(self._get_pod_context_name(tmpl, item['sub_template_uid'])))
375+
for tmpl in item['group']['templates']))

src/collective/documentgenerator/browser/views.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
from Acquisition import aq_inner
33
from Acquisition import aq_parent
44
from collections import OrderedDict
5+
from collective.documentgenerator.browser.table import SubTemplatesUsageTable
56
from collective.documentgenerator.browser.table import TemplatesTable
67
from collective.documentgenerator.content.pod_template import IPODTemplate
78
from collective.documentgenerator.content.pod_template import MailingLoopTemplate
89
from collective.documentgenerator.content.pod_template import SubTemplate
10+
from collective.documentgenerator.viewlets.sub_template_usage import _sub_template_usage_groups_to_rows
911
from collective.documentgenerator.content.style_template import IStyleTemplate
1012
from collective.documentgenerator.utils import common_prefix_length
1113
from collective.documentgenerator.utils import get_path_segments
@@ -106,6 +108,8 @@ class SubTemplatesUsage(BrowserView):
106108
"""Overview listing, for each sub-template, the POD templates that use
107109
it in their 'merge_templates' field, grouped by the path they live in."""
108110

111+
__table__ = SubTemplatesUsageTable
112+
109113
def sub_templates_usage(self):
110114
"""Return a list of {'sub_template': brain, 'rel_path': unicode, 'groups': [...]}
111115
entries, one per sub-template (including unused ones), ordered by the
@@ -120,8 +124,8 @@ def sub_templates_usage(self):
120124
segments = get_path_segments(aq_parent(aq_inner(sub_template)))
121125
all_segments.append(segments)
122126
templates = get_pod_templates_using(sub_template)
123-
result.append({"sub_template": brain, "segments": segments,
124-
"groups": group_templates_by_path(templates)})
127+
result.append({"sub_template": brain, "sub_template_uid": brain.UID,
128+
"segments": segments, "groups": group_templates_by_path(templates)})
125129
common = common_prefix_length([[seg[0] for seg in segs] for segs in all_segments])
126130
for entry in result:
127131
tail = entry.pop("segments")[common:]
@@ -130,6 +134,16 @@ def sub_templates_usage(self):
130134
safe_unicode(e["sub_template"].Title).lower()))
131135
return result
132136

137+
def update(self):
138+
self.table = self.__table__(self.context, self.request)
139+
self.table.__name__ = u"sub-templates-usage"
140+
self.table.results = _sub_template_usage_groups_to_rows(self.sub_templates_usage())
141+
self.table.update()
142+
143+
def __call__(self):
144+
self.update()
145+
return self.index()
146+
133147

134148
class DisplayChildrenPodTemplateProvider(ContentProviderBase):
135149
template = ViewPageTemplateFile("children_pod_template.pt")

src/collective/documentgenerator/tests/test_sub_templates_usage_view.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# -*- coding: utf-8 -*-
22

3+
from collective.documentgenerator.browser.table import SubTemplateUsageModelVariableColumn
4+
from collective.documentgenerator.browser.table import SubTemplateColumn
5+
from collective.documentgenerator.browser.table import SubTemplateUsagePathColumn
6+
from collective.documentgenerator.browser.table import SubTemplateUsageTemplatesColumn
37
from collective.documentgenerator.testing import PODTemplateIntegrationTest
48
from plone import api
59
from Products.CMFPlone.utils import _createObjectByType
@@ -16,10 +20,27 @@ def setUp(self):
1620
super(TestSubTemplatesUsageView, self).setUp()
1721
self.view = self.portal.restrictedTraverse("@@sub-templates-usage")
1822

23+
def _rows(self):
24+
"""Add a second usage group for the demo sub-template and an unused sub-template.
25+
26+
Returns the 3 table rows: the demo sub-template first group, its second
27+
group (a continuation row) and the unused sub-template.
28+
"""
29+
pod = self.portal.podtemplates
30+
folder = _createObjectByType("Folder", pod, "folder_z", title=u"Zeta")
31+
api.content.create(type="SubTemplate", id="st_z", title=u"In Zeta", container=folder)
32+
api.content.create(type="ConfigurablePODTemplate", id="in_zeta", title=u"In Zeta template",
33+
container=folder,
34+
merge_templates=[{"template": pod.sub_template.UID(),
35+
"pod_context_name": u"zeta_var", "do_rendering": False}])
36+
self.view.update()
37+
return self.view.table.results
38+
1939
def test_sub_templates_usage(self):
2040
entries = self.view.sub_templates_usage()
2141
# one entry per sub-template (here the single demo 'sub_template')
2242
self.assertEqual([e["sub_template"].getId for e in entries], ["sub_template"])
43+
self.assertEqual(entries[0]["sub_template_uid"], self.portal.podtemplates.sub_template.UID())
2344
# the only sub-template defines the whole common path -> empty rel_path
2445
self.assertEqual(entries[0]["rel_path"], u"")
2546
# both using templates share the same folder -> one group, sorted by title
@@ -49,3 +70,73 @@ def test_rel_path_strips_common_part(self):
4970
# the common 'podtemplates' segment never appears in the displayed path
5071
pod_title = pod.Title()
5172
self.assertFalse(any(pod_title in rel_path for rel_path in rel_paths))
73+
74+
def test_update(self):
75+
rows = self._rows()
76+
self.assertEqual([col.__name__ for col in self.view.table.columns],
77+
["SubTemplateColumn", "SubTemplateUsagePathColumn", "SubTemplateUsageTemplatesColumn",
78+
"SubTemplateUsageModelVariableColumn"])
79+
# one row per (sub-template, path group), 'first' marking a group start
80+
self.assertEqual([(r["sub_template"].getId, r["first"], r["group"] is None) for r in rows],
81+
[("sub_template", True, False), ("sub_template", False, False),
82+
("st_z", True, True)])
83+
# the whole page renders the table
84+
self.assertIn(u'class="listing nosort sub-templates-usage"', self.view())
85+
86+
def test_SubTemplateColumn(self):
87+
rows = self._rows()
88+
column = SubTemplateColumn(self.view.context, self.view.request, self.view.table)
89+
self.assertEqual(column.renderHeadCell(), u"Sous-modèle")
90+
self.assertEqual(column.renderCell(rows[0]),
91+
u'<a href="http://nohost/plone/podtemplates/sub_template">Header</a>')
92+
# a continuation row leaves the cell empty, as the removed rowspan did
93+
self.assertEqual(column.renderCell(rows[1]), u"")
94+
# a sub-template out of the common path is prefixed by its relative path
95+
self.assertEqual(column.renderCell(rows[2]),
96+
u'<span>Zeta</span> / '
97+
u'<a href="http://nohost/plone/podtemplates/folder_z/st_z">In Zeta</a>')
98+
99+
def test_SubTemplateUsagePathColumn(self):
100+
rows = self._rows()
101+
column = SubTemplateUsagePathColumn(self.view.context, self.view.request, self.view.table)
102+
self.assertEqual(column.renderHeadCell(),
103+
u"Chemin des modèles utilisant ce sous-modèle")
104+
self.assertEqual(column.renderCell(rows[0]), u"POD Templates")
105+
self.assertEqual(column.renderCell(rows[1]), u"POD Templates / Zeta")
106+
# an unused sub-template has no path to show
107+
self.assertEqual(column.renderCell(rows[2]), u"-")
108+
109+
def test_SubTemplateUsageTemplatesColumn(self):
110+
rows = self._rows()
111+
column = SubTemplateUsageTemplatesColumn(self.view.context, self.view.request, self.view.table)
112+
self.assertEqual(column.renderHeadCell(), u"Modèles concernés")
113+
self.assertEqual(column.renderCell(rows[0]),
114+
u'<ul><li><a href="http://nohost/plone/podtemplates/test_template_bis">'
115+
u'Collection template</a></li>'
116+
u'<li><a href="http://nohost/plone/podtemplates/test_template_multiple">'
117+
u'Multiple format template</a></li></ul>')
118+
self.assertEqual(column.renderCell(rows[1]),
119+
u'<ul><li><a href="http://nohost/plone/podtemplates/folder_z/in_zeta">'
120+
u'In Zeta template</a></li></ul>')
121+
self.assertEqual(column.renderCell(rows[2]), u"<em>Pas utilisé</em>")
122+
# render_template is the seam imio.dms.mail overrides to flag the signer rules
123+
self.assertEqual(column.render_template(self.portal.podtemplates.test_template),
124+
u'<a href="http://nohost/plone/podtemplates/test_template">General template</a>')
125+
126+
def test_SubTemplateUsageModelVariableColumn(self):
127+
rows = self._rows()
128+
column = SubTemplateUsageModelVariableColumn(self.view.context, self.view.request, self.view.table)
129+
self.assertEqual(column.renderHeadCell(), u"Model variable")
130+
# one variable name per using template, in the same order as the templates column
131+
self.assertEqual(column.renderCell(rows[0]), u"<ul><li>header</li><li>header</li></ul>")
132+
self.assertEqual(column.renderCell(rows[1]), u"<ul><li>zeta_var</li></ul>")
133+
self.assertEqual(column.renderCell(rows[2]), u"")
134+
135+
# _get_pod_context_name finds nothing for a template not merging the sub-template,
136+
# for a template without the field at all and for an unknown uid
137+
pod = self.portal.podtemplates
138+
sub_uid = pod.sub_template.UID()
139+
self.assertEqual(column._get_pod_context_name(pod.test_template_multiple, sub_uid), "header")
140+
self.assertEqual(column._get_pod_context_name(pod.test_template, sub_uid), u"")
141+
self.assertEqual(column._get_pod_context_name(pod.test_style_template, sub_uid), u"")
142+
self.assertEqual(column._get_pod_context_name(pod.test_template_multiple, "unknown-uid"), u"")

0 commit comments

Comments
 (0)