Listing objects

Introduction

Plone has several methods of getting the list of folder items depending on whether

  • You want to get all items or items visible for the currently logged in user
  • You want to get hold of the item objects themselves or just indexed search information is enough (the latter is faster)
  • You want to get Plone’s contentish items only (contentItems) or Zope 2 management objects too (objectIds), the latter covers various site utilities found in the portal root and otherwise hidden magical items

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.

Getting all content objects inside a folder

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.

Getting folder objects filtered

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.

Rules for filtering items

Plone applies some default rules for listFolderContents()

  • portal_properties.nav_tree_properties.metaTypesNotToQuery: folders (large folders) don’t generate listing
  • default_page is not listed
  • portal_properties.nav_tree_properties.: meta types marked here do not appear in the listing

Getting object ids

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.

Getting non-contentish Zope objects

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

Checking for the existence of a particular object id

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

Listing the folder items using portal_catalog

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

Count of content items

Counting items using getFolderContents

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())

Counting items using contentItems

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]

Custom folder listing

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"
    />
  • Below is the example view code, named as productcardsummaryview.py.
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()
  • Below is the corresponding page template skeleton productcardsummaryview.pt
<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">
                                &nbsp;
                            </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&hellip;
                            </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>
  • Go to view page by adding /@@productsummary to your folder URL.

Making view to be available in Display... menu

You need to add browser:menuItem entry to make your view appear in the Display... menu where folders and topics can choose the style of the display.

See ::doc::dynamic views </content/dynamic_views>.

You need to add

  • <browser:menuItem> configuration directive with view id (e.g. @@productsummary)
  • New properties to Folder.xml or Topic.xml so that view becomes available