[Zope-Checkins] CVS: Zope/lib/python/Products/PluginIndexes/DateRangeIndex - DateRangeIndex.py:1.1.2.1 __init__.py:1.1.2.1
Martijn Pieters
mj@zope.com
Wed, 8 May 2002 17:15:01 -0400
Update of /cvs-repository/Zope/lib/python/Products/PluginIndexes/DateRangeIndex
In directory cvs.zope.org:/tmp/cvs-serv16358/DateRangeIndex
Added Files:
Tag: mj-dateindexes_integration-branch
DateRangeIndex.py __init__.py
Log Message:
Third time lucky. Rename dir, and move DateRangeIndex to it's own package.
=== Added File Zope/lib/python/Products/PluginIndexes/DateRangeIndex/DateRangeIndex.py ===
from Products.PluginIndexes import PluggableIndex
from Products.PluginIndexes.common.util import parseIndexRequest
from OFS.SimpleItem import SimpleItem
from BTrees.IOBTree import IOBTree
from BTrees.IIBTree import IISet, union, intersection
from Globals import package_home, DTMLFile, InitializeClass
from AccessControl import ClassSecurityInfo
from DateTime.DateTime import DateTime
import os
_dtmldir = os.path.join( package_home( globals() ), 'dtml' )
VIEW_PERMISSION = 'View'
INDEX_MGMT_PERMISSION = 'Manage ZCatalogIndex Entries'
class DateRangeIndex(UnIndex):
"""
Index a date range, such as the canonical "effective-expiration"
range in the CMF. Any object may return None for either the
start or the end date: for the start date, this should be
the logical equivalent of "since the beginning of time"; for the
end date, "until the end of time".
Therefore, divide the space of indexed objects into four containers:
- Objects which always match ( i.e., they returned None for both );
- Objects which match after a given time ( i.e., they returned None
for the end date );
- Objects which match until a given time ( i.e., they returned None
for the start date );
- Objects which match only during a specific interval.
"""
__implements__ = ( PluggableIndex.PluggableIndexInterface, )
security = ClassSecurityInfo()
meta_type = "DateRangeIndex"
manage_options= ( { 'label' : 'Properties'
, 'action' : 'manage_indexProperties'
}
,
)
query_options = ['query']
since_field = until_field = None
def __init__(self, id, since_field=None, until_field=None,
caller=None, extra=None):
if extra:
since_field = extra.since_field
until_field = extra.until_field
self._setId(id)
self._edit(since_field, until_field)
self.clear()
security.declareProtected( VIEW_PERMISSION
, 'getSinceField'
)
def getSinceField( self ):
"""
"""
return self._since_field
security.declareProtected( VIEW_PERMISSION
, 'getUntilField'
)
def getUntilField( self ):
"""
"""
return self._until_field
manage_indexProperties = DTMLFile( 'dri_properties', _dtmldir )
security.declareProtected( INDEX_MGMT_PERMISSION
, 'manage_edit'
)
def manage_edit( self, since_field, until_field, REQUEST ):
"""
"""
self._edit( since_field, until_field )
REQUEST[ 'RESPONSE' ].redirect( '%s/manage_main'
'?manage_tabs_message=Updated'
% REQUEST.get('URL2')
)
security.declarePrivate( '_edit' )
def _edit( self, since_field, until_field ):
"""
Update the fields used to compute the range.
"""
self._since_field = since_field
self._until_field = until_field
security.declareProtected( INDEX_MGMT_PERMISSION
, 'clear'
)
def clear( self ):
"""
Start over fresh.
"""
self._always = IISet() # XXX is this the right one?
self._since_only = IOBTree()
self._until_only = IOBTree()
self._since = IOBTree()
self._until = IOBTree()
self._unindex = IOBTree() # 'datum' will be a tuple of date ints
#
# PluggableIndexInterface implementation (XXX inherit assertions?)
#
def getEntryForObject( self, documentId, default=None ):
"""
Get all information contained for the specific object
identified by 'documentId'. Return 'default' if not found.
"""
return self._unindex.get( documentId, default )
def index_object( self, documentId, obj, threshold=None ):
"""
Index an object:
- 'documentId' is the integer ID of the document
- 'obj' is the object to be indexed
- ignore threshold
"""
if self._since_field is None:
return 0
since = getattr( obj, self._since_field, None )
if callable( since ):
since = since()
since = self._convertDateTime( since )
until = getattr( obj, self._until_field, None )
if callable( until ):
until = until()
until = self._convertDateTime( until )
datum = ( since, until )
old_datum = self._unindex.get( documentId, None )
if datum == old_datum: # No change? bail out!
return 0
if old_datum is not None:
old_since, old_until = old_datum
self._removeForwardIndexEntry( old_since, old_until, documentId )
self._insertForwardIndexEntry( since, until, documentId )
self._unindex[ documentId ] = datum
return 1
def unindex_object( self, documentId ):
"""
Remove the object corresponding to 'documentId' from the index.
"""
datum = self._unindex.get( documentId, None )
if datum is None:
return
since, until = datum
self._removeForwardIndexEntry( since, until, documentId )
del self._unindex[ documentId ]
def uniqueValues( self, name=None, withLengths=0 ):
"""
Return a list of unique values for 'name'.
If 'withLengths' is true, return a sequence of tuples, in
the form '( value, length )'.
"""
if not name in ( self._since_field, self._until_field ):
return []
if name == self._since_field:
t1 = self._since
t2 = self._since_only
else:
t1 = self._until
t2 = self._until_only
result = []
IntType = type( 0 )
if not withValues:
result.extend( t1.keys() )
result.extend( t2.keys() )
else:
for key in t1.keys():
set = t1[ key ]
if type( set ) is IntType:
length = 1
else:
length = len( set )
result.append( ( key, length) )
for key in t2.keys():
set = t2[ key ]
if type( set ) is IntType:
length = 1
else:
length = len( set )
result.append( ( key, length) )
return tuple( result )
def _apply_index( self, request, cid='' ):
"""
Apply the index to query parameters given in 'request', which
should be a mapping object.
If the request does not contain the needed parametrs, then
return None.
If the request contains a parameter with the name of the
column + "_usage", snif for information on how to handle
applying the index.
Otherwise return two objects. The first object is a ResultSet
containing the record numbers of the matching records. The
second object is a tuple containing the names of all data fields
used.
"""
record = parseIndexRequest( request, self.getId() )
if record.keys is None:
return None
term = self._convertDateTime( record.keys[0] )
#
# Aggregate sets for each bucket separately, to avoid
# large-small union penalties.
#
until_only = IISet()
# XXX use multi-union
map( until_only.update, self._until_only.values( term ) )
since_only = IISet()
# XXX use multi-union
map( since_only.update, self._since_only.values( None, term ) )
until = IISet()
# XXX use multi-union
map( until.update, self._until.values( term ) )
since = IISet()
# XXX use multi-union
map( since.update, self._since.values( None, term ) )
bounded = intersection( until, since )
result = union( self._always, until_only )
result = union( result, since_only )
result = union( result, bounded )
return result, ( self._since_field, self._until_field )
#
# ZCatalog needs this, although it isn't (yet) part of the interface.
#
security.declareProtected( VIEW_PERMISSION , 'numObjects' )
def numObjects( self ):
"""
"""
return len( self._unindex )
#
# Helper functions.
#
def _insertForwardIndexEntry( self, since, until, documentId ):
"""
Insert 'documentId' into the appropriate set based on
'datum'.
"""
if since is None and until is None:
self._always.insert( documentId )
elif since is None:
set = self._until_only.get( until, None )
if set is None:
set = self._until_only[ until ] = IISet() # XXX: Store an int?
set.insert( documentId )
elif until is None:
set = self._since_only.get( since, None )
if set is None:
set = self._since_only[ since ] = IISet() # XXX: Store an int?
set.insert( documentId )
else:
set = self._since.get( since, None )
if set is None:
set = self._since[ since ] = IISet() # XXX: Store an int?
set.insert( documentId )
set = self._until.get( until, None )
if set is None:
set = self._until[ until ] = IISet() # XXX: Store an int?
set.insert( documentId )
def _removeForwardIndexEntry( self, since, until, documentId ):
"""
Remove 'documentId' from the appropriate set based on
'datum'.
"""
if since is None and until is None:
self._always.remove( documentId )
elif since is None:
set = self._until_only.get( until, None )
if set is not None:
set.remove( documentId )
if not set:
del self._until_only[ until ]
elif until is None:
set = self._since_only.get( since, None )
if set is not None:
set.remove( documentId )
if not set:
del self._since_only[ since ]
else:
set = self._since.get( since, None )
if set is not None:
set.remove( documentId )
if not set:
del self._since[ since ]
set = self._until.get( until, None )
if set is not None:
set.remove( documentId )
if not set:
del self._until[ until ]
def _convertDateTime( self, value ):
if value is None:
return value
if type( value ) == type( '' ):
dt_obj = DateTime( value )
value = dt_obj.millis() / 1000 / 60 # flatten to minutes
if isinstance( value, DateTime ):
value = value.millis() / 1000 / 60 # flatten to minutes
return int( value )
InitializeClass( DateRangeIndex )
addDateRangeIndexForm = DTMLFile( 'dri_add', _dtmldir )
def manage_addDateRangeIndex(self, id, extra=None,
REQUEST=None, RESPONSE=None, URL3=None):
"""
Add a date range index to the catalog, using the incredibly icky
double-indirection-which-hides-NOTHING.
"""
return self.manage_addIndex(id, 'DateRangeIndex', extra,
REQUEST, RESPONSE, URL3)
=== Added File Zope/lib/python/Products/PluginIndexes/DateRangeIndex/__init__.py ===
# Empty on purpose