Contents
Plone has several methods of getting the list of folder items depending on whether
Special attentation must be paid also on object ids. Zope looks all objects by traversing site graph using ids. The id mapping is usually the property of a parent object, not child. Thus most of the listing methods tend to return (id, object) tuples instead of plain objects.
Method contentItems is defined in CMFCore/PortalFolder.py. See source code for details, e.g. filtering and other forms of listing:
items = folder.contentItems() # return Python list of children object tuples (id, object)
Warning
contentItems() call may be costly, since it will return the actual content objects, not their indexed metadata from portal_catalog. You should avoid this method if possible.
Warning
folder.contentItems() returns all items regardless of the user security context.
listFolderContents() method retrieves the full objects in the folder). It takes contentFilter argument which can be used to filter the results. contentFilter uses the same syntax as portal_catalog queries.
Example:
# List all types in this folder whose portal_type is "CourseModulePage"
return self.listFolderContents(contentFilter={"portal_type" : "CourseModulePage"})
Warning
Security warning: listFolderContens() honors the currently logged in user roles.
Warning
Performance warning: Slow for big folders. Preferably use portal_catalog and path based query to query items in a big folder.
Plone applies some default rules for listFolderContents()
If you need to get ids only, use objectIds() method. This is a fast method:
# Return a list of object ids in the folder
ids = folder.objectIds()
Warning
objectIds() returns ids for Zope 2 objects too, not just content.
Manipulating non-contentish Zope objects are needed in some special cases.
This listing method applies to all OFS.Folder.Folder objects, not just Plone content objects.
Example:
for id, item in folder.objectItems():
# id is 8-bit string of object id in the folder
# item is the object itself
pass
If you want to know whether the folder has a certain item or not, you can use the following snippet.
There is a special case for Large Plone Folders (BTree based). The following is optimal code, but you can simplify it if you don’t need to check if the folder is BTreeFolder:
# Use the BTreeFolder API if possible
myid = "index_html"
if base_hasattr(context, 'has_key'):
# BTreeFolder's has_key returns numeric values
return context.has_key(myid) and True or False
elif myid in context.objectIds():
return True
else:
return False
This should be your preferred method for querying folder items. portal_catalog searches are fast, because they return catalog brain objects of real content objects (less database look ups).
Warning
Returned catalog brain data, like Title, will be UTF-8 encoded. You need to call brain[“title”].decode(“utf-8”) or similar to all strings you want to extract from the data.
Simple example how to get all items in a folder:
# Get the physical path (includes Plone site name)
# to the folder
path = folder.getPhysicalPath()
# Convert getPhysicalPath() tuples result to
# slash separated string, which is used by ExtendedPathIndex
path = "/".join(path)
# This will fetch catalog brains.
# Includes also unreleased items, not caring about workflow state.
# depth = 1 means that subfolder items are not included
brains = context.portal_catalog(path={"query" : path, "depth" : 1})
Complex example how to perform various filtering and honour some default Plone filtering rules. This example is taken from Products.CMFPlone/skins/plone_scripts/getFolderContents:
mtool = context.portal_membership
cur_path = '/'.join(context.getPhysicalPath())
path = {}
if not contentFilter:
# The form and other are what really matters
contentFilter = dict(getattr(context.REQUEST, 'form',{}))
contentFilter.update(dict(getattr(context.REQUEST, 'other',{})))
else:
contentFilter = dict(contentFilter)
if not contentFilter.get('sort_on', None):
contentFilter['sort_on'] = 'getObjPositionInParent'
if contentFilter.get('path', None) is None:
path['query'] = cur_path
path['depth'] = 1
contentFilter['path'] = path
show_inactive = mtool.checkPermission('Access inactive portal content', context)
# Evaluate in catalog context because some containers override queryCatalog
# with their own unrelated method (Topics)
contents = context.portal_catalog.queryCatalog(contentFilter, show_all=1,
show_inactive=show_inactive)
if full_objects:
contents = [b.getObject() for b in contents]
if batch:
from Products.CMFPlone import Batch
b_start = context.REQUEST.get('b_start', 0)
batch = Batch(contents, b_size, int(b_start), orphan=0)
return batch
return contents
The least expensive call for this for tens of items is to call len() for getFolderContents() which is portal_catalog based query:
items = len(self.getFolderContents())
Alternative, if you know there are not many objects in in the folder, you can call contentItems() as this will potentially wake less items than complex catalog query.
Warning
Security: This method does not consider access rights.
Example (AT content class method):
def getMainImage(self):
items = self.contentItems() # id, object tuples
if len(items) > 0:
return items[1]
Here is an example how to create a view which will render custom listing for a folder or a collection (ATTopic).
The view is called ProductSummaryView and it is registered with name productsummary. This example is not suitable for your add-on product as is, but you need to tailor it for your specific needs.
Warning
If you are going to call item/getObject for a catalog brain it might cause excessive database load as it causes a new database query per object. Try use information available in the catalog or add more catalog indexes. To know more about the issue read about waking up database objects.
First let’s register our view
We could limit content types for which view is enabled by putting Products.ATContentTypes.interface.IATFolder or Products.ATContentTypes.interface.IATTopic into for attribute. The configure.zcml snippet below.
<browser:page
for="*"
name="productcardsummary"
class=".productcardsummaryview.ProductCardSummaryView"
template="productcardsummaryview.pt"
allowed_interface=".productcardsummaryview.IProductCardSummaryView"
permission="zope2.View"
/>
from zope.interface import implements, Interface
from zope import schema
from Products.Five import BrowserView
from Products.CMFCore.utils import getToolByName
from Products.ATContentTypes.interface import IATTopic
# zope.18n message translator for your add-on product
from yourproduct.namespace import appMessageFactory as _
class IProductCardSummaryView(Interface):
""" Allowed template variables exposed from the view.
"""
# Item list as iterable Products.CMFPlone.PloneBatch.Batch object
contents = schema.Object(Interface)
class ProductCardSummaryView(BrowserView):
"""
List summary information for all product cards in the folder.
Batch results.
"""
implements(IProductCardSummaryView)
def query(self, start, limit, contentFilter):
""" Make catalog query for the folder listing.
@param start: First index to query
@param limit: maximum number of items in the batch
@param contentFilter: portal_catalog filtering dictionary with index -> value pairs.
@return: Products.CMFPlone.PloneBatch.Batch object
"""
# Batch size
b_size = limit
# Batch start index, zero based
b_start = start
# We use different query method, depending on
# whether we do listing for topic or folder
if IATTopic.providedBy(self.context):
# ATTopic like content
# Call Products.ATContentTypes.content.topic.ATTopic.queryCatalog() method
# This method handles b_start internally and
# grabs it from HTTPRequest object
return self.context.queryCatalog(contentFilter, batch=True, b_size=b_size)
else:
# Folder or Large Folder like content
# Call CMFPlone(/skins/plone_scripts/getFolderContents Python script
# This method handles b_start parametr internally and grabs it from the request object
return self.context.getFolderContents(contentFilter, batch=True, b_size=b_size)
def __call__(self):
""" Render the content item listing.
"""
# How many items is one one page
limit = 3
# What kind of query we perform?
# Here we limit results to ProductCard content type
filter = { "portal_type" : "ProductCard" }
# Read the first index of the selected batch parameter as HTTP GET request query parameter
start = self.request.get("b_start", 0)
# Perform portal_catalog query
self.contents = self.query(start, limit, filter)
# Return the rendered template (productcardsummaryview.pt), with content listing information filled in
return self.index()
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en"
metal:use-macro="here/main_template/macros/master"
i18n:domain="yourproduct.namespace">
<body>
<div metal:fill-slot="main">
<tal:main-macro metal:define-macro="main"
tal:define="kssClassesView context/@@kss_field_decorator_view;
getKssClasses nocall:kssClassesView/getKssClassesInlineEditable;
">
<div tal:replace="structure provider:plone.abovecontenttitle" />
<h1 metal:use-macro="here/kss_generic_macros/macros/generic_title_view">
Title or id
</h1>
<div tal:replace="structure provider:plone.belowcontenttitle" />
<p metal:use-macro="here/kss_generic_macros/macros/generic_description_view">
Description
</p>
<div tal:replace="structure provider:plone.abovecontentbody" />
<tal:listing define="batch view/contents">
<tal:block tal:repeat="item batch">
<div class="tileItem visualIEFloatFix vevent"
tal:define="item_url item/getURL|item/absolute_url;
item_id item/getId|item/id;
item_title_or_id item/pretty_title_or_id;
item_description item/Description;
item_type item/portal_type;
item_type_title item/Type;
item_type_class python: 'contenttype-' + normalizeString(item_type);
item_modified item/ModificationDate;
item_created item/CreationDate;
item_wf_state item/review_state|python: wtool.getInfoFor(item, 'review_state', '');
item_wf_state_class python:'state-' + normalizeString(item_wf_state);
item_creator item/Creator;
item_start item/start/ISO|item/StartDate|nothing;
item_end item/end/ISO|item/EndDate|nothing;
"
tal:attributes="class string:tileItem visualIEFloatFix vevent ${item_type_class}">
<a href="#"
tal:attributes="href item_url">
<img src="" alt=""
witdh="64"
height="64"
tal:condition="item_object/main_image|python:False"
tal:attributes="src item_object/main_image" />
</a>
<h2 class="tileHeadline"
metal:define-macro="listitem">
<a href="#"
class="summary url"
tal:attributes="href item_url"
tal:content="item_title_or_id">
Item Title
</a>
</h2>
<p class="tileBody">
<span tal:omit-tag="" tal:condition="not:item_description">
</span>
<span class="description" tal:content="item_description">
description
</span>
</p>
<p class="tileFooter">
<a href=""
tal:attributes="href item_url"
i18n:translate="read_more">
Read More…
</a>
</p>
<div class="visualClear"><!-- --></div>
</div>
</tal:block>
<!-- Navigation -->
<div metal:use-macro="here/batch_macros/macros/navigation" />
</tal:listing>
<div tal:replace="structure provider:plone.belowcontentbody" />
</tal:main-macro>
</div>
</body>
</html>