2014/07/30

Collection Group By View

A Collection in Plone works much like a report or query does in a database. Based on a set of Criteria such as: content types, dates, or keywords, you can search items and display them in a variety of dynamic ways. By default, there are Standard View, Summary View, Full View, Tabular View and Thumbnail View. What I need is to add an Aggregate View (like Group By). Here is how:

First, create a view.py file in the browser folder, where you define the View class. In my case, the results need to be grouped by category field values. Now I get a dictionary like {'category1': [<plone.app.contentlisting.catalog.CatalogContentListingObject instance at /mysite/myfolder/101-c-1>, <plone.app.contentlisting.catalog.CatalogContentListingObject instance at /mysite/myfolder/101-c-2>, <plone.app.contentlisting.catalog.CatalogContentListingObject instance at /mysite/myfolder/101-c-3>], 'category2': [...]}. With TAL nested loop in the aggregate_view.pt template, we can display the result. Finally register this browser view.

Note that this tip might suit only for collections of limited items. There should be much room for performance improvement. For those who are interested in the details how Collection works, see reference at plone.app.contenttypes/behaviors/collection.py

<html lang="en"
xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:metal="http://xml.zope.org/namespaces/metal"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
metal:use-macro="here/main_template/macros/master"
i18n:domain="my.content">
<body>
<div metal:fill-slot="content-core">
<metal:block define-macro="content-core"
tal:define="templateId template/getId;
global all view/getCategory">
<div metal:define-macro="text-field-view"
id="parent-fieldname-text" class="stx"
tal:define="has_text exists:context/aq_explicit/text/output;
text python:has_text and here.text.output or ''"
tal:condition="text"
tal:attributes="class python:context.Format() in ('text/structured', 'text/x-rst', ) and 'stx' or 'plain'">
<div metal:define-slot="inside" tal:replace="structure text">The body</div>
</div>
<ol style="list-style-type: upper-alpha; font-size: 1.4em; font-weight: bold">
<tal:keys define="global cat_names all/keys"
tal:repeat="cat cat_names">
<li style="border-bottom: 2px solid #666"
tal:content="python:view.t_title('myvoc.category',cat)">Category</li>
<tal:list define="global items python:all[cat]"
tal:repeat="item items">
<tal:obj define="obj item/getObject;">
<div class="award-project">
<div>
<label class="fieldHead" i18n:translate="">Year</label>
<span class="forText">:</span>
<span tal:replace="obj/year" />
</div>
<div>
<label class="fieldHead" i18n:translate="">Project</label>
<span class="forText">:</span>
<span tal:replace="obj/title" />
</div>
<div>
<label class="fieldHead" i18n:translate="">Institute</label>
<span class="forText">:</span>
<span tal:replace="obj/description" />
<span tal:condition="obj/region">
, <span tal:replace="python:view.t_title('myvoc.region',obj.region)" />
</span>
</div>
<div>
<label class="fieldHead" i18n:translate="">Director</label>
<span class="forText">:</span>
<span tal:replace="obj/director" />
</div>
<div>
<label class="fieldHead" i18n:translate="">Budget</label>
<span class="forText">:</span>
<span tal:replace="obj/budget" />
</div>
</div>
</tal:obj>
</tal:list>
</tal:keys>
</ol>
<metal:empty metal:define-slot="no_items_in_listing">
<p class="discreet"
tal:condition="not: all"
i18n:translate="">
There are currently no items in this collection.
</p>
</metal:empty>
</metal:block>
</div>
</body>
</html>
<browser:page
name="aggregate_view"
for="plone.app.contenttypes.interfaces.ICollection"
class=".view.AggregateView"
template="aggregate_view.pt"
permission="zope2.View"
menu="plone_displayviews"
title="Aggregate"
layer=".interfaces.IThemeSpecific" />
view raw configure.zcml hosted with ❤ by GitHub
from Acquisition import aq_inner
from plone.app.contenttypes.behaviors.collection import ICollection
from zope.component import getUtility
from zope.schema.interfaces import IVocabularyFactory
class AggregateView(BrowserView):
def results(self, **kwargs):
context = aq_inner(self.context)
wrapped = ICollection(context)
return wrapped.results(**kwargs)
def getCategory(self):
results = self.results(batch=False)
_mapping = {}
for item in results:
obj = item.getObject()
category = obj.category
try:
_mapping[category].append(item)
except KeyError:
_mapping[category] = []
_mapping[category].append(item)
return _mapping
def t_title(self, vocab, value):
try:
factory = getUtility(IVocabularyFactory, vocab)
vocabulary = factory(self.context)
term = vocabulary.getTerm(value)
return term.title
except:
return None
view raw view.py hosted with ❤ by GitHub