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.
The method contentItems is defined in CMFCore/PortalFolder.py. From Plone 4 onwards, you can also use folder.items() instead (this applies to the whole section below). 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)
警告
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.
警告
folder.contentItems() returns all items regardless of the user security context.
listFolderContents() method retrieves the full objects in the folder. It takes contentFilter as an argument which can be used to filter the results. contentFilter uses the same syntax as portal_catalog queries but does not support all the same parameters e.g. “object_provides” is not supported. See the ContentFilter class for details.
Example:
# List all types in this folder whose portal_type is "CourseModulePage"
return self.listFolderContents(contentFilter={"portal_type" : "CourseModulePage"})
警告
Security warning: listFolderContens() honors the currently logged in user roles.
警告
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, or keys() in Plone 4. This is a fast method:
# Return a list of object ids in the folder
ids = folder.objectIds() # Plone 3 or older
ids = folder.keys() # Plone 4 or newer
警告
objectIds() and keys() will return 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():
# "elif myid in context:" in Plone 4 or newer
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).
警告
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() (or simply items() in Plone 4 or newer) as this will potentially wake less items than complex catalog query.
警告
Security: This method does not consider access rights.
Example (AT content class method):
def getMainImage(self):
items = self.contentItems() # id, object tuples
# "items = self.items()" in Plone 4 or newer
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.
警告
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>
The following example is for a very complex folder listing view. You can call view methods to returns the listed items themselves and render the HTML in another view - this allows you to recycle this listing code easily.
The view does the various sanity checks what normal Plone item listings have
Example code
class FolderListingView(BrowserView):
""" Mobile folder listing helper view
Use getItems() to get list of mobile folder listable items for automatically generated
mobile folder listings (touch button list).
"""
def getListingContainer(self):
""" Get the item for which we perform the listing
"""
context = self.context.aq_inner
if IFolderish.providedBy(context):
return context
else:
return context.aq_parent
def getActiveTemplate(self):
state = getMultiAdapter((self.context, self.request), name=u'plone_context_state')
return state.view_template_id()
def getTemplateIdsNoListing(self):
"""
@return: List of mobile-specific ids found from portal_properties where not to show folder listing
"""
try:
from gomobile.mobile.utilities import getCachedMobileProperties
context = aq_inner(self.context)
mobile_properties = getCachedMobileProperties(context, self.request)
except:
mobile_properties = None
return getattr(mobile_properties, "no_folder_listing_view_ids", [])
def filterItems(self, container, items):
""" Apply mobile specific filtering rules
@param items: List of context brains
"""
# Filter out default content
default_page_helper = getMultiAdapter((container, self.request), name='default_page')
portal_state = getMultiAdapter((container, self.request), name='plone_portal_state')
# Active language
language = portal_state.language()
# Return the default page id or None if not set
default_page = default_page_helper.getDefaultPage(container)
security_manager = getSecurityManager()
meta_types_not_to_list = container.portal_properties.navtree_properties.metaTypesNotToList
def show(item):
""" Filter whether the user can view a mobile item.
@param item: Real content object (not brain)
@return: True if item should be visible in the listing
"""
# Check from mobile behavior should we do the listing
try:
behavior = IMobileBehavior(item)
appearInFolderListing = behavior.appearInFolderListing
except TypeError:
# Site root or some weird object, give up
appearInFolderListing = True
if not appearInFolderListing:
# Default to appearing
return False
# Default page should not appear in the quick listing
if item.getId() == default_page:
return False
if item.meta_type in meta_types_not_to_list:
return False
# Two letter language code
item_lang = item.Language()
# Empty string makes language netral content
if item_lang not in ["", None]:
if item_lang != language:
return False
# Note: getExcludeFromNav not necessarily exist on all content types
if hasattr(item, "getExcludeFromNav"):
if item.getExcludeFromNav():
return False
# Does the user have a permission to view this object
if not security_manager.checkPermission(permissions.View, item):
return False
return True
return [ i for i in items if show(i) == True ]
def constructListing(self):
# Iterable of content items for the item listing
items = []
# Check from mobile behavior should we do the listing
try:
behavior = IMobileBehavior(self.context)
do_listing = behavior.mobileFolderListing
except TypeError:
# Site root or some weird object, give up
do_listing = False
# Do listing by default, must be explictly disabledc
if not do_listing:
# No mobile behavior -> no mobile listing
return None
container = self.getListingContainer()
# Do not list if already doing folder listing
template = self.getActiveTemplate()
print "Active template id:" + template
if template in self.getTemplateIdsNoListing():
# Listing forbidden by mobile rules
return None
portal_properties = getToolByName(container, "portal_properties")
navtree_properties = portal_properties.navtree_properties
if container.meta_type in navtree_properties.parentMetaTypesNotToQuery:
# Big folder... listing forbidden
return None
state = container.restrictedTraverse('@@plone_portal_state')
items = container.listFolderContents()
items = self.filterItems(container, items)
return items
def getItems(self):
"""
@return: Iterable of content objects. Never return None.
"""
items = self.constructListing()
if items == None:
return []
return items