From e8a6bdf98e196f3e1acd5de56939f6b6b4ef2962 Mon Sep 17 00:00:00 2001 From: Julien Muchembled <jm@nexedi.com> Date: Tue, 12 Jul 2011 16:26:53 +0200 Subject: [PATCH] New methods to efficiently run a script on all objects returned by a catalog search Update Folder.callMethodOnObjectList so that an active proces can collect results. --- product/ERP5Catalog/CatalogTool.py | 45 ++++++++++++++++++++++++++++++ product/ERP5Type/Core/Folder.py | 19 ++++++------- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/product/ERP5Catalog/CatalogTool.py b/product/ERP5Catalog/CatalogTool.py index 79f5f95634..617218a889 100644 --- a/product/ERP5Catalog/CatalogTool.py +++ b/product/ERP5Catalog/CatalogTool.py @@ -956,4 +956,49 @@ class CatalogTool (UniqueObject, ZCatalog, CMFCoreCatalogTool, ActiveObject): return aq_base_name return aq_base_name + def _searchAndActivate(self, method_id, method_args=(), method_kw={}, + activate_kw={}, min_uid=None, **kw): + """Search the catalog and run a script by activity on all found objects + + This method is configurable (via 'packet_size' & 'activity_count' + parameters) so that it can work efficiently with databases of any size. + + 'activate_kw' may specify an active process to collect results. + Note however, we don't use Base_makeActiveResult so you're likely to get + ConflictError at the beginning. You could avoid this by making sure + 'result_list' is already initialized on the active process. + """ + catalog_kw = dict(kw) + packet_size = catalog_kw.pop('packet_size', 30) + limit = packet_size * catalog_kw.pop('activity_count', 100) + if min_uid: + catalog_kw['uid'] = {'query': min_uid, 'range': 'nlt'} + if catalog_kw.pop('restricted', False): + search = self + else: + search = self.unrestrictedSearchResults + r = search(sort_on=(('uid','ascending'),), limit=limit, **catalog_kw) + result_count = len(r) + if result_count: + if result_count == limit: + tag = activate_kw.get('tag') + if not tag: + activate_kw['tag'] = tag = 'searchAndActivate_%r' % time.time() + _tag = '%s_%s' % (tag, min_uid) + self.activate(tag=tag, after_tag=_tag, activity='SQLQueue') \ + ._searchAndActivate(method_id,method_args, method_kw, + dict(activate_kw), r[-1].getUid(), **kw) + activate_kw['tag'] = _tag + r = [x.getPath() for x in r] + r.sort() + activate = self.getPortalObject().portal_activities.activate + for i in xrange(0, result_count, packet_size): + activate(activity='SQLQueue', **activate_kw).callMethodOnObjectList( + r[i:i+packet_size], method_id, *method_args, **method_kw) + + security.declarePublic('searchAndActivate') + def searchAndActivate(self, *args, **kw): + """Restricted version of _searchAndActivate""" + return self._searchAndActivate(restricted=True, *args, **kw) + InitializeClass(CatalogTool) diff --git a/product/ERP5Type/Core/Folder.py b/product/ERP5Type/Core/Folder.py index 562c02229b..803ee5c137 100644 --- a/product/ERP5Type/Core/Folder.py +++ b/product/ERP5Type/Core/Folder.py @@ -1524,21 +1524,20 @@ class Folder(CopyContainer, CMFBTreeFolder, CMFHBTreeFolder, Base, FolderMixIn, object.manage_beforeDelete(object, self) self._delOb(id) - security.declareProtected( Permissions.ManagePortal, 'callMethodOnObjectList' ) + security.declareProtected(Permissions.ManagePortal, 'callMethodOnObjectList') def callMethodOnObjectList(self, object_path_list, method_id, *args, **kw): """ - Very usefull if we want to activate the call of a method + Very useful if we want to activate the call of a method on many objects at a time. Like this we could prevent creating - too much acitivities at a time, and we may have only the path - + too many activities at a time, and we may have only the path """ - portal = self.getPortalObject() + result_list = [] + traverse = self.getPortalObject().unrestrictedTraverse for object_path in object_path_list: - current_object = portal.unrestrictedTraverse(object_path) - method = getattr(current_object, method_id, None) - if method is None: - raise ValueError, "The method %s was not found" % method_id - method(*args, **kw) + result = getattr(traverse(object_path), method_id)(*args, **kw) + if type(result) in (list, tuple): + result_list += result + return result_list def _verifyObjectPaste(self, object, validate_src=1): # To paste in an ERP5Type folder, we need to check 'Add permission' -- 2.30.9