[Checkins] SVN: z3c.contents/trunk/ Merge darrylcousins branch back to the trunk

Roger Ineichen roger at projekt01.ch
Fri Apr 11 10:57:35 EDT 2008


Log message for revision 85255:
  Merge darrylcousins branch back to the trunk
  Improve buildout setup

Changed:
  U   z3c.contents/trunk/CHANGES.txt
  U   z3c.contents/trunk/buildout.cfg
  _U  z3c.contents/trunk/externals/
  U   z3c.contents/trunk/setup.py
  A   z3c.contents/trunk/src/z3c/contents/BROWSER.txt
  U   z3c.contents/trunk/src/z3c/contents/README.txt
  U   z3c.contents/trunk/src/z3c/contents/browser.py
  U   z3c.contents/trunk/src/z3c/contents/browser.zcml
  U   z3c.contents/trunk/src/z3c/contents/column.py
  U   z3c.contents/trunk/src/z3c/contents/configure.zcml
  U   z3c.contents/trunk/src/z3c/contents/contents.pt
  A   z3c.contents/trunk/src/z3c/contents/ftesting.zcml
  A   z3c.contents/trunk/src/z3c/contents/ftests.py
  A   z3c.contents/trunk/src/z3c/contents/header.py
  U   z3c.contents/trunk/src/z3c/contents/interfaces.py
  A   z3c.contents/trunk/src/z3c/contents/search.pt
  A   z3c.contents/trunk/src/z3c/contents/search.py
  A   z3c.contents/trunk/src/z3c/contents/testing.pt
  U   z3c.contents/trunk/src/z3c/contents/testing.py
  U   z3c.contents/trunk/src/z3c/contents/tests.py

-=-
Modified: z3c.contents/trunk/CHANGES.txt
===================================================================
--- z3c.contents/trunk/CHANGES.txt	2008-04-11 14:23:24 UTC (rev 85254)
+++ z3c.contents/trunk/CHANGES.txt	2008-04-11 14:57:35 UTC (rev 85255)
@@ -5,4 +5,6 @@
 Version 0.5.0 (unreleased)
 --------------------------
 
+- Merged search branch back to trunk
+
 - Initial Release

Modified: z3c.contents/trunk/buildout.cfg
===================================================================
--- z3c.contents/trunk/buildout.cfg	2008-04-11 14:23:24 UTC (rev 85254)
+++ z3c.contents/trunk/buildout.cfg	2008-04-11 14:57:35 UTC (rev 85255)
@@ -1,12 +1,13 @@
 [buildout]
 develop = .
           externals/z3c.table
-parts = test checker coverage
+parts = test checker coverage-test coverage-report
 
 
 [test]
 recipe = zc.recipe.testrunner
 eggs = z3c.contents [test]
+defaults = ['--tests-pattern', '^f?tests$', '-v']
 
 
 [checker]
@@ -14,6 +15,14 @@
 path = src/z3c/contents
 
 
-[coverage]
+[coverage-test]
+recipe = zc.recipe.testrunner
+eggs = z3c.contents [test]
+defaults = ['--coverage', '../../coverage']
+
+
+[coverage-report]
 recipe = zc.recipe.egg
 eggs = z3c.coverage
+scripts = coverage=coverage-report
+arguments = ('coverage', 'coverage/report')


Property changes on: z3c.contents/trunk/externals
___________________________________________________________________
Name: svn:externals
   - z3c.table          svn://svn.zope.org/repos/main/z3c.table/trunk

   + z3c.table         svn://svn.zope.org/repos/main/z3c.table/trunk


Modified: z3c.contents/trunk/setup.py
===================================================================
--- z3c.contents/trunk/setup.py	2008-04-11 14:23:24 UTC (rev 85254)
+++ z3c.contents/trunk/setup.py	2008-04-11 14:57:35 UTC (rev 85255)
@@ -23,7 +23,7 @@
 
 setup (
     name='z3c.contents',
-    version='0.5.0',
+    version='0.5.0dev',
     author = "Roger Ineichen and the Zope Community",
     author_email = "zope3-dev at zope.org",
     description = "Container management page based on z3c.form and z3c.table for Zope3",
@@ -52,10 +52,14 @@
     extras_require = dict(
         test = [
             'z3c.macro',
+            'z3c.layer.ready2go',
+            'z3c.macro',
             'z3c.table',
+            'z3c.etestbrowser',
             'zope.app.pagetemplate',
+            'zope.app.securitypolicy',
             'zope.app.testing',
-            'zope.component',
+            'zope.publisher',
             'zope.testing',
             ],
         ),
@@ -68,12 +72,18 @@
         'z3c.table',
         'z3c.template',
         'zope.annotation',
+        'zope.app.container',
+        'zope.component',
         'zope.contentprovider',
         'zope.copypastemove',
         'zope.exceptions',
+        'zope.i18n',
+        'zope.i18nmessageid',
+        'zope.index',
         'zope.interface',
+        'zope.schema',
         'zope.security',
         'zope.traversing',
         ],
     zip_safe = False,
-)
\ No newline at end of file
+)

Copied: z3c.contents/trunk/src/z3c/contents/BROWSER.txt (from rev 85254, z3c.contents/branches/darrylcousins/src/z3c/contents/BROWSER.txt)
===================================================================
--- z3c.contents/trunk/src/z3c/contents/BROWSER.txt	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/contents/BROWSER.txt	2008-04-11 14:57:35 UTC (rev 85255)
@@ -0,0 +1,60 @@
+========
+Contents
+========
+
+The goal of this package is to offer a modular replacement for the default 
+contents.html page used in Zope3.
+
+Initially
+=========
+
+In ftesting.zcml we have set up a test layer to use for the following tests and
+have also registered a Contents view as 'index' for the root folder:
+
+  >>> from z3c.etestbrowser.testing import ExtendedTestBrowser
+  >>> import zope.component
+  >>> browser = ExtendedTestBrowser()
+  >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+  >>> 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&amp;contents-sortOrder=descending"
+    title="Sort">Name</a></th>
+  <th><a
+    href="?contents-sortOn=contents-createdColumn-2&amp;contents-sortOrder=ascending"
+    title="Sort">Created</a></th>
+  <th><a
+    href="?contents-sortOn=contents-modifiedColumn-3&amp;contents-sortOrder=ascending"
+    title="Sort">Modified</a></th>
+
+
+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&amp;contents-sortOrder=descending&amp;search.widgets.searchterm=4+second+Third"
+    title="Sort">Name</a></th>

Modified: z3c.contents/trunk/src/z3c/contents/README.txt
===================================================================
--- z3c.contents/trunk/src/z3c/contents/README.txt	2008-04-11 14:23:24 UTC (rev 85254)
+++ z3c.contents/trunk/src/z3c/contents/README.txt	2008-04-11 14:57:35 UTC (rev 85255)
@@ -44,7 +44,8 @@
   >>> from z3c.form.testing import setupFormDefaults
   >>> setupFormDefaults()
 
-And we need to configure our contents.pt template for the ContentsPage:
+And we need to configure our contents.pt template for the ContentsPage (we
+also configure the template for the search sub form here too)
 
   >>> import os
   >>> import sys
@@ -53,6 +54,8 @@
   >>> context = xmlconfig.file('meta.zcml', z3c.template)
   >>> contentsTemplate = os.path.join(os.path.dirname(z3c.contents.__file__),
   ...     'contents.pt')
+  >>> searchTemplate = os.path.join(os.path.dirname(z3c.contents.__file__),
+  ...     'search.pt')
   >>> context = xmlconfig.string("""
   ... <configure
   ...     xmlns:z3c="http://namespaces.zope.org/z3c">
@@ -60,8 +63,12 @@
   ...       for="z3c.contents.interfaces.IContentsPage"
   ...       template="%s"
   ...       />
+  ...   <z3c:template
+  ...       for="z3c.contents.browser.ContentsSearchForm"
+  ...       template="%s"
+  ...       />
   ... </configure>
-  ... """ % contentsTemplate, context=context)
+  ... """ % (contentsTemplate, searchTemplate), context=context)
 
 
 And load the formui confguration, which will make sure that all macros get 
@@ -110,6 +117,7 @@
            &ndash; required
         </div>
       <div>
+  ...
     </div>
     </div>
     <div>
@@ -174,6 +182,7 @@
            &ndash; required
         </div>
       <div>
+  ...
         <table>
           <thead>
             <tr>
@@ -236,7 +245,6 @@
     </div>
   </form>
 
-
 Sorting
 -------
 
@@ -361,6 +369,7 @@
            &ndash; required
         </div>
       <div>
+  ...
         <table>
           <thead>
             <tr>
@@ -827,7 +836,113 @@
   [(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)
+
+And also register ISearchableText adapter for the contained objects, the default
+filters for searching include searching the keys (content __name__) and using
+an ISearchableText adapter for the contained objects.
+
+  >>> from z3c.contents.testing import SearchableTextForContent
+  >>> zope.component.provideAdapter(SearchableTextForContent)
+
+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'1 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.
+
+  >>> 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=descending"
+      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=descending&search.widgets.searchterm=First+zero"
+      title="Sort">Name</a></th>
+      <th>Created</th>
+      <th>Modified</th>
+    </tr>
+  </thead>
+  ...
+
+
 Batching
 --------
 
@@ -855,11 +970,29 @@
            &ndash; required
         </div>
       <div>
+      <fieldset>
+        <legend>Search</legend>
+          <table>
+  <tr>
+  <td class="row">
+    <label for="search-widgets-searchterm">Search</label>
+    <input type="text" id="search-widgets-searchterm"
+         name="search.widgets.searchterm"
+         class="text-widget required textline-field" value="" />
+  </td>
+  <td class="action">
+    <input type="submit" id="search-buttons-search"
+         name="search.buttons.search"
+         class="submit-widget button-field" value="Search" />
+  </td>
+  </tr>
+  </table>
+      </fieldset>
       <table class="contents">
     <thead>
       <tr>
         <th>X</th>
-        <th>Name</th>
+        <th><a href="?contents-sortOn=contents-renameColumn-1&contents-sortOrder=descending" title="Sort">Name</a></th>
         <th>Created</th>
         <th>Modified</th>
       </tr>

Modified: z3c.contents/trunk/src/z3c/contents/browser.py
===================================================================
--- z3c.contents/trunk/src/z3c/contents/browser.py	2008-04-11 14:23:24 UTC (rev 85254)
+++ z3c.contents/trunk/src/z3c/contents/browser.py	2008-04-11 14:57:35 UTC (rev 85255)
@@ -31,16 +31,17 @@
 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 IContainerNamesContainer
 from zope.app.container.interfaces import DuplicateIDError
 
-from z3c.form import button
+from z3c.form import button, field
 from z3c.formui import form
 from z3c.table import table
 from z3c.template.template import getPageTemplate
 
 from z3c.contents import interfaces
+from z3c.contents.search import SearchableTextFindFilter
 
 _ = zope.i18nmessageid.MessageFactory('z3c')
 
@@ -62,6 +63,41 @@
         return default
 
 
+class ContentsSearch(object):
+    """An adapter for container context to satisfy search form requirements
+    
+    """
+
+    zope.interface.implements(interfaces.IContentsSearch)
+    zope.component.adapts(zope.interface.Interface)
+
+    def __init__(self, context):
+        self.context = context
+
+    def get_searchterm(self):
+        return u''
+
+    def set_searchterm(self, value):
+        pass
+
+    searchterm = property(get_searchterm, set_searchterm)
+
+class ContentsSearchForm(form.Form):
+
+    template = getPageTemplate()
+    fields = field.Fields(interfaces.IContentsSearch)
+    prefix = 'search'
+    table = None
+
+    @button.buttonAndHandler(_('Search'), name='search')
+    def handleSearch(self, action):
+        data, errors = self.extractData()
+        if errors:
+            self.status = u'Some error message'
+            return
+        self.table.searchterm = data.get('searchterm', '')
+
+
 # conditions
 def canCut(form):
     return form.supportsCut
@@ -90,6 +126,9 @@
 
     template = getPageTemplate()
 
+    # search sub-form
+    search = None
+
     # internal defaults
     selectedItems = []
     ignoreContext = False
@@ -100,6 +139,9 @@
     supportsPaste = False
     supportsRename = False
 
+    # sort attributes
+    sortOn = 1 # initial sort on name column
+
     # customize this part
     allowCut = True
     allowCopy = True
@@ -108,6 +150,7 @@
     allowRename = True
 
     prefix = 'contents'
+    searchterm = ''
 
     # error messages
     cutNoItemsMessage = _('No items selected for cut')
@@ -128,10 +171,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.setupCopyPasteMove()
         self.updateWidgets()
         self.updateActions()
@@ -162,6 +211,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! Maybe we can use catalogs here?
+        return search.search(id_filters=[SimpleIdFindFilter(searchterms)],
+                            object_filters=[SearchableTextFindFilter(searchterms)])
+
+
+    @property
     def hasContent(self):
         return bool(self.values)
 
@@ -326,7 +397,7 @@
             return
         try:
             for item in self.selectedItems:
-                del self.context[api.getName(item)]
+                self.executeDelete(item)
         except KeyError:
             self.status = self.deleteErrorMessage
             transaction.doom()
@@ -334,6 +405,11 @@
         self.updateAfterActionExecution()
         self.status = self.deleteSucsessMessage
 
+    def executeDelete(self, item):
+        """Do the actual item deletion
+        """
+        del self.context[api.getName(item)]
+
     @button.buttonAndHandler(_('Rename'), name='rename', condition=canRename)
     def handlerRename(self, action):
         changed = False

Modified: z3c.contents/trunk/src/z3c/contents/browser.zcml
===================================================================
--- z3c.contents/trunk/src/z3c/contents/browser.zcml	2008-04-11 14:23:24 UTC (rev 85254)
+++ z3c.contents/trunk/src/z3c/contents/browser.zcml	2008-04-11 14:57:35 UTC (rev 85255)
@@ -16,4 +16,10 @@
       layer="zope.publisher.interfaces.browser.IBrowserRequest"
       />
 
+  <z3c:template
+      template="search.pt"
+      for=".browser.ContentsSearchForm"
+      layer="zope.publisher.interfaces.browser.IBrowserRequest"
+      />
+
 </configure>

Modified: z3c.contents/trunk/src/z3c/contents/column.py
===================================================================
--- z3c.contents/trunk/src/z3c/contents/column.py	2008-04-11 14:23:24 UTC (rev 85254)
+++ z3c.contents/trunk/src/z3c/contents/column.py	2008-04-11 14:57:35 UTC (rev 85255)
@@ -70,3 +70,4 @@
                 itemLink, key, newName, msg)
         else:
             return itemLink
+

Modified: z3c.contents/trunk/src/z3c/contents/configure.zcml
===================================================================
--- z3c.contents/trunk/src/z3c/contents/configure.zcml	2008-04-11 14:23:24 UTC (rev 85254)
+++ z3c.contents/trunk/src/z3c/contents/configure.zcml	2008-04-11 14:57:35 UTC (rev 85255)
@@ -38,6 +38,47 @@
       provides="z3c.table.interfaces.IColumn"
       />
 
+  <adapter
+      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.contents.header.ContentsColumnHeader"
+      for="zope.app.container.interfaces.IContainer
+           zope.interface.Interface
+           z3c.contents.interfaces.IContentsPage
+           z3c.contents.column.RenameColumn"
+      provides="z3c.table.interfaces.IColumnHeader"
+      />
+
+  <adapter
+      factory="z3c.contents.header.ContentsColumnHeader"
+      for="zope.app.container.interfaces.IContainer
+           zope.interface.Interface
+           z3c.contents.interfaces.IContentsPage
+           z3c.table.column.CreatedColumn"
+      provides="z3c.table.interfaces.IColumnHeader"
+      />
+
+  <adapter
+      factory="z3c.contents.header.ContentsColumnHeader"
+      for="zope.app.container.interfaces.IContainer
+           zope.interface.Interface
+           z3c.contents.interfaces.IContentsPage
+           z3c.table.column.ModifiedColumn"
+      provides="z3c.table.interfaces.IColumnHeader"
+      />
+
+
   <include file="browser.zcml" />
 
 </configure>

Modified: z3c.contents/trunk/src/z3c/contents/contents.pt
===================================================================
--- z3c.contents/trunk/src/z3c/contents/contents.pt	2008-04-11 14:23:24 UTC (rev 85254)
+++ z3c.contents/trunk/src/z3c/contents/contents.pt	2008-04-11 14:57:35 UTC (rev 85255)
@@ -1,5 +1,9 @@
 <div metal:use-macro="macro:form">
   <div metal:fill-slot="main">
+    <fieldset>
+      <legend>Search</legend>
+        <tal:block replace="structure view/search/render">search form</tal:block>
+    </fieldset>
     <tal:block replace="structure view/renderTable">table</tal:block>
     <tal:block define="batch view/renderBatch">
     <div class="batch" tal:condition="batch">

Copied: z3c.contents/trunk/src/z3c/contents/ftesting.zcml (from rev 85254, z3c.contents/branches/darrylcousins/src/z3c/contents/ftesting.zcml)
===================================================================
--- z3c.contents/trunk/src/z3c/contents/ftesting.zcml	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/contents/ftesting.zcml	2008-04-11 14:57:35 UTC (rev 85255)
@@ -0,0 +1,98 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:browser="http://namespaces.zope.org/browser"
+    xmlns:z3c="http://namespaces.zope.org/z3c"
+    i18n_domain="demo">
+
+  <include package="zope.app.component" file="meta.zcml" />
+  <include package="zope.app.component.browser" file="meta.zcml" />
+  <include package="zope.app.container.browser" file="meta.zcml" />
+  <include package="zope.app.form.browser" file="meta.zcml" />
+  <include package="zope.app.pagetemplate" file="meta.zcml" />
+  <include package="zope.app.publication" file="meta.zcml" />
+  <include package="zope.app.publisher" file="meta.zcml" />
+  <include package="zope.app.security" file="meta.zcml" />
+  <include package="zope.app.securitypolicy" file="meta.zcml" />
+  <include package="zope.viewlet" file="meta.zcml" />
+  <include package="z3c.form" file="meta.zcml" />
+  <include package="z3c.macro" file="meta.zcml" />
+  <include package="z3c.pagelet" file="meta.zcml" />
+  <include package="z3c.template" file="meta.zcml" />
+
+  <browser:menu id="zmi_views" title="Views" />
+  <browser:menu id="zmi_actions" title="Actions" />
+
+  <include package="zope.app.appsetup" />
+  <include package="zope.app.component" />
+  <include package="zope.app.container" />
+  <include package="zope.app.error" />
+  <include package="zope.app.folder" />
+  <include package="zope.app.i18n" />
+  <include package="zope.app.publication" />
+  <include package="zope.app.security" />
+  <include package="zope.app.securitypolicy" />
+  <include package="zope.app.session" />
+  <include package="zope.app.wsgi" />
+  <include package="zope.annotation" />
+  <include package="zope.component" />
+  <include package="zope.contentprovider" />
+  <include package="zope.location" />
+  <include package="zope.publisher" />
+  <include package="zope.traversing" />
+  <include package="zope.traversing.browser" />
+  <include package="zope.viewlet" />
+
+  <include package="z3c.form" />
+  <include package="z3c.formui" />
+  <include package="z3c.macro" />
+  <include package="z3c.pagelet" />
+  <include package="z3c.table" />
+  <include package="z3c.contents" />
+
+  <securityPolicy
+      component="zope.securitypolicy.zopepolicy.ZopeSecurityPolicy" />
+
+  <role id="zope.Anonymous" title="Everybody" />
+  <grantAll role="zope.Anonymous" />
+
+  <browser:defaultView
+      for="*"
+      name="index"
+      />
+
+  <interface
+      interface="z3c.contents.testing.IContentsTestBrowserSkin"
+      type="zope.publisher.interfaces.browser.IBrowserSkinType"
+      name="ContentsTesting"
+      />
+
+  <z3c:layout
+      for="*"
+      layer="z3c.contents.testing.IContentsTestBrowserLayer"
+      template="testing.pt"
+      />
+
+  <!-- test contents page for the root folder -->
+  <z3c:pagelet
+      name="index"
+      for="zope.app.folder.interfaces.IRootFolder"
+      class="z3c.contents.browser.ContentsPage"
+      layer="z3c.contents.testing.IContentsTestBrowserLayer"
+      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>

Copied: z3c.contents/trunk/src/z3c/contents/ftests.py (from rev 85254, z3c.contents/branches/darrylcousins/src/z3c/contents/ftests.py)
===================================================================
--- z3c.contents/trunk/src/z3c/contents/ftests.py	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/contents/ftests.py	2008-04-11 14:57:35 UTC (rev 85255)
@@ -0,0 +1,41 @@
+import os
+import doctest
+import lxml
+import transaction
+
+from zope.app.testing import functional
+import zope.component
+
+ftesting_zcml = os.path.join(os.path.dirname(__file__), 
+                                           'ftesting.zcml')
+TestLayer = functional.ZCMLLayer(
+                       ftesting_zcml, __name__, 'TestLayer')
+
+def printElement(browser, xpath, multiple=False, serialize=True):
+    """Print method to use with z3c.etestbrowser"""
+    result = [serialize and lxml.etree.tounicode(elem) or elem
+              for elem in browser.etree.xpath(xpath)]
+    if not multiple:
+        print result[0]
+        return
+    for elem in result:
+        print elem
+
+def setUp(test):
+    functional.FunctionalTestSetup().setUp()
+    test.globs['getRootFolder'] = functional.getRootFolder
+    test.globs['printElement'] = printElement
+
+def tearDown(test):
+    functional.FunctionalTestSetup().tearDown()
+
+def test_suite():
+    suite = functional.FunctionalDocFileSuite('BROWSER.txt',
+        setUp=setUp, tearDown=tearDown,
+        optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+        )
+    suite.layer = TestLayer
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Copied: z3c.contents/trunk/src/z3c/contents/header.py (from rev 85254, z3c.contents/branches/darrylcousins/src/z3c/contents/header.py)
===================================================================
--- z3c.contents/trunk/src/z3c/contents/header.py	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/contents/header.py	2008-04-11 14:57:35 UTC (rev 85255)
@@ -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']
+

Modified: z3c.contents/trunk/src/z3c/contents/interfaces.py
===================================================================
--- z3c.contents/trunk/src/z3c/contents/interfaces.py	2008-04-11 14:23:24 UTC (rev 85254)
+++ z3c.contents/trunk/src/z3c/contents/interfaces.py	2008-04-11 14:57:35 UTC (rev 85255)
@@ -16,11 +16,46 @@
 """
 __docformat__ = "reStructuredText"
 
+import zope.schema
 import zope.i18nmessageid
+
 from z3c.table import interfaces
 
 _ = zope.i18nmessageid.MessageFactory('z3c')
 
 
 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
+    container.
+    
+    Possible addition here could be a choice field to search within specific
+    columns.
+    """
+
+    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.
+        """
+
+class IOrderableColumn(zope.interface.Interface):
+    """
+    A column that may be ordered
+
+    """

Copied: z3c.contents/trunk/src/z3c/contents/search.pt (from rev 85254, z3c.contents/branches/darrylcousins/src/z3c/contents/search.pt)
===================================================================
--- z3c.contents/trunk/src/z3c/contents/search.pt	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/contents/search.pt	2008-04-11 14:57:35 UTC (rev 85255)
@@ -0,0 +1,15 @@
+<table>
+<tr>
+<td class="row" tal:repeat="widget view/widgets/values">
+  <b tal:condition="widget/error"
+     tal:content="structure widget/error/render"
+  /><label for=""
+         tal:attributes="for widget/id"
+         tal:content="widget/label" />
+  <input type="text" tal:replace="structure widget/render"
+/></td>
+<td class="action" tal:repeat="action view/actions/values">
+  <input type="submit" tal:replace="structure action/render"
+/></td>
+</tr>
+</table>

Copied: z3c.contents/trunk/src/z3c/contents/search.py (from rev 85254, z3c.contents/branches/darrylcousins/src/z3c/contents/search.py)
===================================================================
--- z3c.contents/trunk/src/z3c/contents/search.py	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/contents/search.py	2008-04-11 14:57:35 UTC (rev 85255)
@@ -0,0 +1,94 @@
+##############################################################################
+#
+# 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
+import zope.component
+from zope.app.container.interfaces import (IObjectFindFilter,
+                                           IReadContainer)
+from zope.security.proxy import removeSecurityProxy
+from zope.index.text.interfaces import ISearchableText
+
+from z3c.contents.interfaces import ISearch
+
+class SearchForContainer(object):
+
+    zope.interface.implements(ISearch)
+    zope.component.adapts(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 SearchableTextFindFilter(object):
+    """Filter objects on the ISearchableText adapters to the object
+    
+    """
+
+    zope.interface.implements(IObjectFindFilter)
+    
+    def __init__(self, terms):
+        self.terms = terms
+    
+    def matches(self, object):
+        """Check if one of the search terms is found in the searchable text
+        interface
+        """
+
+        adapter = zope.component.queryAdapter(object, ISearchableText)
+        if not adapter:
+            return False
+        searchable = adapter.getSearchableText().lower()
+        for term in [t.lower() for t in self.terms]:
+            if term in searchable:
+                return True
+        return False
+

Copied: z3c.contents/trunk/src/z3c/contents/testing.pt (from rev 85254, z3c.contents/branches/darrylcousins/src/z3c/contents/testing.pt)
===================================================================
--- z3c.contents/trunk/src/z3c/contents/testing.pt	                        (rev 0)
+++ z3c.contents/trunk/src/z3c/contents/testing.pt	2008-04-11 14:57:35 UTC (rev 85255)
@@ -0,0 +1,14 @@
+<!DOCTYPE html PUBLIC "-//W4C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <title>Contents</title>
+</head>
+<body>
+   <tal:block replace="structure provider:pagelet">
+            content
+   </tal:block>
+</body>
+</html>
+

Modified: z3c.contents/trunk/src/z3c/contents/testing.py
===================================================================
--- z3c.contents/trunk/src/z3c/contents/testing.py	2008-04-11 14:23:24 UTC (rev 85254)
+++ z3c.contents/trunk/src/z3c/contents/testing.py	2008-04-11 14:57:35 UTC (rev 85255)
@@ -16,9 +16,31 @@
 """
 __docformat__ = "reStructuredText"
 
+import zope.interface
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.index.text.interfaces import ISearchableText
 
+import z3c.layer.ready2go
+
+class IContentsTestBrowserLayer(z3c.layer.ready2go.IReady2GoBrowserLayer):
+        """test layer."""
+
+
+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
@@ -26,3 +48,15 @@
     def __repr__(self):
         return u'<%s %s %s>' % (self.__class__.__name__, self.title,
             self.number)
+
+
+class SearchableTextForContent:
+
+    zope.interface.implements(ISearchableText)
+    zope.component.adapts(IContent)
+
+    def __init__(self, content):
+        self.content = content
+
+    def getSearchableText(self):
+        return '%s %d' % (self.content.title, self.content.number)

Modified: z3c.contents/trunk/src/z3c/contents/tests.py
===================================================================
--- z3c.contents/trunk/src/z3c/contents/tests.py	2008-04-11 14:23:24 UTC (rev 85254)
+++ z3c.contents/trunk/src/z3c/contents/tests.py	2008-04-11 14:57:35 UTC (rev 85255)
@@ -35,6 +35,7 @@
 
 from z3c.macro import tales
 import z3c.table.testing 
+from z3c.contents import browser
 
 
 class PrincipalAnnotations(dict):
@@ -73,7 +74,10 @@
     # dublin core stub adapter
     zope.component.provideAdapter(z3c.table.testing.DublinCoreAdapterStub)
 
+    # contents search adapter
+    zope.component.provideAdapter(browser.ContentsSearch)
 
+
 def tearDown(test):
     setup.placefulTearDown()
 



More information about the Checkins mailing list