[Checkins]
SVN: z3c.contents/branches/darrylcousins/src/z3c/contents/
Added simple search support to contents view.
Darryl Cousins
darryl at darrylcousins.net.nz
Sat Mar 22 21:32:14 EDT 2008
Log message for revision 84858:
Added simple search support to contents view.
The contained objects are searched by available attributes and __name__ (id in
container). The search terms are also included in the query string of the
column header sorting urls. Batching links will also need to be looked at.
Changed:
U z3c.contents/branches/darrylcousins/src/z3c/contents/BROWSER.txt
U z3c.contents/branches/darrylcousins/src/z3c/contents/README.txt
U z3c.contents/branches/darrylcousins/src/z3c/contents/browser.py
U z3c.contents/branches/darrylcousins/src/z3c/contents/configure.zcml
U z3c.contents/branches/darrylcousins/src/z3c/contents/ftesting.zcml
A z3c.contents/branches/darrylcousins/src/z3c/contents/header.py
U z3c.contents/branches/darrylcousins/src/z3c/contents/interfaces.py
A z3c.contents/branches/darrylcousins/src/z3c/contents/search.py
U z3c.contents/branches/darrylcousins/src/z3c/contents/testing.py
-=-
Modified: z3c.contents/branches/darrylcousins/src/z3c/contents/BROWSER.txt
===================================================================
--- z3c.contents/branches/darrylcousins/src/z3c/contents/BROWSER.txt 2008-03-22 19:20:43 UTC (rev 84857)
+++ z3c.contents/branches/darrylcousins/src/z3c/contents/BROWSER.txt 2008-03-23 01:32:13 UTC (rev 84858)
@@ -18,11 +18,14 @@
>>> browser.handleErrors = False
>>> URL = 'http://localhost/++skin++ContentsTesting%s'
+We have sorting urls in the column headers. Initially the table is sorted on the
+name column ascending.
+
>>> browser.open(URL % '/')
>>> printElement(browser, "//table/thead/tr/th", multiple=True)
<th>X</th>
<th><a
- href="?contents-sortOn=contents-renameColumn-1&contents-sortOrder=ascending"
+ href="?contents-sortOn=contents-renameColumn-1&contents-sortOrder=descending"
title="Sort">Name</a></th>
<th><a
href="?contents-sortOn=contents-createdColumn-2&contents-sortOrder=ascending"
@@ -31,8 +34,28 @@
href="?contents-sortOn=contents-modifiedColumn-3&contents-sortOrder=ascending"
title="Sort">Modified</a></th>
- >>> #print browser.contents
Sample data set up
==================
+ >>> container = getRootFolder()
+ >>> from z3c.contents.testing import Content
+ >>> container[u'zero'] = Content('Zero', 0)
+ >>> container[u'first'] = Content('First', 1)
+ >>> container[u'second'] = Content('Second', 2)
+ >>> container[u'third'] = Content('Third', 3)
+ >>> container[u'fourth'] = Content('Fourth', 4)
+
+We can search within the container - this is a simple search on ids and string
+attributes on the contained objects.
+
+ >>> browser.getControl(name="search.widgets.searchterm").value = u'4 second Third'
+ >>> browser.getControl(name="search.buttons.search").click()
+
+Note that the searchterms are also now included in the sorting header link.
+
+ >>> printElement(browser, "//table/thead/tr/th[2]")
+ <th><a
+ href="?contents-sortOn=contents-renameColumn-1&contents-sortOrder=descending&search.widgets.searchterm=4+second+Third"
+ title="Sort">Name</a></th>
+
Modified: z3c.contents/branches/darrylcousins/src/z3c/contents/README.txt
===================================================================
--- z3c.contents/branches/darrylcousins/src/z3c/contents/README.txt 2008-03-22 19:20:43 UTC (rev 84857)
+++ z3c.contents/branches/darrylcousins/src/z3c/contents/README.txt 2008-03-23 01:32:13 UTC (rev 84858)
@@ -245,7 +245,6 @@
</div>
</form>
-
Sorting
-------
@@ -837,7 +836,112 @@
[(u'fifth', <Content Second 2>), (u'first', <Content First 1>),
(u'zero', <Content Zero 0>)]
+Search
+------
+Add an IFind adapter for the search:
+
+ >>> from z3c.contents.interfaces import ISearch
+ >>> from z3c.contents.search import SearchForContainer
+ >>> zope.component.provideAdapter(SearchForContainer,
+ ... (IContainer, ), provides=ISearch)
+
+The default search adapter matches search terms to the objects id in the
+container or to any possible string attribute.
+
+ >>> searchRequest = TestRequest(form={'search.buttons.search': 'Search',
+ ... 'search.widgets.searchterm': u'First zero'})
+ >>> alsoProvides(searchRequest, IDivFormLayer)
+ >>> searchPage = browser.ContentsPage(secondContainer, searchRequest)
+ >>> searchPage.update()
+ >>> print searchPage.render()
+ <form action="http://127.0.0.1" method="post"
+ enctype="multipart/form-data" class="edit-form"
+ name="contents" id="contents">
+ ...
+ <tbody>
+ <tr>
+ <td><input type="checkbox" class="checkbox-widget" name="contents-checkBoxColumn-0-selectedItems" value="first" /></td>
+ <td><a href="http://127.0.0.1/secondContainer/first">first</a></td>
+ <td>01/01/01 01:01</td>
+ <td>02/02/02 02:02</td>
+ </tr>
+ <tr>
+ <td><input type="checkbox" class="checkbox-widget" name="contents-checkBoxColumn-0-selectedItems" value="zero" /></td>
+ <td><a href="http://127.0.0.1/secondContainer/zero">zero</a></td>
+ <td>01/01/01 01:01</td>
+ <td>02/02/02 02:02</td>
+ </tr>
+ </tbody>
+ ...
+
+Headers
+-------
+
+We have adapters to the columns that support inclusion of links in the headers
+to sort the columns.
+ for="zope.app.container.interfaces.IContainer
+ zope.interface.Interface
+ z3c.contents.interfaces.IContentsPage
+ z3c.contents.column.RenameColumn"
+ provides="z3c.table.interfaces.IColumnHeader"
+
+ >>> from z3c.contents.header import ContentsColumnHeader
+ >>> from z3c.table.interfaces import IColumnHeader
+ >>> zope.component.provideAdapter(ContentsColumnHeader,
+ ... (IContainer, None, interfaces.IContentsPage, column.RenameColumn),
+ ... provides=IColumnHeader)
+
+Now we shall see that the name column header includes a link with arguments to
+sort the table by that column.
+
+ >>> headerRequest = TestRequest()
+ >>> alsoProvides(headerRequest, IDivFormLayer)
+ >>> headerPage = browser.ContentsPage(secondContainer, headerRequest)
+ >>> headerPage.update()
+ >>> print headerPage.render()
+ <form action="http://127.0.0.1" method="post"
+ enctype="multipart/form-data" class="edit-form"
+ name="contents" id="contents">
+ ...
+ <thead>
+ <tr>
+ <th>X</th>
+ <th><a
+ href="?contents-sortOn=contents-renameColumn-1&contents-sortOrder=ascending"
+ title="Sort">Name</a></th>
+ <th>Created</th>
+ <th>Modified</th>
+ </tr>
+ </thead>
+ ...
+
+When we perform a search we also expect the the search terms will also be
+included in the query so as to maintain the search across views.
+
+ >>> searchRequest = TestRequest(form={'search.buttons.search': 'Search',
+ ... 'search.widgets.searchterm': u'First zero'})
+ >>> alsoProvides(searchRequest, IDivFormLayer)
+ >>> searchPage = browser.ContentsPage(secondContainer, searchRequest)
+ >>> searchPage.update()
+ >>> print searchPage.render()
+ <form action="http://127.0.0.1" method="post"
+ enctype="multipart/form-data" class="edit-form"
+ name="contents" id="contents">
+ ...
+ <thead>
+ <tr>
+ <th>X</th>
+ <th><a
+ href="?contents-sortOn=contents-renameColumn-1&contents-sortOrder=ascending&search.widgets.searchterm=First+zero"
+ title="Sort">Name</a></th>
+ <th>Created</th>
+ <th>Modified</th>
+ </tr>
+ </thead>
+ ...
+
+
Batching
--------
Modified: z3c.contents/branches/darrylcousins/src/z3c/contents/browser.py
===================================================================
--- z3c.contents/branches/darrylcousins/src/z3c/contents/browser.py 2008-03-22 19:20:43 UTC (rev 84857)
+++ z3c.contents/branches/darrylcousins/src/z3c/contents/browser.py 2008-03-23 01:32:13 UTC (rev 84858)
@@ -31,7 +31,7 @@
from zope.security.interfaces import Unauthorized
from zope.traversing.interfaces import TraversalError
from zope.traversing import api
-
+from zope.app.container.find import SimpleIdFindFilter
from zope.app.container.interfaces import DuplicateIDError
from z3c.form import button, field
@@ -40,6 +40,7 @@
from z3c.template.template import getPageTemplate
from z3c.contents import interfaces
+from z3c.contents.search import SimpleAttributeFindFilter
_ = zope.i18nmessageid.MessageFactory('z3c')
@@ -62,7 +63,9 @@
class ContentsSearch(object):
- """An adapter for container context to satisfy search form requirements"""
+ """An adapter for container context to satisfy search form requirements
+
+ """
zope.interface.implements(interfaces.IContentsSearch)
zope.component.adapts(zope.interface.Interface)
@@ -83,10 +86,15 @@
template = getPageTemplate()
fields = field.Fields(interfaces.IContentsSearch)
prefix = 'search'
+ table = None
@button.buttonAndHandler(_('Search'), name='search')
def handleSearch(self, action):
- pass
+ data, errors = self.extractData()
+ if errors:
+ self.status = u'Some error message'
+ return
+ self.table.searchterm = data.get('searchterm', '')
# conditions
@@ -130,6 +138,9 @@
supportsPaste = False
supportsRename = False
+ # sort attributes
+ sortOn = 1 # initial sort on name column
+
# customize this part
allowCut = True
allowCopy = True
@@ -138,6 +149,7 @@
allowRename = True
prefix = 'contents'
+ searchterm = ''
# error messages
cutNoItemsMessage = _('No items selected for cut')
@@ -158,14 +170,16 @@
renameItemNotFoundMessage = _('Item not found')
def update(self):
+
+ self.search = ContentsSearchForm(self.context, self.request)
+ self.search.table = self
+ self.search.update()
+
# first setup columns and process the items as selected if any
super(ContentsPage, self).update()
# second find out if we support paste
self.clipboard = queryPrincipalClipboard(self.request)
- self.search = ContentsSearchForm(self.context, self.request)
- self.search.update()
-
self.setupCopyPasteMove()
self.updateWidgets()
self.updateActions()
@@ -196,6 +210,28 @@
return self.template()
@property
+ def values(self):
+ # not searching
+ if not self.searchterm:
+ return self.context.values()
+
+ # no search adapter for the context
+ try:
+ search = interfaces.ISearch(self.context)
+ except TypeError:
+ return self.context.values()
+
+ # perform the search
+ searchterms = self.searchterm.split(' ')
+
+ # possible enhancement would be to look up these filters as adapters to
+ # the container!
+ result = search.search(id_filters=[SimpleIdFindFilter(searchterms)],
+ object_filters=[SimpleAttributeFindFilter(searchterms)])
+ return result
+
+
+ @property
def hasContent(self):
return bool(self.values)
Modified: z3c.contents/branches/darrylcousins/src/z3c/contents/configure.zcml
===================================================================
--- z3c.contents/branches/darrylcousins/src/z3c/contents/configure.zcml 2008-03-22 19:20:43 UTC (rev 84857)
+++ z3c.contents/branches/darrylcousins/src/z3c/contents/configure.zcml 2008-03-23 01:32:13 UTC (rev 84858)
@@ -42,10 +42,17 @@
factory="z3c.contents.browser.ContentsSearch"
/>
+ <adapter
+ provides="z3c.contents.interfaces.ISearch"
+ for="zope.app.container.interfaces.IReadContainer"
+ permission="zope.Public"
+ factory="z3c.contents.search.SearchForContainer"
+ />
+
<!-- include also sorting column headers for some columns
leaving out CheckBoxColumn -->
<adapter
- factory="z3c.table.header.SortingColumnHeader"
+ factory="z3c.contents.header.ContentsColumnHeader"
for="zope.app.container.interfaces.IContainer
zope.interface.Interface
z3c.contents.interfaces.IContentsPage
@@ -54,7 +61,7 @@
/>
<adapter
- factory="z3c.table.header.SortingColumnHeader"
+ factory="z3c.contents.header.ContentsColumnHeader"
for="zope.app.container.interfaces.IContainer
zope.interface.Interface
z3c.contents.interfaces.IContentsPage
@@ -63,7 +70,7 @@
/>
<adapter
- factory="z3c.table.header.SortingColumnHeader"
+ factory="z3c.contents.header.ContentsColumnHeader"
for="zope.app.container.interfaces.IContainer
zope.interface.Interface
z3c.contents.interfaces.IContentsPage
Modified: z3c.contents/branches/darrylcousins/src/z3c/contents/ftesting.zcml
===================================================================
--- z3c.contents/branches/darrylcousins/src/z3c/contents/ftesting.zcml 2008-03-22 19:20:43 UTC (rev 84857)
+++ z3c.contents/branches/darrylcousins/src/z3c/contents/ftesting.zcml 2008-03-23 01:32:13 UTC (rev 84858)
@@ -81,4 +81,18 @@
permission="zope.ManageContent"
/>
+ <class class="z3c.contents.testing.Content">
+ <implements
+ interface="zope.annotation.interfaces.IAttributeAnnotatable"
+ />
+ <allow
+ interface="z3c.contents.testing.IContent"
+ />
+ <require
+ permission="zope.ManageContent"
+ set_schema="z3c.contents.testing.IContent"
+ />
+ </class>
+
+
</configure>
Added: z3c.contents/branches/darrylcousins/src/z3c/contents/header.py
===================================================================
--- z3c.contents/branches/darrylcousins/src/z3c/contents/header.py (rev 0)
+++ z3c.contents/branches/darrylcousins/src/z3c/contents/header.py 2008-03-23 01:32:13 UTC (rev 84858)
@@ -0,0 +1,26 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+
+from z3c.table.header import SortingColumnHeader
+
+class ContentsColumnHeader(SortingColumnHeader):
+ """Sorting column header."""
+
+ request_args = ['search.widgets.searchterm']
+
Property changes on: z3c.contents/branches/darrylcousins/src/z3c/contents/header.py
___________________________________________________________________
Name: svn:keywords
+ Id
Modified: z3c.contents/branches/darrylcousins/src/z3c/contents/interfaces.py
===================================================================
--- z3c.contents/branches/darrylcousins/src/z3c/contents/interfaces.py 2008-03-22 19:20:43 UTC (rev 84857)
+++ z3c.contents/branches/darrylcousins/src/z3c/contents/interfaces.py 2008-03-23 01:32:13 UTC (rev 84858)
@@ -25,7 +25,8 @@
class IContentsPage(interfaces.ITable):
- """Container management page"""
+ """Container management page
+ """
class IContentsSearch(zope.interface.Interface):
"""We would like to provide a search field for searching within the
@@ -36,3 +37,20 @@
"""
searchterm = zope.schema.TextLine(title=_(u'Search'))
+
+
+class ISearch(zope.interface.Interface):
+ """
+ Search support for containers.
+
+ This is different to z.a.c.interfaces.IFind in that the search method
+ will match **any** filter whereas the find method of IFind will match
+ if **all** filters match.
+ """
+
+ def search(id_filters=None, object_filters=None):
+ """Find object that matches **any** filters in all sub-objects.
+
+ This container itself is not included.
+ """
+
Added: z3c.contents/branches/darrylcousins/src/z3c/contents/search.py
===================================================================
--- z3c.contents/branches/darrylcousins/src/z3c/contents/search.py (rev 0)
+++ z3c.contents/branches/darrylcousins/src/z3c/contents/search.py 2008-03-23 01:32:13 UTC (rev 84858)
@@ -0,0 +1,92 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+from zope.app.container.interfaces import (IObjectFindFilter,
+ IReadContainer)
+from zope.security.proxy import removeSecurityProxy
+
+from z3c.contents.interfaces import ISearch
+
+class SearchForContainer(object):
+
+ zope.interface.implements(ISearch)
+
+ __used_for__ = IReadContainer
+
+ def __init__(self, context):
+ self._context = context
+
+ def search(self, id_filters=None, object_filters=None):
+ 'See ISearch'
+ id_filters = id_filters or []
+ object_filters = object_filters or []
+ result = []
+ container = self._context
+ for id, object in container.items():
+ _search_helper(id, object, container,
+ id_filters, object_filters,
+ result)
+ return result
+
+
+def _search_helper(id, object, container, id_filters, object_filters, result):
+ # check id filters if we get a match then return immediately
+ for id_filter in id_filters:
+ if id_filter.matches(id):
+ result.append(object)
+ return
+
+ # now check all object filters
+ for object_filter in object_filters:
+ if object_filter.matches(object):
+ result.append(object)
+ return
+
+ # do we need to check sub containers?
+ if not IReadContainer.providedBy(object):
+ return
+
+ container = object
+ for id, object in container.items():
+ _search_helper(id, object, container, id_filters, object_filters, result)
+
+
+class SimpleAttributeFindFilter(object):
+ """Filter objects on text or integer attributes"""
+ zope.interface.implements(IObjectFindFilter)
+
+ def __init__(self, terms):
+ self.terms = terms
+
+ def matches(self, object):
+ """Check if one of the search terms is in any text or integer field of
+ this object
+
+ """
+ # surely a better way to get to the attributes than is done here?
+ object = removeSecurityProxy(object)
+
+ for key in [k for k in dir(object) if not k.startswith('_')]:
+ value = str(getattr(object, key)).lower()
+ for term in self.terms:
+ term = term.lower()
+ if term in value:
+ return True
+ return False
+
Property changes on: z3c.contents/branches/darrylcousins/src/z3c/contents/search.py
___________________________________________________________________
Name: svn:keywords
+ Id
Modified: z3c.contents/branches/darrylcousins/src/z3c/contents/testing.py
===================================================================
--- z3c.contents/branches/darrylcousins/src/z3c/contents/testing.py 2008-03-22 19:20:43 UTC (rev 84857)
+++ z3c.contents/branches/darrylcousins/src/z3c/contents/testing.py 2008-03-23 01:32:13 UTC (rev 84858)
@@ -16,6 +16,7 @@
"""
__docformat__ = "reStructuredText"
+import zope.interface
from zope.publisher.interfaces.browser import IBrowserRequest
import z3c.layer.ready2go
@@ -26,8 +27,14 @@
class IContentsTestBrowserSkin(IContentsTestBrowserLayer):
"""The browser skin."""
+class IContent(zope.interface.Interface):
+
+ title = zope.schema.TextLine(title=u'Title')
+ number = zope.schema.Int(title=u'Number')
+
class Content(object):
"""Sample content which is pickable for copy test."""
+ zope.interface.implements(IContent)
def __init__(self, title, number):
self.title = title
self.number = number
More information about the Checkins
mailing list