Skip to content

Commit

Permalink
Implement content.find.
Browse files Browse the repository at this point in the history
1. Implement .find to map to portal_catalog.searchResults / .__call__.
2. Allow passing in Interface objects as well as Interface.__identifier__ strings.
3. Context implicitly becomes the portal root if needed.
  • Loading branch information
jaroel committed Feb 8, 2015
1 parent c0487c8 commit 2054c60
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 6 deletions.
80 changes: 74 additions & 6 deletions docs/content.rst
Original file line number Diff line number Diff line change
Expand Up @@ -120,18 +120,86 @@ The following operations will get objects from the stucture above, including usi
Find content objects
--------------------

You can use the *catalog* to search for content.
Here is a simple example:
You can use the find function to search for content.

Finding all Documents:

.. code-block:: python
from plone import api
documents = api.content.find(portal_type='Document')
.. invisible-code-block: python
self.assertGreater(len(documents), 0)
Finding all Documents within a context:

.. code-block:: python
from plone import api
documents = api.content.find(
context=api.portal.get(), portal_type='Document')
.. invisible-code-block: python
self.assertGreater(len(documents), 0)
Limit search depth:

.. code-block:: python
from plone import api
catalog = api.portal.get_tool(name='portal_catalog')
documents = catalog(portal_type='Document')
documents = api.content.find(depth=1, portal_type='Document')
.. invisible-code-block: python
self.assertEqual(catalog.__class__.__name__, 'CatalogTool')
self.assertEqual(len(documents), 3)
self.assertGreater(len(documents), 0)
Limit search depth within a context:

.. code-block:: python
from plone import api
documents = api.content.find(
context=api.portal.get(), depth=1, portal_type='Document')
.. invisible-code-block: python
self.assertGreater(len(documents), 0)
Search by interface:

.. code-block:: python
from plone import api
from Products.ATContentTypes.interfaces.document import IATDocument
documents = api.content.find(object_provides=IATDocument)
# Or if you have the identifier:
documents = api.content.find(object_provides=IATDocument.__identifier__)
.. invisible-code-block: python
self.assertGreater(len(documents), 0)
Combining the multiple arguments:

.. code-block:: python
from plone import api
from Products.ATContentTypes.interfaces.document import IATDocument
documents = api.content.find(
context=api.portal.get(), depth=2, object_provides=IATDocument,
SearchableText='Team')
.. invisible-code-block: python
self.assertGreater(len(documents), 0)
More information about how to use the catalog may be found in the `Plone Documentation <http://docs.plone.org/develop/plone/searching_and_indexing/index.html>`_.
Note that the catalog returns *brains* (metadata stored in indexes) and not objects.
Expand Down
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def read(*rnames):
'Products.statusmessages',
'Zope2', # Globals, OFS(tests),
'decorator',
'plone.app.contentlisting',
'plone.app.uuid',
'plone.uuid',
'setuptools',
Expand All @@ -55,6 +56,7 @@ def read(*rnames):
'zest.releaser',
],
'test': [
'Products.Archetypes',
'Products.MailHost',
'Products.CMFPlone',
'Products.ZCatalog',
Expand Down
43 changes: 43 additions & 0 deletions src/plone/api/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from Products.CMFCore.interfaces import ISiteRoot
from Products.CMFCore.WorkflowCore import WorkflowException
from copy import copy as _copy
from plone.app.contentlisting.interfaces import IContentListing
from plone.api import portal
from plone.api.exc import InvalidParameterError
from plone.api.validation import at_least_one_of
Expand Down Expand Up @@ -473,3 +474,45 @@ def get_uuid(obj=None):
:Example: :ref:`content_get_uuid_example`
"""
return IUUID(obj)


def find(context=None, depth=None, **kwargs):
"""Find content
:param context: Context within we want to search
:type obj: Content object
:param depth: How far in the content tree we want to search from context
:type obj: Content object
:returns: Catalog brains
:rtype: List
:Example: :ref:`content_find_example`
"""
query = {}
query.update(**kwargs)

# Passing a context or depth overides the exising path query
if context or depth:
query['path'] = {}

# Limit search depth
if depth is not None:
# If we don't have a context, we'll assume the portal root.
if context is None:
context = portal.get()
query['path']['depth'] = depth

if context is not None:
query['path']['query'] = '/'.join(context.getPhysicalPath())

# Convert interfaces to their identifiers
object_provides = query.get('object_provides', [])
if object_provides:
if not isinstance(object_provides, list):
object_provides = [object_provides]
for k, v in enumerate(object_provides):
if not isinstance(v, basestring):
object_provides[k] = v.__identifier__
query['object_provides'] = object_provides

catalog = portal.get_tool('portal_catalog')
return IContentListing(catalog(**query))
42 changes: 42 additions & 0 deletions src/plone/api/tests/test_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,48 @@ def test_delete_multiple(self):
assert 'copy_of_about' not in container
assert 'about' not in container['events']

def test_find(self):
"""Test the finding of content in various ways."""

# Find documents
documents = api.content.find(portal_type='Document')
self.assertEqual(len(documents), 2)

def test_find_context(self):
# Find documents in context
documents = api.content.find(
context=self.portal.about, portal_type='Document')
self.assertEqual(len(documents), 2)
documents = api.content.find(
context=self.portal.events, portal_type='Document')
self.assertEqual(len(documents), 0)

def test_find_depth(self):
# Limit search depth from portal root
documents = api.content.find(depth=2, portal_type='Document')
self.assertEqual(len(documents), 2)
documents = api.content.find(depth=1, portal_type='Document')
self.assertEqual(len(documents), 0)

# Limit search depth with explicit context
documents = api.content.find(
context=self.portal.about, depth=1, portal_type='Document')
self.assertEqual(len(documents), 2)
documents = api.content.find(
context=self.portal.about, depth=0, portal_type='Document')
self.assertEqual(len(documents), 0)

def test_find_interface(self):
# Find documents by interface or it's identier
from Products.ATContentTypes.interfaces.document import IATDocument

identifier = IATDocument.__identifier__
documents = api.content.find(object_provides=identifier)
self.assertEqual(len(documents), 2)

documents = api.content.find(object_provides=IATDocument)
self.assertEqual(len(documents), 2)

def test_get_state(self):
"""Test retrieving the workflow state of a content item."""

Expand Down

0 comments on commit 2054c60

Please sign in to comment.