[Checkins] SVN: Produts.RecentItemsIndex/trunk/ Eggified, ported for compatibility with Zope 2.12.
Tres Seaver
tseaver at palladion.com
Tue Mar 16 18:10:38 EDT 2010
Log message for revision 110000:
Eggified, ported for compatibility with Zope 2.12.
Added skeleton for Sphinx documentation.
Changed:
_U Produts.RecentItemsIndex/trunk/
A Produts.RecentItemsIndex/trunk/Products/
A Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/
A Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/__init__.py
A Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/index.py
A Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/tests/
A Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/tests/test_index.py
A Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/version.txt
A Produts.RecentItemsIndex/trunk/Products/__init__.py
U Produts.RecentItemsIndex/trunk/README.txt
D Produts.RecentItemsIndex/trunk/__init__.py
A Produts.RecentItemsIndex/trunk/bootstrap.py
A Produts.RecentItemsIndex/trunk/buildout.cfg
A Produts.RecentItemsIndex/trunk/docs/
A Produts.RecentItemsIndex/trunk/docs/CHANGES.rst
A Produts.RecentItemsIndex/trunk/docs/Makefile
A Produts.RecentItemsIndex/trunk/docs/_build/
A Produts.RecentItemsIndex/trunk/docs/_static/
A Produts.RecentItemsIndex/trunk/docs/_templates/
A Produts.RecentItemsIndex/trunk/docs/conf.py
A Produts.RecentItemsIndex/trunk/docs/index.rst
D Produts.RecentItemsIndex/trunk/index.py
A Produts.RecentItemsIndex/trunk/setup.py
D Produts.RecentItemsIndex/trunk/test.py
D Produts.RecentItemsIndex/trunk/version.txt
-=-
Property changes on: Produts.RecentItemsIndex/trunk
___________________________________________________________________
Added: svn:ignore
+ bin
develop-eggs
eggs
parts
*.egg-info
.installed.cfg
Copied: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/__init__.py (from rev 109997, Produts.RecentItemsIndex/trunk/__init__.py)
===================================================================
--- Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/__init__.py (rev 0)
+++ Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/__init__.py 2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+#
+##############################################################################
+"""RecentItemsIndex Zope product
+
+A ZCatalog plug-in-index for indexing recent items matching a specific field
+value.
+
+$Id: __init__.py,v 1.1.1.1 2004/07/19 17:46:21 caseman Exp $"""
+
+import index
+
+def initialize(context):
+
+ context.registerClass(
+ index.RecentItemsIndex,
+ meta_type='RecentItemsIndex',
+ permission='Add Pluggable Index',
+ constructors=(index.addIndexForm,),
+ icon='www/index.gif',
+ visibility=None
+ )
Property changes on: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/__init__.py
___________________________________________________________________
Added: svn:mergeinfo
+
Copied: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/index.py (from rev 109997, Produts.RecentItemsIndex/trunk/index.py)
===================================================================
--- Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/index.py (rev 0)
+++ Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/index.py 2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,313 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation 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.
+#
+##############################################################################
+"""ZCatalog index for efficient queries of the most recent items
+"""
+import types
+
+from zope.interface import implements
+from AccessControl import Permissions
+from AccessControl.PermissionRole import rolesForPermissionOn
+from AccessControl.SecurityInfo import ClassSecurityInfo
+from Acquisition import aq_inner
+from Acquisition import aq_parent
+from BTrees.OOBTree import OOBTree
+from BTrees.IOBTree import IOBTree
+from BTrees.OIBTree import OIBucket
+from BTrees.Length import Length
+from App.special_dtml import DTMLFile
+from App.class_init import InitializeClass
+from OFS.SimpleItem import SimpleItem
+from Products.PluginIndexes.interfaces import IPluggableIndex
+from Products.ZCatalog.Lazy import LazyMap
+from zLOG import LOG
+from zLOG import WARNING
+
+_marker = []
+
+def _getSourceValue(obj, attrname):
+ """Return the data to be indexed for obj"""
+ value = getattr(obj, attrname)
+ try:
+ # Try calling it
+ value = value()
+ except (TypeError, AttributeError):
+ pass
+ return value
+
+class RecentItemsIndex(SimpleItem):
+ """Recent items index"""
+
+ implements(IPluggableIndex)
+
+ meta_type = 'Recent Items Index'
+
+ # class default; instances get Length() in their clear()
+ numObjects = lambda: 0
+
+ manage_options = (
+ {'label': 'Overview', 'action': 'manage_main'},
+ )
+
+ manage_main = DTMLFile('www/manageIndex', globals())
+
+ security = ClassSecurityInfo()
+ security.declareObjectProtected(Permissions.manage_zcatalog_indexes)
+
+ def __init__(
+ self, id, field_name=None, date_name=None, max_length=None,
+ guard_roles=None, guard_permission=None, extra=None, caller=None):
+ """Recent items index constructor
+
+ id -- Zope id for index in
+
+ field_name -- Name of attribute used to classify the objects. A
+ recent item list is created for each value of this field indexed.
+ If this value is omitted, then a single recent item list for all
+ cataloged objects is created.
+
+ date_name -- Name of attribute containing a date which specifies the
+ object's age.
+
+ max_length -- Maximum length of each recent items list.
+
+ guard_roles -- A list of one or more roles that must be granted the
+ guard permission in order for an object to be indexed. Ignored if
+ no guard_permission value is given.
+
+ guard_permission -- The permission that must be granted to the
+ guard roles for an object in order for it to be indexed. Ignored if
+ no guard_roles value is given.
+
+ extra and caller are used by the wonderous ZCatalog addIndex
+ machinery. You can ignore them, unfortunately I can't 8^/
+ """
+ self.id = id
+ self.field_name = field_name or getattr(extra, 'field_name', None)
+ self.date_name = date_name or extra.date_name
+ self.max_length = max_length or extra.max_length
+ assert self.max_length > 0, 'Max item length value must be 1 or greater'
+ if guard_roles is None:
+ guard_roles = getattr(extra, 'guard_roles', None)
+ if guard_permission is None:
+ guard_permission = getattr(extra, 'guard_permission', None)
+ if guard_permission is not None and guard_roles:
+ self.guard_permission = guard_permission
+ self.guard_roles = tuple(guard_roles)
+ else:
+ self.guard_permission = self.guard_roles = None
+ self.clear()
+
+ ## Index specific methods ##
+
+ def getIndexSourceNames(self):
+ """ See IPluggableIndex.
+ """
+ return (self.field_name,)
+
+ def indexSize(self):
+ """ See IPluggableIndex.
+ """
+ return len(self._value2items)
+
+ def getItemCounts(self):
+ """Return a dict of field value => item count"""
+ counts = {}
+ for value, items in self._value2items.items():
+ counts[value] = len(items)
+ return counts
+
+ def query(self, value=None, limit=None, merge=1):
+ """Return a lazy sequence of catalog brains like a catalog search
+ that coorespond to the most recent items for the value(s) given.
+ If value is omitted, then the most recent for all values are returned.
+ The result are returned in order, newest first. An integer value
+ can be specified in limit which restricts the maximum number of
+ results if desired. If no limit is specified, the indexes'
+ maximum length is used as the limit
+ """
+ catalog = aq_parent(aq_inner(self))
+ if value is None and self.field_name is not None:
+ # Query all values
+ value = list(self._value2items.keys())
+ elif value is not None and self.field_name is None:
+ # Ignore value given if there is no classifier field
+ value = None
+ if isinstance(value, (types.TupleType, types.ListType)):
+ # Query for multiple values
+ results = []
+ for fieldval in value:
+ try:
+ itempairs = self._value2items[fieldval].keys()
+ except KeyError:
+ pass
+ else:
+ results.extend(itempairs)
+ results.sort()
+ if merge:
+ results = [rid for date, rid in results]
+ else:
+ # Create triples expected by mergeResults()
+ results = [(date, rid, catalog.__getitem__)
+ for date, rid in results]
+ else:
+ # Query for single value
+ try:
+ items = self._value2items[value]
+ except KeyError:
+ results = []
+ else:
+ if merge:
+ results = items.values()
+ else:
+ # Create triples expected by mergeResults()
+ results = [(date, rid, catalog.__getitem__)
+ for date, rid in items.keys()]
+ results.reverse()
+ if limit is not None:
+ results = results[:limit]
+ if merge:
+ return LazyMap(catalog.__getitem__, results, len(results))
+ else:
+ return results
+
+ ## Pluggable Index API ##
+
+ def index_object(self, docid, obj, theshold=None):
+ """Add document to index"""
+ if self.guard_permission is not None and self.guard_roles:
+ allowed_roles = rolesForPermissionOn(self.guard_permission, obj)
+ for role in allowed_roles:
+ if role in self.guard_roles:
+ break
+ else:
+ # Object does not have proper permission grant
+ # to be in the index
+ self.unindex_object(docid)
+ return 0
+ try:
+ if self.field_name is not None:
+ fieldvalue = _getSourceValue(obj, self.field_name)
+ else:
+ fieldvalue = None
+ datevalue = _getSourceValue(obj, self.date_name)
+ except AttributeError:
+ # One or the other source attributes is missing
+ # unindex the object and bail
+ self.unindex_object(docid)
+ return 0
+ datevalue = datevalue.timeTime()
+ entry = self.getEntryForObject(docid)
+ if (entry is None or fieldvalue != entry['value']
+ or datevalue != entry['date']):
+ # XXX Note that setting the date older than a previously pruned
+ # object will result in an incorrect index state. This may
+ # present a problem if dates are changed arbitrarily
+ if entry is None:
+ self.numObjects.change(1)
+ else:
+ # unindex existing entry
+ self.unindex_object(docid)
+ self._rid2value[docid] = fieldvalue
+ try:
+ items = self._value2items[fieldvalue]
+ except KeyError:
+ # Unseen value, create a new items bucket
+ items = self._value2items[fieldvalue] = OIBucket()
+ items[datevalue, docid] = docid
+ while len(items) > self.max_length:
+ # Prune the oldest items
+ olddate, oldrid = items.minKey()
+ # Unindex by hand to avoid theoretical infinite loops
+ self.numObjects.change(-1)
+ del items[olddate, oldrid]
+ if not items:
+ # Not likely, unless max_length is 1
+ del self._value2items[fieldvalue]
+ try:
+ del self._rid2value[oldrid]
+ except KeyError:
+ LOG('RecentItemsIndex', WARNING,
+ 'Could not unindex field value for %s.' % oldrid)
+ return 1
+ else:
+ # Index is up to date, nothing to do
+ return 0
+
+ def unindex_object(self, docid):
+ """Remove docid from the index. If docid is not in the index,
+ do nothing"""
+ try:
+ fieldvalue = self._rid2value[docid]
+ except KeyError:
+ return 0 # docid not in index
+ self.numObjects.change(-1)
+ del self._rid2value[docid]
+ items = self._value2items[fieldvalue]
+ for date, rid in items.keys():
+ if rid == docid:
+ del items[date, rid]
+ if not items:
+ del self._value2items[fieldvalue]
+ return 1
+ return 1
+
+ def _apply_index(self, request, cid=''):
+ """We do not play in normal catalog queries"""
+ return None
+
+ def getEntryForObject(self, docid, default=None):
+ """Return a dict containing the field value and date for
+ docid, or default if it is not in the index. The returned dict
+ has the keys 'value' and 'date'
+ """
+ try:
+ fieldvalue = self._rid2value[docid]
+ except KeyError:
+ return default
+ for date, rid in self._value2items[fieldvalue].keys():
+ if rid == docid:
+ return {'value':fieldvalue, 'date':date}
+ # If we get here then _rid2values is inconsistent with _value2items
+ LOG('RecentItemsIndex', WARNING,
+ 'Field value found for item %s, but no date. '
+ 'This should not happen.' % docid)
+ return default
+
+ def hasUniqueValuesFor(self, name):
+ """Return true if the index holds the unique values for name"""
+ return name == self.field_name
+
+ def uniqueValues(self, name=None):
+ """Return the unique field values indexed"""
+ if name is None:
+ name = self.field_name
+ if name == self.field_name:
+ return self._value2items.keys()
+ else:
+ return []
+
+ ## ZCatalog ZMI methods ##
+
+ def clear(self):
+ """reinitialize the index"""
+ self.numObjects = Length()
+ # Mapping field value => top items
+ self._value2items = OOBTree()
+ # Mapping indexed rid => field value for unindexing
+ self._rid2value = IOBTree()
+
+
+InitializeClass(RecentItemsIndex)
+
+addIndexForm = DTMLFile('www/addIndex', globals())
Property changes on: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/index.py
___________________________________________________________________
Added: svn:mergeinfo
+
Copied: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/tests/test_index.py (from rev 109997, Produts.RecentItemsIndex/trunk/test.py)
===================================================================
--- Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/tests/test_index.py (rev 0)
+++ Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/tests/test_index.py 2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,504 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation 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.
+#
+##############################################################################
+""" Tests for Products.RecentItemsIndex.index
+"""
+import unittest
+
+class RecentItemsIndexTest(unittest.TestCase):
+
+ def _getTargetClass(self):
+ from Products.RecentItemsIndex.index import RecentItemsIndex
+ return RecentItemsIndex
+
+ def _makeOne(self,
+ id='test',
+ field_name='type',
+ date_name='date',
+ max_length=10,
+ *args, **kw):
+ catalog = self._makeCatalog()
+ index = self._getTargetClass()(id, field_name, date_name, max_length,
+ *args, **kw)
+ return index.__of__(catalog)
+
+ def _makeDoc(self, **kw):
+
+ class Doc:
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
+ return Doc(**kw)
+
+ def _makeCatalog(self):
+ from OFS.SimpleItem import SimpleItem
+
+ class DummyCatalog(SimpleItem):
+
+ def __init__(self):
+ self.docs = {}
+
+ def __getitem__(self, item):
+ return self.docs[item]
+
+ return DummyCatalog()
+
+ def _makeViewable(self, id='test_viewable'):
+ from OFS.SimpleItem import SimpleItem
+ from DateTime.DateTime import DateTime
+
+ class Viewable(SimpleItem):
+
+ date = DateTime('2/21/2004')
+ type = 'Viewable'
+
+ def __init__(self, role=None):
+ self._addRole(role)
+ if role is not None:
+ self.manage_permission('View', [role])
+
+ return Viewable(id)
+
+ def _makeAndIndexOneDoc(self, index):
+ from DateTime.DateTime import DateTime
+ doc = self._makeDoc(type='fluke', date=DateTime('1/1/2004'))
+ return index.index_object(1, doc)
+
+ def _makeAndIndexDocs(self, index):
+ from Acquisition import aq_parent
+ from DateTime.DateTime import DateTime
+ types = ['huey', 'dooey', 'looey', 'dooey'] * 15
+ date = DateTime('1/1/2004')
+ docs = {}
+ for docid, typ in zip(range(len(types)), types):
+ if not docid % 3:
+ date = date + 1
+ if not docid % 7:
+ date = date - (docid % 3)
+ doc = docs[docid] = self._makeDoc(docid=docid, type=typ, date=date)
+ index.index_object(docid, doc)
+ aq_parent(index).docs = docs
+ return docs
+
+ def test_class_conforms_to_IPluggableIndex(self):
+ from zope.interface.verify import verifyClass
+ from Products.PluginIndexes.interfaces import IPluggableIndex
+ verifyClass(IPluggableIndex, self._getTargetClass())
+
+ def test_instance_conforms_to_IPluggableIndex(self):
+ from zope.interface.verify import verifyObject
+ from Products.PluginIndexes.interfaces import IPluggableIndex
+ index = self._makeOne()
+ verifyObject(IPluggableIndex, index)
+
+ def test_construct_with_extra(self):
+ # Simulate instantiating from ZCatalog
+ class extra:
+ field_name = 'bruford'
+ date_name = 'wakeman'
+ max_length = 25
+ guard_roles = ['Anonymous']
+ guard_permission = 'View'
+ index = self._getTargetClass()('extra', extra=extra)
+ self.assertEqual(index.getId(), 'extra')
+ self.assertEqual(index.field_name, 'bruford')
+ self.assertEqual(index.date_name, 'wakeman')
+ self.assertEqual(index.max_length, 25)
+ self.assertEqual(tuple(index.guard_roles), ('Anonymous',))
+ self.assertEqual(index.guard_permission, 'View')
+
+ def test_construct_with_no_classifier_or_guard(self):
+ # Simulate instantiating from ZCatalog
+ class extra:
+ date_name = 'modified'
+ max_length = 30
+ index = self._getTargetClass()('nuttin', extra=extra)
+ self.assertEqual(index.getId(), 'nuttin')
+ self.assertEqual(index.date_name, 'modified')
+ self.assertEqual(index.max_length, 30)
+
+ def test_construct_with_bogus_max_length(self):
+ self.assertRaises(
+ Exception, self._getTargetClass(), 'test', 'type', 'date', 0)
+ self.assertRaises(
+ Exception, self._getTargetClass(), 'test', 'type', 'date', -20)
+
+ def test_index_object_skips_obj_without_field_or_date(self):
+ index = self._makeOne()
+ doc = self._makeDoc()
+ self.failIf(index.index_object(1, doc))
+ self.assertEqual(index.numObjects(), 0)
+ self.assertEqual(index.getItemCounts(), {})
+
+ def test_index_object_skips_obj_without_date(self):
+ index = self._makeOne()
+ doc = self._makeDoc(type='cheetos')
+ self.failIf(index.index_object(1, doc))
+ self.assertEqual(index.numObjects(), 0)
+ self.assertEqual(index.getItemCounts(), {})
+
+ def test_index_object_skips_obj_without_field(self):
+ from DateTime.DateTime import DateTime
+ index = self._makeOne()
+ doc = self._makeDoc(date=DateTime('4/17/2004'))
+ self.failIf(index.index_object(1, doc))
+ self.assertEqual(index.numObjects(), 0)
+ self.assertEqual(index.getItemCounts(), {})
+
+ def test_index_single(self):
+ index = self._makeOne()
+ result = self._makeAndIndexOneDoc(index)
+ self.failUnless(result)
+ self.assertEqual(index.numObjects(), 1)
+ self.assertEqual(index.getItemCounts(), {'fluke': 1})
+
+ def test_unindex_single(self):
+ index = self._makeOne()
+ result = self._makeAndIndexOneDoc(index)
+ self.failUnless(index.unindex_object(1))
+ self.assertEqual(index.numObjects(), 0)
+ self.assertEqual(index.getItemCounts(), {})
+
+ def test_index_many(self):
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ maxlen = index.max_length
+ self.assertEqual(index.getItemCounts(),
+ {'huey': maxlen, 'dooey':maxlen, 'looey':maxlen})
+ self.assertEqual(index.numObjects(), maxlen*3)
+
+ def test_index_many_no_classifier(self):
+ index = self._makeOne('test', None, 'date', 10)
+ docs = self._makeAndIndexDocs(index)
+ maxlen = index.max_length
+ self.assertEqual(index.getItemCounts(), {None: maxlen,})
+ self.assertEqual(index.numObjects(), maxlen)
+
+ def test_unindex_one_type(self):
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ for docid, doc in docs.items():
+ if doc.type == 'looey':
+ index.unindex_object(docid)
+ self.assertEqual(index.numObjects(), 20)
+ self.assertEqual(index.getItemCounts(), {'huey': 10, 'dooey':10})
+
+ def test_unindex_all(self):
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ for docid in docs.keys():
+ index.unindex_object(docid)
+ self.assertEqual(index.numObjects(), 0)
+ self.assertEqual(index.getItemCounts(), {})
+ self.assertEqual(list(index.uniqueValues()), [])
+
+ def _get_top_docs(self, docs):
+ top = {'huey':[], 'dooey':[], 'looey':[]}
+ for doc in docs.values():
+ top[doc.type].append((doc.date.timeTime(), doc.docid))
+ for typ, docs in top.items():
+ docs.sort()
+ top[typ] = docs[-10:]
+ return top
+
+ def test_getEntryForObject(self):
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ top = self._get_top_docs(docs)
+ for docid, doc in docs.items():
+ entry = index.getEntryForObject(docid)
+ if entry is not None:
+ self.assertEqual(entry,
+ {'value': doc.type, 'date': doc.date.timeTime()})
+ else:
+ self.failIf((doc.date.timeTime(), doc.docid) in top[doc.type])
+
+ def test_unindex_most_recent(self):
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ top = self._get_top_docs(docs)
+ item_counts = index.getItemCounts()
+ total_count = 30
+ for i in range(10):
+ for typ in ('huey', 'dooey', 'looey'):
+ nil, byebyeid = top[typ].pop()
+ self.failUnless(index.unindex_object(byebyeid))
+ item_counts[typ] -= 1
+ if not item_counts[typ]:
+ del item_counts[typ]
+ total_count -= 1
+ self.assertEqual(index.getItemCounts(), item_counts)
+ self.assertEqual(index.numObjects(), total_count)
+ self.assertEqual(index.numObjects(), 0)
+ self.assertEqual(index.getItemCounts(), {})
+
+ def test_unindex_bogus_rid(self):
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ self.failIf(index.unindex_object(-2000))
+
+ def _get_indexed_doc(self, index, fromtop=0):
+ docs = self._makeAndIndexDocs(index)
+ top = self._get_top_docs(docs)
+ items = docs.items()
+ if fromtop:
+ items.reverse()
+ for docid, doc in items:
+ entry = index.getEntryForObject(docid)
+ if entry is not None:
+ break
+ else:
+ self.fail('No objects in index')
+ self.assertEqual(entry, {'value':doc.type, 'date':doc.date.timeTime()})
+ return doc
+
+ def test_reindex_no_change(self):
+ # reindex with no change should be a no-op
+ index = self._makeOne()
+ doc = self._get_indexed_doc(index)
+ self.failIf(index.index_object(doc.docid, doc))
+ self.assertEqual(index.getEntryForObject(doc.docid),
+ {'value':doc.type, 'date':doc.date.timeTime()})
+
+ def test_reindex_change_date(self):
+ index = self._makeOne()
+ doc = self._get_indexed_doc(index)
+ doc.date = doc.date + 10
+ self.failUnless(index.index_object(doc.docid, doc))
+ self.assertEqual(index.getEntryForObject(doc.docid),
+ {'value':doc.type, 'date':doc.date.timeTime()})
+
+ def test_reindex_change_value(self):
+ index = self._makeOne()
+ doc = self._get_indexed_doc(index, fromtop=1)
+ oldtype = doc.type
+ for typ in index.uniqueValues():
+ if typ != oldtype:
+ doc.type = typ
+ break
+ self.failUnless(index.index_object(doc.docid, doc))
+ self.assertEqual(index.getEntryForObject(doc.docid),
+ {'value':doc.type, 'date':doc.date.timeTime()})
+
+ def test_reindex_change_date_and_value(self):
+ index = self._makeOne()
+ doc = self._get_indexed_doc(index, fromtop=1)
+ doc.date = doc.date + 4
+ oldtype = doc.type
+ for typ in index.uniqueValues():
+ if typ != oldtype:
+ doc.type = typ
+ break
+ self.failUnless(index.index_object(doc.docid, doc))
+ self.assertEqual(index.getEntryForObject(doc.docid),
+ {'value':doc.type, 'date':doc.date.timeTime()})
+
+ def test_query_empty_index(self):
+ index = self._makeOne()
+ result = index.query('foobar')
+ self.failIf(result)
+
+ def test_simple_query(self):
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ top = self._get_top_docs(docs)
+ result = index.query('huey')
+ expected = [docid for nil, docid in top['huey']]
+ expected.reverse()
+ self.assertEqual([doc.docid for doc in result], expected)
+
+ def test_query_bogus_value(self):
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ self.failIf(index.query('snacks'))
+
+ def test_query_limit(self):
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ top = self._get_top_docs(docs)
+ result = index.query('huey', limit=3)
+ expected = [docid for nil, docid in top['huey']]
+ expected.reverse()
+ expected = expected[:3]
+ self.assertEqual([doc.docid for doc in result], expected)
+
+ def test_query_no_merge(self):
+ from Acquisition import aq_parent
+ index = self._makeOne()
+ catalog = aq_parent(index)
+ docs = self._makeAndIndexDocs(index)
+ top = self._get_top_docs(docs)
+ result = index.query('dooey', merge=0)
+ expected = [(date, docid, catalog.__getitem__)
+ for date, docid in top['dooey']]
+ expected.reverse()
+ for rrow, erow in zip(result, expected):
+ self.assertEqual(rrow[:2], erow[:2])
+
+ def _getExpectedTopDocs(self, index, docs, limit=None):
+ top = self._get_top_docs(docs)
+ query = ['huey', 'dooey']
+ if limit is None:
+ result = index.query(query)
+ else:
+ result = index.query(query, limit=limit)
+ expected = top['huey'] + top['dooey']
+ expected.sort()
+ expected = [docid for nil, docid in expected]
+ expected.reverse()
+ return result, expected
+
+ def test_query_multiple_values(self):
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ result, expected = self._getExpectedTopDocs(index, docs)
+ self.assertEqual([doc.docid for doc in result], expected)
+
+ def test_query_all_values(self):
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ top = self._get_top_docs(docs)
+ result = index.query()
+ expected = top['huey'] + top['dooey'] + top['looey']
+ expected.sort()
+ expected = [docid for nil, docid in expected]
+ expected.reverse()
+ self.assertEqual([doc.docid for doc in result], expected)
+ return expected
+
+ def test_query_no_classifier(self):
+ index = self._makeOne('test', None, 'date', 10)
+ docs = self._makeAndIndexDocs(index)
+ top = self._get_top_docs(docs)
+ result = index.query()
+ expected = top['huey'] + top['dooey'] + top['looey']
+ expected.sort()
+ expected = [docid for nil, docid in expected]
+ expected.reverse()
+ self.assertEqual([doc.docid for doc in result], expected[:10])
+
+ def test_query_no_classifier_ignores_value(self):
+ index = self._makeOne('test', None, 'date', 10)
+ docs = self._makeAndIndexDocs(index)
+ top = self._get_top_docs(docs)
+ result = index.query('ptooey')
+ expected = top['huey'] + top['dooey'] + top['looey']
+ expected.sort()
+ expected = [docid for nil, docid in expected]
+ expected.reverse()
+ self.assertEqual([doc.docid for doc in result], expected[:10])
+
+ def test_query_multiple_with_tuple(self):
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ result, expected = self._getExpectedTopDocs(index, docs)
+ self.assertEqual([doc.docid for doc in result], expected)
+
+ def test_query_multiple_bogus_values(self):
+ index = self._makeOne()
+ self.failIf(index.query(['fooey', 'blooey']))
+ result = index.query(['blooey', 'looey'])
+ expected = index.query('looey')
+ self.assertEqual(list(result), list(expected))
+
+ def test_query_multiple_limit(self):
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ result, expected = self._getExpectedTopDocs(index, docs, limit=4)
+ expected = expected[:4]
+ self.assertEqual([doc.docid for doc in result], expected)
+
+ def test_query_multiple_no_merge(self):
+ from Acquisition import aq_parent
+ index = self._makeOne()
+ catalog = aq_parent(index)
+ docs = self._makeAndIndexDocs(index)
+ top = self._get_top_docs(docs)
+ result = index.query(['dooey', 'huey'], merge=0)
+ expected = [(date, docid, catalog.__getitem__)
+ for date, docid in top['huey'] + top['dooey']]
+ expected.sort()
+ expected.reverse()
+ for rrow, erow in zip(result, expected):
+ self.assertEqual(rrow[:2], erow[:2])
+
+ def test_apply_index(self):
+ # _apply_index always returns none since recent items index
+ # do not participate in the normal ZCatalog query as they
+ # handle both intersection and sorting
+ index = self._makeOne()
+ self.failUnless(index._apply_index({}) is None)
+ self.failUnless(index._apply_index({'query':'looey'}) is None)
+
+ def test_uniqueValues(self):
+ index = self._makeOne()
+ self.failIf(index.uniqueValues('type'))
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ values = list(index.uniqueValues('type'))
+ values.sort()
+ self.assertEqual(values, ['dooey', 'huey', 'looey'])
+ self.failIf(index.uniqueValues('carbtastic'))
+
+ def test_hasUniqueValuesFor(self):
+ index = self._makeOne()
+ self.failUnless(index.hasUniqueValuesFor('type'))
+ self.failIf(index.hasUniqueValuesFor('spork'))
+
+ def test_numObjects(self):
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ self.assertEqual(index.numObjects(), 30)
+
+ def test_numObjects_small_maxlen(self):
+ index = self._makeOne()
+ index.max_length = 1
+ docs = self._makeAndIndexDocs(index)
+ self.assertEqual(index.numObjects(), 3)
+
+ def test_numObjects_empty_index(self):
+ index = self._makeOne()
+ self.assertEqual(index.numObjects(), 0)
+
+ def test_clear(self):
+ index = self._makeOne()
+ docs = self._makeAndIndexDocs(index)
+ self.failUnless(index.numObjects())
+ index.clear()
+ self.assertEqual(index.numObjects(), 0)
+
+ def test_role_permission_guard(self):
+ index = self._makeOne(
+ 'test', 'type', 'date', 5, ['NerfHerder', 'Bloke'], 'View')
+ viewable = self._makeViewable('NerfHerder')
+ index.index_object(0, viewable)
+ self.assertEqual(index.numObjects(), 1)
+ notviewable = self._makeViewable()
+ index.index_object(1, notviewable)
+ self.assertEqual(index.numObjects(), 1)
+ bloke = self._makeViewable('Bloke')
+ index.index_object(2, bloke)
+ self.assertEqual(index.numObjects(), 2)
+ bloke.manage_permission('View', [])
+ index.index_object(2, bloke)
+ self.assertEqual(index.numObjects(), 1)
+ dummy = self._makeViewable('Dummy')
+ index.index_object(3, dummy)
+ self.assertEqual(index.numObjects(), 1)
+ viewable.manage_permission('View', [])
+ index.index_object(0, viewable)
+ self.assertEqual(index.numObjects(), 0)
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(RecentItemsIndexTest),
+ ))
Property changes on: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/tests/test_index.py
___________________________________________________________________
Added: svn:mergeinfo
+
Copied: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/version.txt (from rev 109997, Produts.RecentItemsIndex/trunk/version.txt)
===================================================================
--- Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/version.txt (rev 0)
+++ Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/version.txt 2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1 @@
+0.1
Property changes on: Produts.RecentItemsIndex/trunk/Products/RecentItemsIndex/version.txt
___________________________________________________________________
Added: svn:mergeinfo
+
Added: Produts.RecentItemsIndex/trunk/Products/__init__.py
===================================================================
--- Produts.RecentItemsIndex/trunk/Products/__init__.py (rev 0)
+++ Produts.RecentItemsIndex/trunk/Products/__init__.py 2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,6 @@
+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+try:
+ __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+ from pkgutil import extend_path
+ __path__ = extend_path(__path__, __name__)
Modified: Produts.RecentItemsIndex/trunk/README.txt
===================================================================
--- Produts.RecentItemsIndex/trunk/README.txt 2010-03-16 21:50:57 UTC (rev 109999)
+++ Produts.RecentItemsIndex/trunk/README.txt 2010-03-16 22:10:38 UTC (rev 110000)
@@ -1,21 +1,20 @@
-Recent Items Index
+Products.RecentItemsIndex README
+================================
- This index is designed to optimize queries which ask for the most
- recent objects that match a certain value for an attribute. The designed
- usage is a query for the most recent objects of a particular portal
- type.
-
- The index only retains up to a fixed number of items for each field
- value which means that the performance of queries using the index
- are independant of the size of the catalog.
-
- The index also has a custom query interface so that applications
- may query it directly for greatest efficiency since it handles both
- the result selection and sorting simultaneously.
-
- At the moment the index is not searchable through the standard ZCatalog
- 'searchResults()' API. This is because the catalog does not yet support
- indexes that can do searching and sorting simultaneously as this one
- does.
+This product provides a ZCatalog index designed to optimize queries which
+ask for the most recent objects that match a certain value for an attribute.
+The designed usage is a query for the most recent objects of a particular
+portal type.
-
+The index only retains up to a fixed number of items for each field
+value which means that the performance of queries using the index
+are independant of the size of the catalog.
+
+The index also has a custom query interface so that applications
+may query it directly for greatest efficiency since it handles both
+the result selection and sorting simultaneously.
+
+At the moment the index is not searchable through the standard ZCatalog
+``searchResults()`` API. This is because the catalog does not yet support
+indexes that can do searching and sorting simultaneously as this one
+does.
Deleted: Produts.RecentItemsIndex/trunk/__init__.py
===================================================================
--- Produts.RecentItemsIndex/trunk/__init__.py 2010-03-16 21:50:57 UTC (rev 109999)
+++ Produts.RecentItemsIndex/trunk/__init__.py 2010-03-16 22:10:38 UTC (rev 110000)
@@ -1,32 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2002 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.0 (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.
-#
-##############################################################################
-"""RecentItemsIndex Zope product
-
-A ZCatalog plug-in-index for indexing recent items matching a specific field
-value.
-
-$Id: __init__.py,v 1.1.1.1 2004/07/19 17:46:21 caseman Exp $"""
-
-import index
-
-def initialize(context):
-
- context.registerClass(
- index.RecentItemsIndex,
- meta_type='RecentItemsIndex',
- permission='Add Pluggable Index',
- constructors=(index.addIndexForm,),
- icon='www/index.gif',
- visibility=None
- )
Added: Produts.RecentItemsIndex/trunk/bootstrap.py
===================================================================
--- Produts.RecentItemsIndex/trunk/bootstrap.py (rev 0)
+++ Produts.RecentItemsIndex/trunk/bootstrap.py 2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,121 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation 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.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id$
+"""
+
+import os, shutil, sys, tempfile, urllib2
+from optparse import OptionParser
+
+tmpeggs = tempfile.mkdtemp()
+
+is_jython = sys.platform.startswith('java')
+
+# parsing arguments
+parser = OptionParser()
+parser.add_option("-v", "--version", dest="version",
+ help="use a specific zc.buildout version")
+parser.add_option("-d", "--distribute",
+ action="store_true", dest="distribute", default=False,
+ help="Use Disribute rather than Setuptools.")
+
+parser.add_option("-c", None, action="store", dest="config_file",
+ help=("Specify the path to the buildout configuration "
+ "file to be used."))
+
+options, args = parser.parse_args()
+
+# if -c was provided, we push it back into args for buildout' main function
+if options.config_file is not None:
+ args += ['-c', options.config_file]
+
+if options.version is not None:
+ VERSION = '==%s' % options.version
+else:
+ VERSION = ''
+
+USE_DISTRIBUTE = options.distribute
+args = args + ['bootstrap']
+
+to_reload = False
+try:
+ import pkg_resources
+ if not hasattr(pkg_resources, '_distribute'):
+ to_reload = True
+ raise ImportError
+except ImportError:
+ ez = {}
+ if USE_DISTRIBUTE:
+ exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py'
+ ).read() in ez
+ ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True)
+ else:
+ exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+ ).read() in ez
+ ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+ if to_reload:
+ reload(pkg_resources)
+ else:
+ import pkg_resources
+
+if sys.platform == 'win32':
+ def quote(c):
+ if ' ' in c:
+ return '"%s"' % c # work around spawn lamosity on windows
+ else:
+ return c
+else:
+ def quote (c):
+ return c
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+ws = pkg_resources.working_set
+
+if USE_DISTRIBUTE:
+ requirement = 'distribute'
+else:
+ requirement = 'setuptools'
+
+if is_jython:
+ import subprocess
+
+ assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd',
+ quote(tmpeggs), 'zc.buildout' + VERSION],
+ env=dict(os.environ,
+ PYTHONPATH=
+ ws.find(pkg_resources.Requirement.parse(requirement)).location
+ ),
+ ).wait() == 0
+
+else:
+ assert os.spawnle(
+ os.P_WAIT, sys.executable, quote (sys.executable),
+ '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION,
+ dict(os.environ,
+ PYTHONPATH=
+ ws.find(pkg_resources.Requirement.parse(requirement)).location
+ ),
+ ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout' + VERSION)
+import zc.buildout.buildout
+zc.buildout.buildout.main(args)
+shutil.rmtree(tmpeggs)
Added: Produts.RecentItemsIndex/trunk/buildout.cfg
===================================================================
--- Produts.RecentItemsIndex/trunk/buildout.cfg (rev 0)
+++ Produts.RecentItemsIndex/trunk/buildout.cfg 2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,27 @@
+[buildout]
+develop = .
+parts =
+ test
+ zopepy
+ docs
+
+unzip = true
+
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = Products.RecentItemsIndex
+
+
+[zopepy]
+recipe = zc.recipe.egg
+eggs =
+ Zope2
+ Products.RecentItemsIndex
+interpreter = zopepy
+scripts = zopepy
+
+
+[docs]
+recipe = zc.recipe.egg
+eggs = Sphinx
Added: Produts.RecentItemsIndex/trunk/docs/CHANGES.rst
===================================================================
--- Produts.RecentItemsIndex/trunk/docs/CHANGES.rst (rev 0)
+++ Produts.RecentItemsIndex/trunk/docs/CHANGES.rst 2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,14 @@
+Products.RecentItemsIndex Changelog
+===================================
+
+0.1 (unreleased)
+----------------
+
+- Added minimal Sphinx documentation.
+
+- Eggified, ported for compatibility with Zope 2.12.
+
+- Initial public release.
+
+- Migrated to svn.zope.org from Casey Duncan's original version on
+ cvs.zope.org.
Added: Produts.RecentItemsIndex/trunk/docs/Makefile
===================================================================
--- Produts.RecentItemsIndex/trunk/docs/Makefile (rev 0)
+++ Produts.RecentItemsIndex/trunk/docs/Makefile 2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,89 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ProjectsRecentItemsIndex.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ProjectsRecentItemsIndex.qhc"
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+ "run these through (pdf)latex."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
Property changes on: Produts.RecentItemsIndex/trunk/docs/_build
___________________________________________________________________
Added: svn:ignore
+ *
Added: Produts.RecentItemsIndex/trunk/docs/conf.py
===================================================================
--- Produts.RecentItemsIndex/trunk/docs/conf.py (rev 0)
+++ Produts.RecentItemsIndex/trunk/docs/conf.py 2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,194 @@
+# -*- coding: utf-8 -*-
+#
+# Projects.RecentItemsIndex documentation build configuration file, created by
+# sphinx-quickstart on Tue Mar 16 18:05:54 2010.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Projects.RecentItemsIndex'
+copyright = u'2010, Zope Foundation and contributors'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.1'
+# The full version, including alpha/beta/rc tags.
+release = '0.1'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further. For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents. If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar. Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it. The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'ProjectsRecentItemsIndexdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+ ('index', 'ProjectsRecentItemsIndex.tex', u'Projects.RecentItemsIndex Documentation',
+ u'Zope Foundation and contributors', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
Added: Produts.RecentItemsIndex/trunk/docs/index.rst
===================================================================
--- Produts.RecentItemsIndex/trunk/docs/index.rst (rev 0)
+++ Produts.RecentItemsIndex/trunk/docs/index.rst 2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,20 @@
+.. Projects.RecentItemsIndex documentation master file, created by
+ sphinx-quickstart on Tue Mar 16 18:05:54 2010.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+Welcome to Projects.RecentItemsIndex's documentation!
+=====================================================
+
+Contents:
+
+.. toctree::
+ :maxdepth: 2
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
Deleted: Produts.RecentItemsIndex/trunk/index.py
===================================================================
--- Produts.RecentItemsIndex/trunk/index.py 2010-03-16 21:50:57 UTC (rev 109999)
+++ Produts.RecentItemsIndex/trunk/index.py 2010-03-16 22:10:38 UTC (rev 110000)
@@ -1,299 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 Zope Corporation 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.
-#
-##############################################################################
-"""ZCatalog index for efficient queries of the most recent items
-
-$Id: index.py,v 1.5 2004/08/10 16:01:26 caseman Exp $"""
-
-import types
-from Globals import DTMLFile, InitializeClass
-from Acquisition import aq_inner, aq_parent
-from Persistence import Persistent
-from OFS.SimpleItem import SimpleItem
-from Products.PluginIndexes.common.PluggableIndex \
- import PluggableIndexInterface
-from Products.ZCatalog.Lazy import LazyMap
-from AccessControl import Permissions
-from AccessControl.PermissionRole import rolesForPermissionOn
-from AccessControl.SecurityInfo import ClassSecurityInfo
-from BTrees.OOBTree import OOBTree
-from BTrees.IOBTree import IOBTree
-from BTrees.OIBTree import OIBucket
-from BTrees.Length import Length
-from zLOG import LOG, WARNING
-
-_marker = []
-
-def _getSourceValue(obj, attrname):
- """Return the data to be indexed for obj"""
- value = getattr(obj, attrname)
- try:
- # Try calling it
- value = value()
- except (TypeError, AttributeError):
- pass
- return value
-
-class RecentItemsIndex(SimpleItem):
- """Recent items index"""
-
- __implements__ = PluggableIndexInterface
-
- meta_type = 'Recent Items Index'
-
- manage_options = (
- {'label': 'Overview', 'action': 'manage_main'},
- )
-
- manage_main = DTMLFile('www/manageIndex', globals())
-
- security = ClassSecurityInfo()
- security.declareObjectProtected(Permissions.manage_zcatalog_indexes)
-
- def __init__(
- self, id, field_name=None, date_name=None, max_length=None,
- guard_roles=None, guard_permission=None, extra=None, caller=None):
- """Recent items index constructor
-
- id -- Zope id for index in
-
- field_name -- Name of attribute used to classify the objects. A
- recent item list is created for each value of this field indexed.
- If this value is omitted, then a single recent item list for all
- cataloged objects is created.
-
- date_name -- Name of attribute containing a date which specifies the
- object's age.
-
- max_length -- Maximum length of each recent items list.
-
- guard_roles -- A list of one or more roles that must be granted the
- guard permission in order for an object to be indexed. Ignored if
- no guard_permission value is given.
-
- guard_permission -- The permission that must be granted to the
- guard roles for an object in order for it to be indexed. Ignored if
- no guard_roles value is given.
-
- extra and caller are used by the wonderous ZCatalog addIndex
- machinery. You can ignore them, unfortunately I can't 8^/
- """
- self.id = id
- self.field_name = field_name or getattr(extra, 'field_name', None)
- self.date_name = date_name or extra.date_name
- self.max_length = max_length or extra.max_length
- assert self.max_length > 0, 'Max item length value must be 1 or greater'
- if guard_roles is None:
- guard_roles = getattr(extra, 'guard_roles', None)
- if guard_permission is None:
- guard_permission = getattr(extra, 'guard_permission', None)
- if guard_permission is not None and guard_roles:
- self.guard_permission = guard_permission
- self.guard_roles = tuple(guard_roles)
- else:
- self.guard_permission = self.guard_roles = None
- self.clear()
-
- ## Index specific methods ##
-
- def getItemCounts(self):
- """Return a dict of field value => item count"""
- counts = {}
- for value, items in self._value2items.items():
- counts[value] = len(items)
- return counts
-
- def query(self, value=None, limit=None, merge=1):
- """Return a lazy sequence of catalog brains like a catalog search
- that coorespond to the most recent items for the value(s) given.
- If value is omitted, then the most recent for all values are returned.
- The result are returned in order, newest first. An integer value
- can be specified in limit which restricts the maximum number of
- results if desired. If no limit is specified, the indexes'
- maximum length is used as the limit
- """
- catalog = aq_parent(aq_inner(self))
- if value is None and self.field_name is not None:
- # Query all values
- value = list(self._value2items.keys())
- elif value is not None and self.field_name is None:
- # Ignore value given if there is no classifier field
- value = None
- if isinstance(value, (types.TupleType, types.ListType)):
- # Query for multiple values
- results = []
- for fieldval in value:
- try:
- itempairs = self._value2items[fieldval].keys()
- except KeyError:
- pass
- else:
- results.extend(itempairs)
- results.sort()
- if merge:
- results = [rid for date, rid in results]
- else:
- # Create triples expected by mergeResults()
- results = [(date, rid, catalog.__getitem__)
- for date, rid in results]
- else:
- # Query for single value
- try:
- items = self._value2items[value]
- except KeyError:
- results = []
- else:
- if merge:
- results = items.values()
- else:
- # Create triples expected by mergeResults()
- results = [(date, rid, catalog.__getitem__)
- for date, rid in items.keys()]
- results.reverse()
- if limit is not None:
- results = results[:limit]
- if merge:
- return LazyMap(catalog.__getitem__, results, len(results))
- else:
- return results
-
- ## Pluggable Index API ##
-
- def index_object(self, docid, obj, theshold=None):
- """Add document to index"""
- if self.guard_permission is not None and self.guard_roles:
- allowed_roles = rolesForPermissionOn(self.guard_permission, obj)
- for role in allowed_roles:
- if role in self.guard_roles:
- break
- else:
- # Object does not have proper permission grant
- # to be in the index
- self.unindex_object(docid)
- return 0
- try:
- if self.field_name is not None:
- fieldvalue = _getSourceValue(obj, self.field_name)
- else:
- fieldvalue = None
- datevalue = _getSourceValue(obj, self.date_name)
- except AttributeError:
- # One or the other source attributes is missing
- # unindex the object and bail
- self.unindex_object(docid)
- return 0
- datevalue = datevalue.timeTime()
- entry = self.getEntryForObject(docid)
- if (entry is None or fieldvalue != entry['value']
- or datevalue != entry['date']):
- # XXX Note that setting the date older than a previously pruned
- # object will result in an incorrect index state. This may
- # present a problem if dates are changed arbitrarily
- if entry is None:
- self.numObjects.change(1)
- else:
- # unindex existing entry
- self.unindex_object(docid)
- self._rid2value[docid] = fieldvalue
- try:
- items = self._value2items[fieldvalue]
- except KeyError:
- # Unseen value, create a new items bucket
- items = self._value2items[fieldvalue] = OIBucket()
- items[datevalue, docid] = docid
- while len(items) > self.max_length:
- # Prune the oldest items
- olddate, oldrid = items.minKey()
- # Unindex by hand to avoid theoretical infinite loops
- self.numObjects.change(-1)
- del items[olddate, oldrid]
- if not items:
- # Not likely, unless max_length is 1
- del self._value2items[fieldvalue]
- try:
- del self._rid2value[oldrid]
- except KeyError:
- LOG('RecentItemsIndex', WARNING,
- 'Could not unindex field value for %s.' % oldrid)
- return 1
- else:
- # Index is up to date, nothing to do
- return 0
-
- def unindex_object(self, docid):
- """Remove docid from the index. If docid is not in the index,
- do nothing"""
- try:
- fieldvalue = self._rid2value[docid]
- except KeyError:
- return 0 # docid not in index
- self.numObjects.change(-1)
- del self._rid2value[docid]
- items = self._value2items[fieldvalue]
- for date, rid in items.keys():
- if rid == docid:
- del items[date, rid]
- if not items:
- del self._value2items[fieldvalue]
- return 1
- return 1
-
- def _apply_index(self, request, cid=''):
- """We do not play in normal catalog queries"""
- return None
-
- def getEntryForObject(self, docid, default=None):
- """Return a dict containing the field value and date for
- docid, or default if it is not in the index. The returned dict
- has the keys 'value' and 'date'
- """
- try:
- fieldvalue = self._rid2value[docid]
- except KeyError:
- return default
- for date, rid in self._value2items[fieldvalue].keys():
- if rid == docid:
- return {'value':fieldvalue, 'date':date}
- # If we get here then _rid2values is inconsistent with _value2items
- LOG('RecentItemsIndex', WARNING,
- 'Field value found for item %s, but no date. '
- 'This should not happen.' % docid)
- return default
-
- def hasUniqueValuesFor(self, name):
- """Return true if the index holds the unique values for name"""
- return name == self.field_name
-
- def uniqueValues(self, name=None):
- """Return the unique field values indexed"""
- if name is None:
- name = self.field_name
- if name == self.field_name:
- return self._value2items.keys()
- else:
- return []
-
- ## ZCatalog ZMI methods ##
-
- def clear(self):
- """reinitialize the index"""
- self.numObjects = Length()
- # Mapping field value => top items
- self._value2items = OOBTree()
- # Mapping indexed rid => field value for unindexing
- self._rid2value = IOBTree()
-
-
-InitializeClass(RecentItemsIndex)
-
-addIndexForm = DTMLFile('www/addIndex', globals())
Added: Produts.RecentItemsIndex/trunk/setup.py
===================================================================
--- Produts.RecentItemsIndex/trunk/setup.py (rev 0)
+++ Produts.RecentItemsIndex/trunk/setup.py 2010-03-16 22:10:38 UTC (rev 110000)
@@ -0,0 +1,55 @@
+import os
+from setuptools import setup
+from setuptools import find_packages
+
+NAME = 'RecentItemsIndex'
+
+here = os.path.abspath(os.path.dirname(__file__))
+package = os.path.join(here, 'Products', NAME)
+docs = os.path.join(here, 'docs')
+
+def _package_doc(name):
+ f = open(os.path.join(package, name))
+ return f.read()
+
+def _docs_doc(name):
+ f = open(os.path.join(docs, name))
+ return f.read()
+
+_boundary = '\n' + ('-' * 60) + '\n\n'
+README = ( open('README.txt').read()
+ + _boundary
+ + _docs_doc('CHANGES.rst')
+ )
+
+setup(name='Products.%s' % NAME,
+ version=_package_doc('version.txt').strip(),
+ description='Read Zope configuration state from profile dirs / tarballs',
+ long_description=README,
+ classifiers=[
+ "Development Status :: 4 - Beta",
+ "Framework :: Plone",
+ "Framework :: Zope2",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: Zope Public License",
+ "Programming Language :: Python",
+ ],
+ keywords='web application server zope zope2 cmf',
+ author="Zope Corporation and contributors",
+ author_email="zope-cmf at zope.org",
+ url="http://pypi.python.org/pypi/Products.%s" % NAME,
+ license="ZPL 2.1 (http://www.zope.org/Resources/License/ZPL-2.1)",
+ packages=find_packages(),
+ include_package_data=True,
+ namespace_packages=['Products'],
+ zip_safe=False,
+ install_requires=[
+ 'setuptools',
+ 'Zope2 >= 2.12.3',
+ ],
+ test_suite="Products.%s.tests" % NAME,
+ entry_points="""
+ [zope2.initialize]
+ Products.%s = Products.%s:initialize
+ """ % (NAME, NAME),
+)
Deleted: Produts.RecentItemsIndex/trunk/test.py
===================================================================
--- Produts.RecentItemsIndex/trunk/test.py 2010-03-16 21:50:57 UTC (rev 109999)
+++ Produts.RecentItemsIndex/trunk/test.py 2010-03-16 22:10:38 UTC (rev 110000)
@@ -1,433 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 Zope Corporation 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.
-#
-##############################################################################
-"""Tests for RecentItemsIndex
-
-$Id: test.py,v 1.5 2004/08/10 16:01:26 caseman Exp $"""
-
-import os
-from unittest import TestCase, TestSuite, main, makeSuite
-
-import ZODB
-from OFS.SimpleItem import SimpleItem
-from DateTime import DateTime
-
-
-class Doc:
-
- def __init__(self, **kw):
- self.__dict__.update(kw)
-
-class DummyCatalog(SimpleItem):
-
- docs = {}
-
- def __getitem__(self, item):
- return self.docs[item]
-
-class Viewable(SimpleItem):
-
- date = DateTime('2/21/2004')
- type = 'Viewable'
-
- def __init__(self, role=None):
- self._addRole(role)
- if role is not None:
- self.manage_permission('View', [role])
-
-class RecentItemsIndexTest(TestCase):
-
- def setUp(self):
- from Products.RecentItemsIndex.index import RecentItemsIndex
- self.test = DummyCatalog()
- self.test.index = RecentItemsIndex('test', 'type', 'date', 10)
- self.index = self.test.index
-
- def test_construct_with_extra(self):
- # Simulate instantiating from ZCatalog
- from Products.RecentItemsIndex.index import RecentItemsIndex
- class extra:
- field_name = 'bruford'
- date_name = 'wakeman'
- max_length = 25
- guard_roles = ['Anonymous']
- guard_permission = 'View'
- index = RecentItemsIndex('extra', extra=extra)
- self.assertEqual(index.getId(), 'extra')
- self.assertEqual(index.field_name, 'bruford')
- self.assertEqual(index.date_name, 'wakeman')
- self.assertEqual(index.max_length, 25)
- self.assertEqual(tuple(index.guard_roles), ('Anonymous',))
- self.assertEqual(index.guard_permission, 'View')
-
- def test_construct_with_no_classifier_or_guard(self):
- # Simulate instantiating from ZCatalog
- from Products.RecentItemsIndex.index import RecentItemsIndex
- class extra:
- date_name = 'modified'
- max_length = 30
- index = RecentItemsIndex('nuttin', extra=extra)
- self.assertEqual(index.getId(), 'nuttin')
- self.assertEqual(index.date_name, 'modified')
- self.assertEqual(index.max_length, 30)
-
- def test_construct_with_bogus_max_length(self):
- from Products.RecentItemsIndex.index import RecentItemsIndex
- self.assertRaises(
- Exception, RecentItemsIndex, 'test', 'type', 'date', 0)
- self.assertRaises(
- Exception, RecentItemsIndex, 'test', 'type', 'date', -20)
-
- def test_index_single(self):
- doc = Doc(type='fluke', date=DateTime('1/1/2004'))
- self.failUnless(self.index.index_object(1, doc))
- self.assertEqual(self.index.numObjects(), 1)
- self.assertEqual(self.index.getItemCounts(), {'fluke': 1})
-
- def test_exclude_obj_without_field_and_date(self):
- doc = Doc()
- self.failIf(self.index.index_object(1, doc))
- self.assertEqual(self.index.numObjects(), 0)
- self.assertEqual(self.index.getItemCounts(), {})
- doc = Doc(type='cheetos')
- self.failIf(self.index.index_object(1, doc))
- self.assertEqual(self.index.numObjects(), 0)
- self.assertEqual(self.index.getItemCounts(), {})
- doc = Doc(date=DateTime('4/17/2004'))
- self.failIf(self.index.index_object(1, doc))
- self.assertEqual(self.index.numObjects(), 0)
- self.assertEqual(self.index.getItemCounts(), {})
-
- def test_unindex_single(self):
- self.test_index_single()
- self.failUnless(self.index.unindex_object(1))
- self.assertEqual(self.index.numObjects(), 0)
- self.assertEqual(self.index.getItemCounts(), {})
-
- def test_index_many(self):
- types = ['huey', 'dooey', 'looey', 'dooey'] * 15
- date = DateTime('1/1/2004')
- docs = {}
- for docid, typ in zip(range(len(types)), types):
- if not docid % 3:
- date = date + 1
- if not docid % 7:
- date = date - (docid % 3)
- doc = docs[docid] = Doc(docid=docid, type=typ, date=date)
- self.index.index_object(docid, doc)
- maxlen = self.index.max_length
- self.assertEqual(self.index.getItemCounts(),
- {'huey': maxlen, 'dooey':maxlen, 'looey':maxlen})
- self.assertEqual(self.index.numObjects(), maxlen*3)
- self.test.docs = docs
- return docs
-
- def test_index_many_no_classifier(self):
- from Products.RecentItemsIndex.index import RecentItemsIndex
- self.test.index = RecentItemsIndex('test', None, 'date', 10)
- self.index = self.test.index
- types = ['huey', 'dooey', 'looey', 'dooey'] * 15
- date = DateTime('1/1/2004')
- docs = {}
- for docid, typ in zip(range(len(types)), types):
- if not docid % 3:
- date = date + 1
- if not docid % 7:
- date = date - (docid % 3)
- doc = docs[docid] = Doc(docid=docid, type=typ, date=date)
- self.index.index_object(docid, doc)
- maxlen = self.index.max_length
- self.assertEqual(self.index.getItemCounts(), {None: maxlen,})
- self.assertEqual(self.index.numObjects(), maxlen)
- self.test.docs = docs
- return docs
-
- def test_unindex_one_type(self):
- docs = self.test_index_many()
- for docid, doc in docs.items():
- if doc.type == 'looey':
- self.index.unindex_object(docid)
- self.assertEqual(self.index.numObjects(), 20)
- self.assertEqual(self.index.getItemCounts(), {'huey': 10, 'dooey':10})
-
- def test_unindex_all(self):
- docs = self.test_index_many()
- for docid in docs.keys():
- self.index.unindex_object(docid)
- self.assertEqual(self.index.numObjects(), 0)
- self.assertEqual(self.index.getItemCounts(), {})
- self.assertEqual(list(self.index.uniqueValues()), [])
-
- def _get_top_docs(self, docs):
- top = {'huey':[], 'dooey':[], 'looey':[]}
- for doc in docs.values():
- top[doc.type].append((doc.date.timeTime(), doc.docid))
- for typ, docs in top.items():
- docs.sort()
- top[typ] = docs[-10:]
- return top
-
- def test_getEntryForObject(self):
- docs = self.test_index_many()
- top = self._get_top_docs(docs)
- for docid, doc in docs.items():
- entry = self.index.getEntryForObject(docid)
- if entry is not None:
- self.assertEqual(entry,
- {'value': doc.type, 'date': doc.date.timeTime()})
- else:
- self.failIf((doc.date.timeTime(), doc.docid) in top[doc.type])
-
- def test_unindex_most_recent(self):
- docs = self.test_index_many()
- top = self._get_top_docs(docs)
- item_counts = self.index.getItemCounts()
- total_count = 30
- for i in range(10):
- for typ in ('huey', 'dooey', 'looey'):
- nil, byebyeid = top[typ].pop()
- self.failUnless(self.index.unindex_object(byebyeid))
- item_counts[typ] -= 1
- if not item_counts[typ]:
- del item_counts[typ]
- total_count -= 1
- self.assertEqual(self.index.getItemCounts(), item_counts)
- self.assertEqual(self.index.numObjects(), total_count)
- self.assertEqual(self.index.numObjects(), 0)
- self.assertEqual(self.index.getItemCounts(), {})
-
- def test_unindex_bogus_rid(self):
- self.test_index_many()
- self.failIf(self.index.unindex_object(-2000))
-
- def _get_indexed_doc(self, fromtop=0):
- docs = self.test_index_many()
- top = self._get_top_docs(docs)
- items = docs.items()
- if fromtop:
- items.reverse()
- for docid, doc in items:
- entry = self.index.getEntryForObject(docid)
- if entry is not None:
- break
- else:
- self.fail('No objects in index')
- self.assertEqual(entry, {'value':doc.type, 'date':doc.date.timeTime()})
- return doc
-
- def test_reindex_no_change(self):
- # reindex with no change should be a no-op
- doc = self._get_indexed_doc()
- self.failIf(self.index.index_object(doc.docid, doc))
- self.assertEqual(self.index.getEntryForObject(doc.docid),
- {'value':doc.type, 'date':doc.date.timeTime()})
-
- def test_reindex_change_date(self):
- doc = self._get_indexed_doc()
- doc.date = doc.date + 10
- self.failUnless(self.index.index_object(doc.docid, doc))
- self.assertEqual(self.index.getEntryForObject(doc.docid),
- {'value':doc.type, 'date':doc.date.timeTime()})
-
- def test_reindex_change_value(self):
- doc = self._get_indexed_doc(fromtop=1)
- oldtype = doc.type
- for typ in self.index.uniqueValues():
- if typ != oldtype:
- doc.type = typ
- break
- self.failUnless(self.index.index_object(doc.docid, doc))
- self.assertEqual(self.index.getEntryForObject(doc.docid),
- {'value':doc.type, 'date':doc.date.timeTime()})
-
- def test_reindex_change_date_and_value(self):
- doc = self._get_indexed_doc(fromtop=1)
- doc.date = doc.date + 4
- oldtype = doc.type
- for typ in self.index.uniqueValues():
- if typ != oldtype:
- doc.type = typ
- break
- self.failUnless(self.index.index_object(doc.docid, doc))
- self.assertEqual(self.index.getEntryForObject(doc.docid),
- {'value':doc.type, 'date':doc.date.timeTime()})
-
- def test_query_empty_index(self):
- result = self.index.query('foobar')
- self.failIf(result)
-
- def test_simple_query(self):
- docs = self.test_index_many()
- top = self._get_top_docs(docs)
- result = self.index.query('huey')
- expected = [docid for nil, docid in top['huey']]
- expected.reverse()
- self.assertEqual([doc.docid for doc in result], expected)
-
- def test_query_bogus_value(self):
- docs = self.test_index_many()
- self.failIf(self.index.query('snacks'))
-
- def test_query_limit(self):
- docs = self.test_index_many()
- top = self._get_top_docs(docs)
- result = self.index.query('huey', limit=3)
- expected = [docid for nil, docid in top['huey']]
- expected.reverse()
- expected = expected[:3]
- self.assertEqual([doc.docid for doc in result], expected)
-
- def test_query_no_merge(self):
- docs = self.test_index_many()
- top = self._get_top_docs(docs)
- result = self.index.query('dooey', merge=0)
- expected = [(date, docid, self.test.__getitem__)
- for date, docid in top['dooey']]
- expected.reverse()
- for rrow, erow in zip(result, expected):
- self.assertEqual(rrow[:2], erow[:2])
-
- def test_query_multiple_values(self):
- docs = self.test_index_many()
- top = self._get_top_docs(docs)
- result = self.index.query(['huey', 'dooey'])
- expected = top['huey'] + top['dooey']
- expected.sort()
- expected = [docid for nil, docid in expected]
- expected.reverse()
- self.assertEqual([doc.docid for doc in result], expected)
- return expected
-
- def test_query_all_values(self):
- docs = self.test_index_many()
- top = self._get_top_docs(docs)
- result = self.index.query()
- expected = top['huey'] + top['dooey'] + top['looey']
- expected.sort()
- expected = [docid for nil, docid in expected]
- expected.reverse()
- self.assertEqual([doc.docid for doc in result], expected)
- return expected
-
- def test_query_no_classifier(self):
- docs = self.test_index_many_no_classifier()
- top = self._get_top_docs(docs)
- result = self.index.query()
- expected = top['huey'] + top['dooey'] + top['looey']
- expected.sort()
- expected = [docid for nil, docid in expected]
- expected.reverse()
- self.assertEqual([doc.docid for doc in result], expected[:10])
-
- def test_query_no_classifier_ignores_value(self):
- docs = self.test_index_many_no_classifier()
- top = self._get_top_docs(docs)
- result = self.index.query('ptooey')
- expected = top['huey'] + top['dooey'] + top['looey']
- expected.sort()
- expected = [docid for nil, docid in expected]
- expected.reverse()
- self.assertEqual([doc.docid for doc in result], expected[:10])
-
- def test_query_multiple_with_tuple(self):
- expected = self.test_query_multiple_values()
- result = self.index.query(('huey', 'dooey'))
- self.assertEqual([doc.docid for doc in result], expected)
-
- def test_query_multiple_bogus_values(self):
- self.failIf(self.index.query(['fooey', 'blooey']))
- result = self.index.query(['blooey', 'looey'])
- expected = self.index.query('looey')
- self.assertEqual(list(result), list(expected))
-
- def test_query_multiple_limit(self):
- expected = self.test_query_multiple_values()[:4]
- result = self.index.query(['huey', 'dooey'], limit=4)
- self.assertEqual([doc.docid for doc in result], expected)
-
- def test_query_multiple_no_merge(self):
- docs = self.test_index_many()
- top = self._get_top_docs(docs)
- result = self.index.query(['dooey', 'huey'], merge=0)
- expected = [(date, docid, self.test.__getitem__)
- for date, docid in top['huey'] + top['dooey']]
- expected.sort()
- expected.reverse()
- for rrow, erow in zip(result, expected):
- self.assertEqual(rrow[:2], erow[:2])
-
- def test_apply_index(self):
- # _apply_index always returns none since recent items index
- # do not participate in the normal ZCatalog query as they
- # handle both intersection and sorting
- self.failUnless(self.index._apply_index({}) is None)
- self.failUnless(self.index._apply_index({'query':'looey'}) is None)
-
- def test_uniqueValues(self):
- self.failIf(self.index.uniqueValues('type'))
- docs = self.test_index_many()
- values = list(self.index.uniqueValues('type'))
- values.sort()
- self.assertEqual(values, ['dooey', 'huey', 'looey'])
- self.failIf(self.index.uniqueValues('carbtastic'))
-
- def test_hasUniqueValuesFor(self):
- self.failUnless(self.index.hasUniqueValuesFor('type'))
- self.failIf(self.index.hasUniqueValuesFor('spork'))
-
- def test_numObjects(self):
- docs = self.test_index_many()
- self.assertEqual(self.index.numObjects(), 30)
-
- def test_numObjects_small_maxlen(self):
- self.index.max_length = 1
- docs = self.test_index_many()
- self.assertEqual(self.index.numObjects(), 3)
-
- def test_numObjects_empty_index(self):
- self.assertEqual(self.index.numObjects(), 0)
-
- def test_clear(self):
- self.test_index_many()
- self.failUnless(self.index.numObjects())
- self.index.clear()
- self.assertEqual(self.index.numObjects(), 0)
-
- def test_role_permission_guard(self):
- from Products.RecentItemsIndex.index import RecentItemsIndex
- index = RecentItemsIndex(
- 'test', 'type', 'date', 5, ['NerfHerder', 'Bloke'], 'View')
- viewable = Viewable('NerfHerder')
- index.index_object(0, viewable)
- self.assertEqual(index.numObjects(), 1)
- notviewable = Viewable()
- index.index_object(1, notviewable)
- self.assertEqual(index.numObjects(), 1)
- bloke = Viewable('Bloke')
- index.index_object(2, bloke)
- self.assertEqual(index.numObjects(), 2)
- bloke.manage_permission('View', [])
- index.index_object(2, bloke)
- self.assertEqual(index.numObjects(), 1)
- dummy = Viewable('Dummy')
- index.index_object(3, dummy)
- self.assertEqual(index.numObjects(), 1)
- viewable.manage_permission('View', [])
- index.index_object(0, viewable)
- self.assertEqual(index.numObjects(), 0)
-
-def test_suite():
- return TestSuite((makeSuite(RecentItemsIndexTest),))
-
-if __name__=='__main__':
- main(defaultTest='test_suite')
Deleted: Produts.RecentItemsIndex/trunk/version.txt
===================================================================
--- Produts.RecentItemsIndex/trunk/version.txt 2010-03-16 21:50:57 UTC (rev 109999)
+++ Produts.RecentItemsIndex/trunk/version.txt 2010-03-16 22:10:38 UTC (rev 110000)
@@ -1 +0,0 @@
-0.1
More information about the checkins
mailing list