[Erp5-report] r16118 - in /erp5/trunk/products/HBTreeFolder2: ./ tests/
nobody at svn.erp5.org
nobody at svn.erp5.org
Thu Sep 6 15:53:47 CEST 2007
Author: aurel
Date: Thu Sep 6 15:53:47 2007
New Revision: 16118
URL: http://svn.erp5.org?rev=16118&view=rev
Log:
initial import of HBTreeFolder product
Added:
erp5/trunk/products/HBTreeFolder2/
erp5/trunk/products/HBTreeFolder2/CHANGES.txt (with props)
erp5/trunk/products/HBTreeFolder2/CMFHBTreeFolder.py (with props)
erp5/trunk/products/HBTreeFolder2/HBTreeFolder2.py (with props)
erp5/trunk/products/HBTreeFolder2/README.txt (with props)
erp5/trunk/products/HBTreeFolder2/__init__.py (with props)
erp5/trunk/products/HBTreeFolder2/btreefolder2.gif (with props)
erp5/trunk/products/HBTreeFolder2/contents.dtml (with props)
erp5/trunk/products/HBTreeFolder2/folderAdd.dtml (with props)
erp5/trunk/products/HBTreeFolder2/tests/
erp5/trunk/products/HBTreeFolder2/tests/__init__.py (with props)
erp5/trunk/products/HBTreeFolder2/tests/testHBTreeFolder2.py (with props)
erp5/trunk/products/HBTreeFolder2/version.txt (with props)
Added: erp5/trunk/products/HBTreeFolder2/CHANGES.txt
URL: http://svn.erp5.org/erp5/trunk/products/HBTreeFolder2/CHANGES.txt?rev=16118&view=auto
==============================================================================
--- erp5/trunk/products/HBTreeFolder2/CHANGES.txt (added)
+++ erp5/trunk/products/HBTreeFolder2/CHANGES.txt Thu Sep 6 15:53:47 2007
@@ -1,0 +1,4 @@
+Version 1.0.0
+
+ - Initial version
+
Propchange: erp5/trunk/products/HBTreeFolder2/CHANGES.txt
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: erp5/trunk/products/HBTreeFolder2/CHANGES.txt
------------------------------------------------------------------------------
svn:executable = *
Added: erp5/trunk/products/HBTreeFolder2/CMFHBTreeFolder.py
URL: http://svn.erp5.org/erp5/trunk/products/HBTreeFolder2/CMFHBTreeFolder.py?rev=16118&view=auto
==============================================================================
--- erp5/trunk/products/HBTreeFolder2/CMFHBTreeFolder.py (added)
+++ erp5/trunk/products/HBTreeFolder2/CMFHBTreeFolder.py Thu Sep 6 15:53:47 2007
@@ -1,0 +1,101 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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
+#
+##############################################################################
+
+from AccessControl.SecurityInfo import ClassSecurityInfo
+from Globals import InitializeClass
+from Products.HBTreeFolder2.HBTreeFolder2 import HBTreeFolder2Base
+
+try:
+ from Products.CMFCore.PortalFolder import PortalFolderBase as PortalFolder
+except ImportError:
+ from Products.CMFCore.PortalFolder import PortalFolder
+
+from Products.CMFCore.PortalFolder import factory_type_information as PortalFolder_FTI
+from Products.CMFCore.utils import getToolByName
+
+_actions = PortalFolder_FTI[0]['actions']
+
+factory_type_information = ( { 'id' : 'CMF HBTree Folder',
+ 'meta_type' : 'CMF HBTree Folder',
+ 'description' : """\
+CMF folder designed to hold a lot of objects.""",
+ 'icon' : 'folder_icon.gif',
+ 'product' : 'CMFCore',
+ 'factory' : 'manage_addCMFHBTreeFolder',
+ 'filter_content_types' : 0,
+ 'immediate_view' : 'folder_edit_form',
+ 'actions' : _actions,
+ },
+ )
+
+
+def manage_addCMFHBTreeFolder(dispatcher, id, title='', REQUEST=None):
+ """Adds a new HBTreeFolder object with id *id*.
+ """
+ id = str(id)
+ ob = CMFHBTreeFolder(id)
+ ob.title = str(title)
+ dispatcher._setObject(id, ob)
+ ob = dispatcher._getOb(id)
+ if REQUEST is not None:
+ REQUEST['RESPONSE'].redirect(ob.absolute_url() + '/manage_main' )
+
+
+class CMFHBTreeFolder(HBTreeFolder2Base, PortalFolder):
+ """HBTree folder for CMF sites.
+ """
+ meta_type = 'CMF HBTree Folder'
+ security = ClassSecurityInfo()
+
+ def __init__(self, id, title=''):
+ PortalFolder.__init__(self, id, title)
+ HBTreeFolder2Base.__init__(self, id)
+
+ def _checkId(self, id, allow_dup=0):
+ PortalFolder._checkId(self, id, allow_dup)
+ HBTreeFolder2Base._checkId(self, id, allow_dup)
+
+
+ def allowedContentTypes(self):
+ """
+ List type info objects for types which can be added in
+ this folder.
+ """
+ result = []
+ portal_types = getToolByName(self, 'portal_types')
+ myType = portal_types.getTypeInfo(self)
+
+ if myType is not None:
+ allowed_types_to_check = []
+ if myType.filter_content_types:
+ for portal_type in myType.allowed_content_types:
+ contentType = portal_types.getTypeInfo(portal_type)
+ if contentType is None:
+ raise AttributeError, "Portal type '%s' does not exist " \
+ "and should not be allowed in '%s'" % \
+ (portal_type, self.getPortalType())
+ result.append(contentType)
+ else:
+ for contentType in portal_types.listTypeInfo(self):
+ if myType.allowType(contentType.getId()):
+ result.append(contentType)
+ else:
+ result = portal_types.listTypeInfo()
+
+ return filter(
+ lambda typ, container=self: typ.isConstructionAllowed(container),
+ result)
+
+
+InitializeClass(CMFHBTreeFolder)
Propchange: erp5/trunk/products/HBTreeFolder2/CMFHBTreeFolder.py
------------------------------------------------------------------------------
svn:executable = *
Added: erp5/trunk/products/HBTreeFolder2/HBTreeFolder2.py
URL: http://svn.erp5.org/erp5/trunk/products/HBTreeFolder2/HBTreeFolder2.py?rev=16118&view=auto
==============================================================================
--- erp5/trunk/products/HBTreeFolder2/HBTreeFolder2.py (added)
+++ erp5/trunk/products/HBTreeFolder2/HBTreeFolder2.py Thu Sep 6 15:53:47 2007
@@ -1,0 +1,605 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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
+#
+##############################################################################
+
+import sys
+from cgi import escape
+from urllib import quote
+from random import randint
+from types import StringType
+
+import Globals
+from Globals import DTMLFile
+from Globals import Persistent, PersistentMapping
+from Acquisition import aq_base
+from BTrees.OOBTree import OOBTree
+from BTrees.OIBTree import OIBTree, union
+from BTrees.Length import Length
+from ZODB.POSException import ConflictError
+from OFS.ObjectManager import BadRequestException, BeforeDeleteException
+from OFS.Folder import Folder
+from AccessControl import getSecurityManager, ClassSecurityInfo
+from AccessControl.Permissions import access_contents_information, \
+ view_management_screens
+from zLOG import LOG, INFO, ERROR, WARNING
+from Products.ZCatalog.Lazy import LazyMap, LazyFilter, LazyCat
+
+
+manage_addHBTreeFolder2Form = DTMLFile('folderAdd', globals())
+
+def manage_addHBTreeFolder2(dispatcher, id, title='', REQUEST=None):
+ """Adds a new HBTreeFolder object with id *id*.
+ """
+ id = str(id)
+ ob = HBTreeFolder2(id)
+ ob.title = str(title)
+ dispatcher._setObject(id, ob)
+ ob = dispatcher._getOb(id)
+ if REQUEST is not None:
+ return dispatcher.manage_main(dispatcher, REQUEST, update_menu=1)
+
+
+listtext0 = '''<select name="ids:list" multiple="multiple" size="%s">
+'''
+listtext1 = '''<option value="%s">%s</option>
+'''
+listtext2 = '''</select>
+'''
+
+
+_marker = [] # Create a new marker object.
+
+MAX_UNIQUEID_ATTEMPTS = 1000
+MAX_OBJECT_PER_LEVEL = 1000
+H_SEPARATOR = '-'
+
+class ExhaustedUniqueIdsError (Exception):
+ pass
+
+
+class HBTreeFolder2Base (Persistent):
+ """Base for BTree-based folders.
+ """
+
+ security = ClassSecurityInfo()
+
+ manage_options=(
+ ({'label':'Contents', 'action':'manage_main',},
+ ) + Folder.manage_options[1:]
+ )
+
+ security.declareProtected(view_management_screens,
+ 'manage_main')
+ manage_main = DTMLFile('contents', globals())
+
+ _htree = None # OOBTree: { id -> object }
+ _count = None # A BTrees.Length
+ _v_nextid = 0 # The integer component of the next generated ID
+ title = ''
+ _tree_list = None
+
+
+ def __init__(self, id=None):
+ if id is not None:
+ self.id = id
+ self._initBTrees()
+
+ def _initBTrees(self):
+ self._htree = OOBTree()
+ self._count = Length()
+ self._tree_list = PersistentMapping()
+
+ def initBTrees(self):
+ """ """
+ return self._initBTrees()
+
+ def _populateFromFolder(self, source):
+ """Fill this folder with the contents of another folder.
+ """
+ for name in source.objectIds():
+ value = source._getOb(name, None)
+ if value is not None:
+ self._setOb(name, aq_base(value))
+
+
+ security.declareProtected(view_management_screens, 'manage_fixCount')
+ def manage_fixCount(self):
+ """Calls self._fixCount() and reports the result as text.
+ """
+ old, new = self._fixCount()
+ path = '/'.join(self.getPhysicalPath())
+ if old == new:
+ return "No count mismatch detected in HBTreeFolder2 at %s." % path
+ else:
+ return ("Fixed count mismatch in HBTreeFolder2 at %s. "
+ "Count was %d; corrected to %d" % (path, old, new))
+
+
+ def _fixCount(self):
+ """Checks if the value of self._count disagrees with
+ len(self.objectIds()). If so, corrects self._count. Returns the
+ old and new count values. If old==new, no correction was
+ performed.
+ """
+ old = self._count()
+ new = len(self.objectIds())
+ if old != new:
+ self._count.set(new)
+ return old, new
+
+
+ security.declareProtected(view_management_screens, 'manage_cleanup')
+ def manage_cleanup(self):
+ """Calls self._cleanup() and reports the result as text.
+ """
+ v = self._cleanup()
+ path = '/'.join(self.getPhysicalPath())
+ if v:
+ return "No damage detected in HBTreeFolder2 at %s." % path
+ else:
+ return ("Fixed HBTreeFolder2 at %s. "
+ "See the log for more details." % path)
+
+
+ def _cleanup(self):
+ """Cleans up errors in the BTrees.
+
+ Certain ZODB bugs have caused BTrees to become slightly insane.
+ Fortunately, there is a way to clean up damaged BTrees that
+ always seems to work: make a new BTree containing the items()
+ of the old one.
+
+ Returns 1 if no damage was detected, or 0 if damage was
+ detected and fixed.
+ """
+ def hCheck(htree):
+ """
+ Recursively check the btree
+ """
+ check(htree)
+ for key in htree.keys():
+ if not htree.has_key(key):
+ raise AssertionError(
+ "Missing value for key: %s" % repr(key))
+ else:
+ ob = htree[key]
+ if isinstance(ob, OOBTree):
+ hCheck(ob)
+ return 1
+
+ from BTrees.check import check
+ path = '/'.join(self.getPhysicalPath())
+ try:
+ return hCheck(self._htree)
+ except AssertionError:
+ LOG('HBTreeFolder2', WARNING,
+ 'Detected damage to %s. Fixing now.' % path,
+ error=sys.exc_info())
+ try:
+ self._htree = OOBTree(self._htree) # XXX hFix needed
+ except:
+ LOG('HBTreeFolder2', ERROR, 'Failed to fix %s.' % path,
+ error=sys.exc_info())
+ raise
+ else:
+ LOG('HBTreeFolder2', INFO, 'Fixed %s.' % path)
+ return 0
+
+ def hashId(self, id):
+ """Return a tuple of ids
+ """
+ id_list = str(id).split(H_SEPARATOR) # We use '-' as the separator by default
+ if len(id_list) > 1:
+ return tuple(id_list)
+ else:
+ return [id,]
+
+# try: # We then try int hashing
+# id_int = int(id)
+# except ValueError:
+# return id_list
+# result = []
+# while id_int:
+# result.append(id_int % MAX_OBJECT_PER_LEVEL)
+# id_int = id_int / MAX_OBJECT_PER_LEVEL
+# result.reverse()
+# return tuple(result)
+
+ def _getOb(self, id, default=_marker):
+ """
+ Return the named object from the folder.
+ """
+ htree = self._htree
+ ob = htree
+ id_list = self.hashId(id)
+ for sub_id in id_list[0:-1]:
+ if default is _marker:
+ ob = ob[sub_id]
+ else:
+ ob = ob.get(sub_id, _marker)
+ if ob is _marker:
+ return default
+ if default is _marker:
+ ob = ob[id]
+ else:
+ ob = ob.get(id, _marker)
+ if ob is _marker:
+ return default
+ return ob.__of__(self)
+
+ def _setOb(self, id, object):
+ """Store the named object in the folder.
+ """
+ htree = self._htree
+ id_list = self.hashId(id)
+ for idx in xrange(len(id_list[0:-1])):
+ sub_id = id_list[idx]
+ if not htree.has_key(sub_id):
+ # Create a new index and index it
+ htree[sub_id] = OOBTree()
+ if isinstance(sub_id, int) or isinstance(sub_id, long):
+ tree_id = 0
+ for id in id_list[:idx+1]:
+ tree_id = tree_id + id * MAX_OBJECT_PER_LEVEL
+ else:
+ tree_id = H_SEPARATOR.join(id_list[:idx+1])
+ self._tree_list[tree_id] = None
+
+ htree = htree[sub_id]
+ # set object in subtree
+ ob_id = id_list[-1]
+ if htree.has_key(id):
+ raise KeyError('There is already an item named "%s".' % id)
+ htree[id] = object
+ self._count.change(1)
+
+ def _delOb(self, id):
+ """Remove the named object from the folder.
+ """
+ htree = self._htree
+ id_list = self.hashId(id)
+ for sub_id in id_list[0:-1]:
+ htree = htree[sub_id]
+ del htree[id]
+ self._count.change(-1)
+
+ security.declareProtected(view_management_screens, 'getBatchObjectListing')
+ def getBatchObjectListing(self, REQUEST=None):
+ """Return a structure for a page template to show the list of objects.
+ """
+ if REQUEST is None:
+ REQUEST = {}
+ pref_rows = int(REQUEST.get('dtpref_rows', 20))
+ b_start = int(REQUEST.get('b_start', 1))
+ b_count = int(REQUEST.get('b_count', 1000))
+ b_end = b_start + b_count - 1
+ url = self.absolute_url() + '/manage_main'
+ idlist = self.objectIds() # Pre-sorted.
+ count = self.objectCount()
+
+ if b_end < count:
+ next_url = url + '?b_start=%d' % (b_start + b_count)
+ else:
+ b_end = count
+ next_url = ''
+
+ if b_start > 1:
+ prev_url = url + '?b_start=%d' % max(b_start - b_count, 1)
+ else:
+ prev_url = ''
+
+ formatted = []
+ formatted.append(listtext0 % pref_rows)
+ for i in range(b_start - 1, b_end):
+ optID = escape(idlist[i])
+ formatted.append(listtext1 % (escape(optID, quote=1), optID))
+ formatted.append(listtext2)
+ return {'b_start': b_start, 'b_end': b_end,
+ 'prev_batch_url': prev_url,
+ 'next_batch_url': next_url,
+ 'formatted_list': ''.join(formatted)}
+
+
+ security.declareProtected(view_management_screens,
+ 'manage_object_workspace')
+ def manage_object_workspace(self, ids=(), REQUEST=None):
+ '''Redirects to the workspace of the first object in
+ the list.'''
+ if ids and REQUEST is not None:
+ REQUEST.RESPONSE.redirect(
+ '%s/%s/manage_workspace' % (
+ self.absolute_url(), quote(ids[0])))
+ else:
+ return self.manage_main(self, REQUEST)
+
+
+ security.declareProtected(access_contents_information,
+ 'tpValues')
+ def tpValues(self):
+ """Ensures the items don't show up in the left pane.
+ """
+ return ()
+
+
+ security.declareProtected(access_contents_information,
+ 'objectCount')
+ def objectCount(self):
+ """Returns the number of items in the folder."""
+ return self._count()
+
+
+ security.declareProtected(access_contents_information, 'has_key')
+ def has_key(self, id):
+ """Indicates whether the folder has an item by ID.
+ """
+ htree = self._htree
+ id_list = self.hashId(id)
+ for sub_id in id_list[0:-1]:
+ if not isinstance(htree, OOBTree):
+ return 0
+ if not htree.has_key(sub_id):
+ return 0
+ htree = htree[sub_id]
+ if not htree.has_key(id):
+ return 0
+ return 1
+
+
+ security.declareProtected(access_contents_information,
+ 'treeIds')
+ def treeIds(self, base_id=None):
+ """ Return a list of subtree ids
+ """
+ tree = self._getTree(base_id=base_id)
+ return [x for x in self._htree.keys() if isinstance(self._htree[x], OOBTree)]
+
+
+ def _getTree(self, base_id):
+ """ Return the tree wich has the base_id
+ """
+ htree = self._htree
+ id_list = self.hashId(base_id)
+ for sub_id in id_list:
+ if not isinstance(htree, OOBTree):
+ return None
+ if not htree.has_key(sub_id):
+ raise IndexError, base_id
+ htree = htree[sub_id]
+ return htree
+
+ def _getTreeIdList(self, htree=None):
+ """ recursively build a list of btree ids
+ """
+ if htree is None:
+ htree = self._htree
+ btree_list = [None,]
+ else:
+ btree_list = []
+ for obj_id in htree.keys():
+ obj = htree[obj_id]
+ if isinstance(obj, OOBTree):
+ btree_list.extend(["%s-%s"%(obj_id, x) for x in self._getTreeIdList(htree=obj)])
+ btree_list.append(obj_id)
+
+ return btree_list
+
+ security.declareProtected(access_contents_information,
+ 'getTreeIdList')
+ def getTreeIdList(self, htree=None):
+ """ Return list of all tree ids
+ """
+ if self._tree_list is None or len(self._tree_list.keys()) == 0:
+ tree_list = self._getTreeIdList(htree=htree)
+ self._tree_list = PersistentMapping()
+ for tree in tree_list:
+ self._tree_list[tree] = None
+ return sorted(self._tree_list.keys())
+
+
+ def _treeObjectValues(self, base_id=None):
+ """ return object values for a given btree
+ """
+ if base_id is not None:
+ return LazyFilter(self._isNotBTree, self._getTree("%s" %base_id).values())
+ else:
+ return LazyFilter(self._isNotBTree, self._htree.values())
+
+ def _treeObjectIds(self, base_id=None):
+ """ return object ids for a given btree
+ """
+ if base_id is not None:
+ return LazyFilter(self._checkObjectId, self._getTree("%s" %base_id).keys())
+ else:
+ return LazyFilter(self._checkObjectId, self._htree.keys())
+
+ def _isNotBTree(self, obj):
+ """ test object is not a btree
+ """
+ if isinstance(obj, OOBTree):
+ return False
+ else:
+ return True
+
+ def _checkObjectId(self, id):
+ """ test id is not in btree id list
+ """
+ return not self._tree_list.has_key(id)
+
+ security.declareProtected(access_contents_information,
+ 'ObjectValues')
+ def objectValues(self, base_id=None):
+ return LazyMap(self._getOb, self.objectIds(base_id))
+
+
+ security.declareProtected(access_contents_information,
+ 'objectIds')
+ def objectIds(self, base_id=None):
+ if base_id is None:
+ return LazyCat(LazyMap(self._treeObjectIds, self.getTreeIdList()))
+ else:
+ return self._treeObjectIds(base_id=base_id)
+
+
+ security.declareProtected(access_contents_information,
+ 'objectItems')
+ def objectItems(self, spec=None):
+ # Returns a list of (id, subobject) tuples of the current object.
+ # If 'spec' is specified, returns only objects whose meta_type match
+ # 'spec'
+ return LazyMap(lambda id, _getOb=self._getOb: (id, _getOb(id)),
+ self.objectIds(spec))
+
+
+ security.declareProtected(access_contents_information,
+ 'objectMap')
+ def objectMap(self):
+ # Returns a tuple of mappings containing subobject meta-data.
+ return LazyMap(lambda (k, v):
+ {'id': k, 'meta_type': getattr(v, 'meta_type', None)},
+ self._htree.items(), self._count())
+
+ # superValues() looks for the _objects attribute, but the implementation
+ # would be inefficient, so superValues() support is disabled.
+ _objects = ()
+
+
+ security.declareProtected(access_contents_information,
+ 'objectIds_d')
+ def objectIds_d(self, t=None):
+ ids = self.objectIds(t)
+ res = {}
+ for id in ids:
+ res[id] = 1
+ return res
+
+
+ security.declareProtected(access_contents_information,
+ 'objectMap_d')
+ def objectMap_d(self, t=None):
+ return self.objectMap()
+
+
+ def _checkId(self, id, allow_dup=0):
+ if not allow_dup and self.has_key(id):
+ raise BadRequestException, ('The id "%s" is invalid--'
+ 'it is already in use.' % id)
+
+
+ def _setObject(self, id, object, roles=None, user=None, set_owner=1):
+ v=self._checkId(id)
+ if v is not None: id=v
+
+ # If an object by the given id already exists, remove it.
+ if self.has_key(id):
+ self._delObject(id)
+
+ self._setOb(id, object)
+ object = self._getOb(id)
+
+ if set_owner:
+ object.manage_fixupOwnershipAfterAdd()
+
+ # Try to give user the local role "Owner", but only if
+ # no local roles have been set on the object yet.
+ if hasattr(object, '__ac_local_roles__'):
+ if object.__ac_local_roles__ is None:
+ user=getSecurityManager().getUser()
+ if user is not None:
+ userid=user.getId()
+ if userid is not None:
+ object.manage_setLocalRoles(userid, ['Owner'])
+
+ object.manage_afterAdd(object, self)
+ return id
+
+
+ def _delObject(self, id, dp=1):
+ object = self._getOb(id)
+ try:
+ object.manage_beforeDelete(object, self)
+ except BeforeDeleteException, ob:
+ raise
+ except ConflictError:
+ raise
+ except:
+ LOG('Zope', ERROR, 'manage_beforeDelete() threw',
+ error=sys.exc_info())
+ self._delOb(id)
+
+
+ # Aliases for mapping-like access.
+ __len__ = objectCount
+ keys = objectIds
+ values = objectValues
+ items = objectItems
+
+ # backward compatibility
+ hasObject = has_key
+
+ security.declareProtected(access_contents_information, 'get')
+ def get(self, name, default=None):
+ return self._getOb(name, default)
+
+
+ # Utility for generating unique IDs.
+
+ security.declareProtected(access_contents_information, 'generateId')
+ def generateId(self, prefix='item', suffix='', rand_ceiling=999999999):
+ """Returns an ID not used yet by this folder.
+
+ The ID is unlikely to collide with other threads and clients.
+ The IDs are sequential to optimize access to objects
+ that are likely to have some relation.
+ """
+ tree = self._htree
+ n = self._v_nextid
+ attempt = 0
+ while 1:
+ if n % 4000 != 0 and n <= rand_ceiling:
+ id = '%s%d%s' % (prefix, n, suffix)
+ if not tree.has_key(id):
+ break
+ n = randint(1, rand_ceiling)
+ attempt = attempt + 1
+ if attempt > MAX_UNIQUEID_ATTEMPTS:
+ # Prevent denial of service
+ raise ExhaustedUniqueIdsError
+ self._v_nextid = n + 1
+ return id
+
+ def __getattr__(self, name):
+ # Boo hoo hoo! Zope 2 prefers implicit acquisition over traversal
+ # to subitems, and __bobo_traverse__ hooks don't work with
+ # restrictedTraverse() unless __getattr__() is also present.
+ # Oh well.
+ res = self._htree.get(name)
+ if res is None:
+ raise AttributeError, name
+ return res
+
+
+Globals.InitializeClass(HBTreeFolder2Base)
+
+
+class HBTreeFolder2 (HBTreeFolder2Base, Folder):
+ """BTreeFolder2 based on OFS.Folder.
+ """
+ meta_type = 'HBTreeFolder2'
+
+ def _checkId(self, id, allow_dup=0):
+ Folder._checkId(self, id, allow_dup)
+ HBTreeFolder2Base._checkId(self, id, allow_dup)
+
+
+Globals.InitializeClass(HBTreeFolder2)
+
Propchange: erp5/trunk/products/HBTreeFolder2/HBTreeFolder2.py
------------------------------------------------------------------------------
svn:executable = *
Added: erp5/trunk/products/HBTreeFolder2/README.txt
URL: http://svn.erp5.org/erp5/trunk/products/HBTreeFolder2/README.txt?rev=16118&view=auto
==============================================================================
--- erp5/trunk/products/HBTreeFolder2/README.txt (added)
+++ erp5/trunk/products/HBTreeFolder2/README.txt Thu Sep 6 15:53:47 2007
@@ -1,0 +1,2 @@
+TODO
+
Propchange: erp5/trunk/products/HBTreeFolder2/README.txt
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: erp5/trunk/products/HBTreeFolder2/README.txt
------------------------------------------------------------------------------
svn:executable = *
Added: erp5/trunk/products/HBTreeFolder2/__init__.py
URL: http://svn.erp5.org/erp5/trunk/products/HBTreeFolder2/__init__.py?rev=16118&view=auto
==============================================================================
--- erp5/trunk/products/HBTreeFolder2/__init__.py (added)
+++ erp5/trunk/products/HBTreeFolder2/__init__.py Thu Sep 6 15:53:47 2007
@@ -1,0 +1,48 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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
+#
+##############################################################################
+
+import HBTreeFolder2
+
+def initialize(context):
+
+ context.registerClass(
+ HBTreeFolder2.HBTreeFolder2,
+ constructors=(HBTreeFolder2.manage_addHBTreeFolder2Form,
+ HBTreeFolder2.manage_addHBTreeFolder2),
+ icon='btreefolder2.gif',
+ )
+
+ #context.registerHelp()
+ #context.registerHelpTitle('Zope Help')
+
+ context.registerBaseClass(HBTreeFolder2.HBTreeFolder2)
+
+ try:
+ from Products.CMFCore import utils
+ except ImportError:
+ # CMF not installed
+ pass
+ else:
+ # CMF installed; make available a special folder type.
+ import CMFHBTreeFolder
+ ADD_FOLDERS_PERMISSION = 'Add portal folders'
+
+ utils.ContentInit(
+ 'CMF HBTree Folder',
+ content_types=(CMFHBTreeFolder.CMFHBTreeFolder,),
+ permission=ADD_FOLDERS_PERMISSION,
+ extra_constructors=(CMFHBTreeFolder.manage_addCMFHBTreeFolder,),
+ fti=CMFHBTreeFolder.factory_type_information
+ ).initialize(context)
+
Propchange: erp5/trunk/products/HBTreeFolder2/__init__.py
------------------------------------------------------------------------------
svn:executable = *
Added: erp5/trunk/products/HBTreeFolder2/btreefolder2.gif
URL: http://svn.erp5.org/erp5/trunk/products/HBTreeFolder2/btreefolder2.gif?rev=16118&view=auto
==============================================================================
Binary file - no diff available.
Propchange: erp5/trunk/products/HBTreeFolder2/btreefolder2.gif
------------------------------------------------------------------------------
svn:executable = *
Propchange: erp5/trunk/products/HBTreeFolder2/btreefolder2.gif
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: erp5/trunk/products/HBTreeFolder2/contents.dtml
URL: http://svn.erp5.org/erp5/trunk/products/HBTreeFolder2/contents.dtml?rev=16118&view=auto
==============================================================================
--- erp5/trunk/products/HBTreeFolder2/contents.dtml (added)
+++ erp5/trunk/products/HBTreeFolder2/contents.dtml Thu Sep 6 15:53:47 2007
@@ -1,0 +1,164 @@
+<dtml-let form_title="'Contents'">
+<dtml-if manage_page_header>
+ <dtml-var manage_page_header>
+<dtml-else>
+ <html><head><title>&dtml-form_title;</title></head>
+ <body bgcolor="#ffffff">
+</dtml-if>
+</dtml-let>
+<dtml-var manage_tabs>
+
+<script type="text/javascript">
+<!--
+
+isSelected = false;
+
+function toggleSelect() {
+ elem = document.objectItems.elements['ids:list'];
+ if (isSelected == false) {
+ for (i = 0; i < elem.options.length; i++) {
+ elem.options[i].selected = true;
+ }
+ isSelected = true;
+ document.objectItems.selectButton.value = "Deselect All";
+ return isSelected;
+ }
+ else {
+ for (i = 0; i < elem.options.length; i++) {
+ elem.options[i].selected = false;
+ }
+ isSelected = false;
+ document.objectItems.selectButton.value = "Select All";
+ return isSelected;
+ }
+}
+
+//-->
+</script>
+
+<dtml-unless skey><dtml-call expr="REQUEST.set('skey', 'id')"></dtml-unless>
+<dtml-unless rkey><dtml-call expr="REQUEST.set('rkey', '')"></dtml-unless>
+
+<!-- Add object widget -->
+<br />
+<dtml-if filtered_meta_types>
+ <table width="100%" cellspacing="0" cellpadding="0" border="0">
+ <tr>
+ <td align="left" valign="top"> </td>
+ <td align="right" valign="top">
+ <div class="form-element">
+ <form action="&dtml-URL1;/" method="get">
+ <dtml-if "_.len(filtered_meta_types) > 1">
+ <select class="form-element" name=":action"
+ onChange="location.href='&dtml-URL1;/'+this.options[this.selectedIndex].value">
+ <option value="manage_workspace" disabled>Select type to add...</option>
+ <dtml-in filtered_meta_types mapping sort=name>
+ <option value="&dtml.url_quote-action;">&dtml-name;</option>
+ </dtml-in>
+ </select>
+ <input class="form-element" type="submit" name="submit" value=" Add " />
+ <dtml-else>
+ <dtml-in filtered_meta_types mapping sort=name>
+ <input type="hidden" name=":method" value="&dtml.url_quote-action;" />
+ <input class="form-element" type="submit" name="submit" value=" Add &dtml-name;" />
+ </dtml-in>
+ </dtml-if>
+ </form>
+ </div>
+ </td>
+ </tr>
+ </table>
+</dtml-if>
+
+<form action="&dtml-URL1;/" name="objectItems" method="post">
+<dtml-if objectCount>
+<dtml-with expr="getBatchObjectListing(REQUEST)" mapping>
+
+<p>
+<dtml-if prev_batch_url><a href="&dtml-prev_batch_url;"><<</a></dtml-if>
+<em>Items <dtml-var b_start> through <dtml-var b_end> of <dtml-var objectCount></em>
+<dtml-if next_batch_url><a href="&dtml-next_batch_url;">>></a></dtml-if>
+</p>
+
+<dtml-var formatted_list>
+
+<table cellspacing="0" cellpadding="2" border="0">
+<tr>
+ <td align="left" valign="top" width="16"></td>
+ <td align="left" valign="top">
+ <div class="form-element">
+ <input class="form-element" type="submit"
+ name="manage_object_workspace:method" value="Edit" />
+ <dtml-unless dontAllowCopyAndPaste>
+ <input class="form-element" type="submit" name="manage_renameForm:method"
+ value="Rename" />
+ <input class="form-element" type="submit" name="manage_cutObjects:method"
+ value="Cut" />
+ <input class="form-element" type="submit" name="manage_copyObjects:method"
+ value="Copy" />
+ <dtml-if cb_dataValid>
+ <input class="form-element" type="submit" name="manage_pasteObjects:method"
+ value="Paste" />
+ </dtml-if>
+ </dtml-unless>
+ <dtml-if "_.SecurityCheckPermission('Delete objects',this())">
+ <input class="form-element" type="submit" name="manage_delObjects:method"
+ value="Delete" />
+ </dtml-if>
+ <dtml-if "_.SecurityCheckPermission('Import/Export objects', this())">
+ <input class="form-element" type="submit"
+ name="manage_importExportForm:method"
+ value="Import/Export" />
+ </dtml-if>
+<script type="text/javascript">
+<!--
+if (document.forms[0]) {
+ document.write('<input class="form-element" type="submit" name="selectButton" value="Select All" onClick="toggleSelect(); return false">')
+ }
+//-->
+</script>
+ </div>
+ </td>
+</tr>
+</table>
+
+</dtml-with>
+<dtml-else>
+<table cellspacing="0" cellpadding="2" border="0">
+<tr>
+<td>
+<div class="std-text">
+There are currently no items in <em>&dtml-title_or_id;</em>
+<br /><br />
+</div>
+<dtml-unless dontAllowCopyAndPaste>
+<dtml-if cb_dataValid>
+<div class="form-element">
+<input class="form-element" type="submit" name="manage_pasteObjects:method"
+ value="Paste" />
+</div>
+</dtml-if>
+</dtml-unless>
+<dtml-if "_.SecurityCheckPermission('Import/Export objects', this())">
+<input class="form-element" type="submit"
+ name="manage_importExportForm:method" value="Import/Export" />
+</dtml-if>
+</td>
+</tr>
+</table>
+</dtml-if>
+</form>
+
+<dtml-if update_menu>
+<script type="text/javascript">
+<!--
+window.parent.update_menu();
+//-->
+</script>
+</dtml-if>
+
+<dtml-if manage_page_footer>
+ <dtml-var manage_page_footer>
+<dtml-else>
+ </body></html>
+</dtml-if>
Propchange: erp5/trunk/products/HBTreeFolder2/contents.dtml
------------------------------------------------------------------------------
svn:executable = *
Added: erp5/trunk/products/HBTreeFolder2/folderAdd.dtml
URL: http://svn.erp5.org/erp5/trunk/products/HBTreeFolder2/folderAdd.dtml?rev=16118&view=auto
==============================================================================
--- erp5/trunk/products/HBTreeFolder2/folderAdd.dtml (added)
+++ erp5/trunk/products/HBTreeFolder2/folderAdd.dtml Thu Sep 6 15:53:47 2007
@@ -1,0 +1,67 @@
+<dtml-let form_title="'Add HBTreeFolder2'">
+<dtml-if manage_page_header>
+ <dtml-var manage_page_header>
+ <dtml-var manage_form_title>
+<dtml-else>
+ <html><head><title>&dtml-form_title;</title></head>
+ <body bgcolor="#ffffff">
+ <h2>&dtml-form_title;</h2>
+</dtml-if>
+</dtml-let>
+
+<p class="form-help">
+A Folder contains other objects. Use Folders to organize your
+web objects in to logical groups.
+</p>
+
+<p class="form-help">
+A HBTreeFolder2 may be able to handle a larger number
+of objects than a standard BTreeFolder because it use btree of
+btree to store objects.
+It is more efficient than the original BTreeFolder2 product,
+but you must provide well constructed id according to hashId method
+</p>
+
+<FORM ACTION="manage_addHBTreeFolder2" METHOD="POST">
+
+<table cellspacing="0" cellpadding="2" border="0">
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Id
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="id" size="40" />
+ </td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-optional">
+ Title
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="title" size="40" />
+ </td>
+ </tr>
+
+ <tr>
+ <td align="left" valign="top">
+ </td>
+ <td align="left" valign="top">
+ <div class="form-element">
+ <input class="form-element" type="submit" name="submit"
+ value="Add" />
+ </div>
+ </td>
+ </tr>
+</table>
+</form>
+
+<dtml-if manage_page_footer>
+ <dtml-var manage_page_footer>
+<dtml-else>
+ </body></html>
+</dtml-if>
Propchange: erp5/trunk/products/HBTreeFolder2/folderAdd.dtml
------------------------------------------------------------------------------
svn:executable = *
Added: erp5/trunk/products/HBTreeFolder2/tests/__init__.py
URL: http://svn.erp5.org/erp5/trunk/products/HBTreeFolder2/tests/__init__.py?rev=16118&view=auto
==============================================================================
--- erp5/trunk/products/HBTreeFolder2/tests/__init__.py (added)
+++ erp5/trunk/products/HBTreeFolder2/tests/__init__.py Thu Sep 6 15:53:47 2007
@@ -1,0 +1,1 @@
+"""Python package."""
Propchange: erp5/trunk/products/HBTreeFolder2/tests/__init__.py
------------------------------------------------------------------------------
svn:executable = *
Added: erp5/trunk/products/HBTreeFolder2/tests/testHBTreeFolder2.py
URL: http://svn.erp5.org/erp5/trunk/products/HBTreeFolder2/tests/testHBTreeFolder2.py?rev=16118&view=auto
==============================================================================
--- erp5/trunk/products/HBTreeFolder2/tests/testHBTreeFolder2.py (added)
+++ erp5/trunk/products/HBTreeFolder2/tests/testHBTreeFolder2.py Thu Sep 6 15:53:47 2007
@@ -1,0 +1,219 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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
+#
+##############################################################################
+
+import unittest
+import ZODB
+import Testing
+import Zope
+from Products.HBTreeFolder2.HBTreeFolder2 \
+ import HBTreeFolder2, ExhaustedUniqueIdsError
+from OFS.ObjectManager import BadRequestException
+from OFS.Folder import Folder
+from Acquisition import aq_base
+
+
+class HBTreeFolder2Tests(unittest.TestCase):
+
+ def getBase(self, ob):
+ # This is overridden in subclasses.
+ return aq_base(ob)
+
+ def setUp(self):
+ self.f = HBTreeFolder2('root')
+ ff = HBTreeFolder2('item')
+ self.f._setOb(ff.id, ff)
+ self.ff = self.f._getOb(ff.id)
+
+ def testAdded(self):
+ self.assertEqual(self.ff.id, 'item')
+
+ def testCount(self):
+ self.assertEqual(self.f.objectCount(), 1)
+ self.assertEqual(self.ff.objectCount(), 0)
+ self.assertEqual(len(self.f), 1)
+ self.assertEqual(len(self.ff), 0)
+
+ def testObjectIds(self):
+ self.assertEqual(list(self.f.objectIds()), ['item'])
+ self.assertEqual(list(self.f.keys()), ['item'])
+ self.assertEqual(list(self.ff.objectIds()), [])
+ f3 = HBTreeFolder2('item3')
+ self.f._setOb(f3.id, f3)
+ lst = list(self.f.objectIds())
+ lst.sort()
+ self.assertEqual(lst, ['item', 'item3'])
+
+ def testObjectValues(self):
+ values = self.f.objectValues()
+ self.assertEqual(len(values), 1)
+ self.assertEqual(values[0].id, 'item')
+ # Make sure the object is wrapped.
+ self.assert_(values[0] is not self.getBase(values[0]))
+
+ def testObjectItems(self):
+ items = self.f.objectItems()
+ self.assertEqual(len(items), 1)
+ id, val = items[0]
+ self.assertEqual(id, 'item')
+ self.assertEqual(val.id, 'item')
+ # Make sure the object is wrapped.
+ self.assert_(val is not self.getBase(val))
+
+ def testHasKey(self):
+ self.assert_(self.f.hasObject('item')) # Old spelling
+ self.assert_(self.f.has_key('item')) # New spelling
+
+ def testDelete(self):
+ self.f._delOb('item')
+ self.assertEqual(list(self.f.objectIds()), [])
+ self.assertEqual(self.f.objectCount(), 0)
+
+ def testObjectMap(self):
+ map = self.f.objectMap()
+ self.assertEqual(list(map), [{'id': 'item', 'meta_type':
+ self.ff.meta_type}])
+ # I'm not sure why objectMap_d() exists, since it appears to be
+ # the same as objectMap(), but it's implemented by Folder.
+ self.assertEqual(list(self.f.objectMap_d()), list(self.f.objectMap()))
+
+ def testObjectIds_d(self):
+ self.assertEqual(self.f.objectIds_d(), {'item': 1})
+
+ def testCheckId(self):
+ self.assertEqual(self.f._checkId('xyz'), None)
+ self.assertRaises(BadRequestException, self.f._checkId, 'item')
+ self.assertRaises(BadRequestException, self.f._checkId, 'REQUEST')
+
+ def testSetObject(self):
+ f2 = HBTreeFolder2('item2')
+ self.f._setObject(f2.id, f2)
+ self.assert_(self.f.has_key('item2'))
+ self.assertEqual(self.f.objectCount(), 2)
+
+ def testWrapped(self):
+ # Verify that the folder returns wrapped versions of objects.
+ base = self.getBase(self.f._getOb('item'))
+ self.assert_(self.f._getOb('item') is not base)
+ self.assert_(self.f['item'] is not base)
+ self.assert_(self.f.get('item') is not base)
+ self.assert_(self.getBase(self.f._getOb('item')) is base)
+
+ def testGenerateId(self):
+ ids = {}
+ for n in range(10):
+ ids[self.f.generateId()] = 1
+ self.assertEqual(len(ids), 10) # All unique
+ for id in ids.keys():
+ self.f._checkId(id) # Must all be valid
+
+ def testGenerateIdDenialOfServicePrevention(self):
+ for n in range(10):
+ item = Folder()
+ item.id = 'item%d' % n
+ self.f._setOb(item.id, item)
+ self.f.generateId('item', rand_ceiling=20) # Shouldn't be a problem
+ self.assertRaises(ExhaustedUniqueIdsError,
+ self.f.generateId, 'item', rand_ceiling=9)
+
+ def testReplace(self):
+ old_f = Folder()
+ old_f.id = 'item'
+ inner_f = HBTreeFolder2('inner')
+ old_f._setObject(inner_f.id, inner_f)
+ self.ff._populateFromFolder(old_f)
+ self.assertEqual(self.ff.objectCount(), 1)
+ self.assert_(self.ff.has_key('inner'))
+ self.assertEqual(self.getBase(self.ff._getOb('inner')), inner_f)
+
+ def testObjectListing(self):
+ f2 = HBTreeFolder2('somefolder')
+ self.f._setObject(f2.id, f2)
+ # Hack in an absolute_url() method that works without context.
+ self.f.absolute_url = lambda: ''
+ info = self.f.getBatchObjectListing()
+ self.assertEqual(info['b_start'], 1)
+ self.assertEqual(info['b_end'], 2)
+ self.assertEqual(info['prev_batch_url'], '')
+ self.assertEqual(info['next_batch_url'], '')
+ self.assert_(info['formatted_list'].find('</select>') > 0)
+ self.assert_(info['formatted_list'].find('item') > 0)
+ self.assert_(info['formatted_list'].find('somefolder') > 0)
+
+ # Ensure batching is working.
+ info = self.f.getBatchObjectListing({'b_count': 1})
+ self.assertEqual(info['b_start'], 1)
+ self.assertEqual(info['b_end'], 1)
+ self.assertEqual(info['prev_batch_url'], '')
+ self.assert_(info['next_batch_url'] != '')
+ self.assert_(info['formatted_list'].find('item') > 0)
+ self.assert_(info['formatted_list'].find('somefolder') < 0)
+
+ info = self.f.getBatchObjectListing({'b_start': 2})
+ self.assertEqual(info['b_start'], 2)
+ self.assertEqual(info['b_end'], 2)
+ self.assert_(info['prev_batch_url'] != '')
+ self.assertEqual(info['next_batch_url'], '')
+ self.assert_(info['formatted_list'].find('item') < 0)
+ self.assert_(info['formatted_list'].find('somefolder') > 0)
+
+ def testObjectListingWithSpaces(self):
+ # The option list must use value attributes to preserve spaces.
+ name = " some folder "
+ f2 = HBTreeFolder2(name)
+ self.f._setObject(f2.id, f2)
+ self.f.absolute_url = lambda: ''
+ info = self.f.getBatchObjectListing()
+ expect = '<option value="%s">%s</option>' % (name, name)
+ self.assert_(info['formatted_list'].find(expect) > 0)
+
+ def testCleanup(self):
+ self.assert_(self.f._cleanup())
+ key = TrojanKey('a')
+ self.f._htree[key] = 'b'
+ self.assert_(self.f._cleanup())
+ key.value = 'z'
+
+ # With a key in the wrong place, there should now be damage.
+ self.assert_(not self.f._cleanup())
+ # Now it's fixed.
+ self.assert_(self.f._cleanup())
+ # Verify the management interface also works,
+ # but don't test return values.
+ self.f.manage_cleanup()
+ key.value = 'a'
+ self.f.manage_cleanup()
+
+
+class TrojanKey:
+ """Pretends to be a consistent, immutable, humble citizen...
+
+ then sweeps the rug out from under the HBTree.
+ """
+ def __init__(self, value):
+ self.value = value
+
+ def __cmp__(self, other):
+ return cmp(self.value, other)
+
+ def __hash__(self):
+ return hash(self.value)
+
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(HBTreeFolder2Tests),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Propchange: erp5/trunk/products/HBTreeFolder2/tests/testHBTreeFolder2.py
------------------------------------------------------------------------------
svn:executable = *
Added: erp5/trunk/products/HBTreeFolder2/version.txt
URL: http://svn.erp5.org/erp5/trunk/products/HBTreeFolder2/version.txt?rev=16118&view=auto
==============================================================================
--- erp5/trunk/products/HBTreeFolder2/version.txt (added)
+++ erp5/trunk/products/HBTreeFolder2/version.txt Thu Sep 6 15:53:47 2007
@@ -1,0 +1,1 @@
+HBTreeFolder2-1.0.0
Propchange: erp5/trunk/products/HBTreeFolder2/version.txt
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: erp5/trunk/products/HBTreeFolder2/version.txt
------------------------------------------------------------------------------
svn:executable = *
More information about the Erp5-report
mailing list