From 3ab70b57e7cc1c858e6e10823cd98f5697417b1d Mon Sep 17 00:00:00 2001 From: Jean-Paul Smets <jp@nexedi.com> Date: Fri, 9 Oct 2009 15:11:27 +0000 Subject: [PATCH] Refactored version of Interactor with compoenent sysytem git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@29545 20353a03-c40f-0410-a6d1-a30d3c3de9de --- product/ERP5Type/InitGenerator.py | 30 +++- product/ERP5Type/Interactor.py | 251 ------------------------------ product/ERP5Type/Utils.py | 31 +++- product/ERP5Type/__init__.py | 15 +- 4 files changed, 68 insertions(+), 259 deletions(-) delete mode 100644 product/ERP5Type/Interactor.py diff --git a/product/ERP5Type/InitGenerator.py b/product/ERP5Type/InitGenerator.py index e8b44b4777..7351b3f2d6 100644 --- a/product/ERP5Type/InitGenerator.py +++ b/product/ERP5Type/InitGenerator.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved. @@ -35,6 +36,10 @@ from zLOG import LOG global product_document_registry product_document_registry = [] +global product_interactor_registry +product_interactor_registry = [] +global interactor_class_id_registry +interactor_class_id_registry = {} def getProductDocumentPathList(): result = product_document_registry @@ -45,7 +50,16 @@ def InitializeDocument(document_class, document_path=None): global product_document_registry # Register class in ERP5Type.Document product_document_registry.append(((document_class, document_path))) - #LOG('InitializeDocument', 0, document_class.__name__) + +def getProductInteractorPathList(): + result = product_interactor_registry + result.sort() + return result + +def InitializeInteractor(interactor_class, interactor_path=None): + global product_interactor_registry + # Register class in ERP5Type.Interactor + product_interactor_registry.append(((interactor_class, interactor_path))) def initializeProductDocumentRegistry(): from Utils import importLocalDocument @@ -56,3 +70,17 @@ def initializeProductDocumentRegistry(): #LOG('Added product document to ERP5Type repository: %s (%s)' % (class_id, document_path), 0, '') #print 'Added product document to ERP5Type repository: %s (%s)' % (class_id, document_path) +def initializeProductInteractorRegistry(): + from Utils import importLocalInteractor + for (class_id, interactor_path) in product_interactor_registry: + if class_id != 'Interactor': # Base class can not be global and placeless + importLocalInteractor(class_id, path=interactor_path) + +def registerInteractorClass(class_id, klass): + global interactor_class_id_registry + interactor_class_id_registry[class_id] = klass + +def installInteractorClassRegistry(): + global interactor_class_id_registry + for class_id, klass in interactor_class_id_registry.items(): + klass().install() diff --git a/product/ERP5Type/Interactor.py b/product/ERP5Type/Interactor.py deleted file mode 100644 index 03f42a8d81..0000000000 --- a/product/ERP5Type/Interactor.py +++ /dev/null @@ -1,251 +0,0 @@ -from MethodObject import Method -from Products.ERP5Type.Base import _aq_reset -""" - Current implementation uses callable objects. - Using decorator would be more modern and consistent with - recent evolution of python. But we have 2.3 ERP5 users - so we should, at least, provide both implementations. - - Code structure should be changed so that Interactors - because a new "type" of ERP5 class such Document - with a modular plugin structure. - - TODO: multiple instances of interactors could - use different parameters. This way, interactions - can be defined on "instances" or can be - made generic. -""" - - -class InteractorMethodCall: - """ - Method's wrapper used to keep arguments passed, in order to retrieve them - from before and after scripts if needed. - """ - - def __init__(self, method, instance, *args, **kw): - self.instance = instance - self.args = args - self.kw = kw - self.method = method - - def __call__(self): - # We use self.__dict__['instance'] to prevent inserting the - # InteractorMethodCall instance in the acquisition chain - # which has some side effects - return self.method(self.__dict__['instance'], *self.args, **self.kw) - -class InteractorMethod(Method): - - def __init__(self, method): - self.after_action_list = [] - self.before_action_list = [] - self.method = method - self.func_code = method.func_code - self.func_defaults = method.func_defaults - self.__name__ = method.__name__ - - def registerBeforeAction(self, action, args, kw): - self.before_action_list.append((action, args, kw)) - - def registerAfterAction(self, action, args, kw): - self.after_action_list.append((action, args, kw)) - - def __call__(self, instance, *args, **kw): - method_call_object = InteractorMethodCall(self.method, instance, *args, **kw) - for action, args, kw in self.before_action_list: - action(method_call_object, *args, **kw) - result = method_call_object() - for action, args, kw in self.after_action_list: - action(method_call_object, *args, **kw) - return result - -class InteractorSource: - - def __init__(self, method): - """ - Register method - """ - self.method = method - - def doAfter(self, action, *args, **kw): - """ - """ - im_class = self.method.im_class - if not isinstance(self.method, InteractorMethod): - # Turn this into an InteractorMethod - interactor_method = InteractorMethod(self.method) - setattr(im_class, self.method.__name__, interactor_method) - self.method = interactor_method - # Register the action - self.method.registerAfterAction(action, args, kw) - -class Interactor: - def install(self): - raise NotImplementedError - - def uninstall(self): - raise NotImplementedError - - # Interaction implementation - def on(self, method): - """ - Parameters may hold predicates ? - no need - use InteractorMethodCall and decide on action - """ - return InteractorSource(method) - - -class AqDynamicInteractor(Interactor): - """This base interactor will reset _aq_dynamic method cache, to regenerate - new accessors. - """ - def resetAqDynamic(self, *args, **kw): - """ - Reset _aq_dynamic - """ - _aq_reset() - - -class WorkflowToolInteractor(AqDynamicInteractor): - """This interactor reset aq_dynamic method cache whenever workflow - associations are changed. - """ - def install(self): - from Products.CMFCore.WorkflowTool import WorkflowTool - self.on(WorkflowTool.manage_changeWorkflows).doAfter(self.resetAqDynamic) - - -class DCWorkflowInteractor(AqDynamicInteractor): - """This interactor reset aq_dynamic method cache whenever a workflow - definition changes - """ - def install(self): - from Products.DCWorkflow.Transitions import Transitions - self.on(Transitions.addTransition).doAfter(self.resetAqDynamic) - self.on(Transitions.deleteTransitions).doAfter(self.resetAqDynamic) - - from Products.DCWorkflow.Transitions import TransitionDefinition - self.on(TransitionDefinition.setProperties).doAfter(self.resetAqDynamic) - - from Products.DCWorkflow.Variables import Variables - self.on(Variables.setStateVar).doAfter(self.resetAqDynamic) - -## # -## # Experimental part -## # -## class InteractionWorkflowAqDynamicInteractor(AqDynamicInteractor): - -## def install(self): -## """ -## Installs interactions -## """ -## from Products.ERP5.Interaction import InteractionDefinition -## self.on(InteractionDefinition.setProperties).doAfter(self.resetAqDynamic, 1, 2, toto="foo") -## self.on(InteractionDefinition.addVariable).doAfter(self.resetAqDynamic, 1, 2, toto="foo") - -## def uninstall(self): -## """ -## Uninstall interactions -## """ - -## # Interaction example - - -## class TypeInteractorExample(Interactor): -## def __init__(self, portal_type): -## self.portal_type = portal_type - -## def install(self): -## from Products.CMFCore.TypesTool import TypesTool -## self.on(TypesTool.manage_edit).doAfter(self.doSomething) - -## def doSomething(self, method_call_object): -## if self.portal_type == method_call_object.instance.portal_type: -## pass -## # do whatever - - -## class InteractorOfInteractor(Interactor): - -## def __init__(self, interactor): -## self.interactor = interactor - -## def install(self): -## self.on(interactor.doSomething).doAfter(self.doSomething) - -## def doSomething(self, method_call_object): -## pass - -## test = AqDynamicInteractor() -## test.install() - - -class FieldValueInteractor(Interactor): - - def install(self): - """ - Installs interactions - """ - from Products.Formulator.Field import ZMIField - from Products.ERP5Form.ProxyField import ProxyField - from Products.Formulator.Form import ZMIForm - self.on(ZMIField.manage_edit).doAfter(self.purgeFieldValueCache) - self.on(ZMIField.manage_edit_xmlrpc).doAfter(self.purgeFieldValueCache) - self.on(ZMIField.manage_tales).doAfter(self.purgeFieldValueCache) - self.on(ZMIField.manage_tales_xmlrpc).doAfter(self.purgeFieldValueCache) - self.on(ProxyField.manage_edit).doAfter(self.purgeFieldValueCache) - self.on(ProxyField.manage_edit_target).doAfter(self.purgeFieldValueCache) - self.on(ProxyField.manage_tales).doAfter(self.purgeFieldValueCache) - self.on(ZMIForm.manage_renameObject).doAfter(self.purgeFieldValueCache) - - def uninstall(self): - """ - Uninstall interactions - """ - - def purgeFieldValueCache(self, method_call_object): - """ - """ - from Products.ERP5Form import Form, ProxyField - Form.purgeFieldValueCache() - ProxyField.purgeFieldValueCache() - -class TypeInteractorExample(Interactor): - def __init__(self, portal_type): - self.portal_type = portal_type - - def install(self): - from Products.CMFCore.TypesTool import TypesTool - self.on(TypesTool.manage_edit).doAfter(self.doSomething) - - def doSomething(self, method_call_object): - if self.portal_type == method_call_object.instance.portal_type: - pass - # do whatever - -class InteractorOfInteractor(Interactor): - - def __init__(self, interactor): - self.interactor = interactor - - def install(self): - self.on(Interactor.doSomething).doAfter(self.doSomething) - - def doSomething(self, method_call_object): - pass - - -#interactor_of_interactor = InteractorOfInteractor(test) -#interactor_of_interactor.install() - - -# Install some interactors: - -WorkflowToolInteractor().install() -DCWorkflowInteractor().install() - -# This is used in ERP5Form and install method is called in ERP5Form -# Don't install this interactor here. -field_value_interactor = FieldValueInteractor() - diff --git a/product/ERP5Type/Utils.py b/product/ERP5Type/Utils.py index 76ed69caf0..52769c7a2b 100644 --- a/product/ERP5Type/Utils.py +++ b/product/ERP5Type/Utils.py @@ -319,7 +319,7 @@ def getTranslationStringWithContext(self, msg_id, context, context_id): # Globals initialization ##################################################### -from InitGenerator import InitializeDocument +from InitGenerator import InitializeDocument, InitializeInteractor, registerInteractorClass # List Regexp python_file_expr = re.compile("py$") @@ -355,7 +355,7 @@ def updateGlobals(this_module, global_hook, this_module._dtmldir = os.path.join( product_path, 'dtml' ) # Update PropertySheet Registry - for module_id in ('PropertySheet', 'interfaces', 'Constraint', ): + for module_id in ('PropertySheet', 'interfaces', 'Constraint'): path, module_id_list = getModuleIdList(product_path, module_id) if module_id == 'PropertySheet': import_method = importLocalPropertySheet @@ -383,6 +383,12 @@ def updateGlobals(this_module, global_hook, path, module_id_list = getModuleIdList(product_path, 'Document') for document in module_id_list: InitializeDocument(document, document_path=path) + + # Return interactor_class list + path, interactor_id_list = getModuleIdList(product_path, 'Interactor') + for interactor in interactor_id_list: + InitializeInteractor(interactor, interactor_path=path) + return module_id_list + core_module_id_list ##################################################### @@ -567,6 +573,20 @@ def importLocalConstraint(class_id, path = None): finally: f.close() +def importLocalInteractor(class_id, path=None): + import Products.ERP5Type.Interactor + if path is None: + instance_home = getConfiguration().instancehome + path = os.path.join(instance_home, "Interactor") + path = os.path.join(path, "%s.py" % class_id) + f = open(path) + try: + module = imp.load_source(class_id, path, f) + setattr(Products.ERP5Type.Interactor, class_id, getattr(module, class_id)) + registerInteractorClass(class_id, getattr(Products.ERP5Type.Interactor, class_id)) + finally: + f.close() + def getLocalExtensionList(): if not getConfiguration: return [] @@ -801,6 +821,7 @@ def importLocalDocument(class_id, document_path = None): import Products.ERP5Type.Document import Permissions import Products + if document_path is None: instance_home = getConfiguration().instancehome path = os.path.join(instance_home, "Document") @@ -934,6 +955,10 @@ def initializeLocalPropertySheetRegistry(): def initializeLocalConstraintRegistry(): initializeLocalRegistry("Constraint", importLocalConstraint) +def initializeLocalInteractorRegistry(): + initializeLocalRegistry("Interactor", importLocalInteractor) + + ##################################################### # Product initialization ##################################################### @@ -942,7 +967,7 @@ def initializeProduct( context, this_module, global_hook, document_module=None, - document_classes=None, + document_classes=None, # XXX - Never used - must be likely removed object_classes=None, portal_tools=None, content_constructors=None, diff --git a/product/ERP5Type/__init__.py b/product/ERP5Type/__init__.py index 96705f7aac..b6952dc745 100644 --- a/product/ERP5Type/__init__.py +++ b/product/ERP5Type/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- ############################################################################## # # Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved. @@ -29,9 +30,9 @@ ERP5Type is provides a RAD environment for Zope / CMF All ERP5 classes derive from ERP5Type """ + # Switch(es) for ongoing development which require single code base USE_BASE_TYPE = False -USE_INTERACTOR = False # Update ERP5 Globals import sys, Permissions, os @@ -99,9 +100,15 @@ def initialize( context ): # We should register local classes at some point from Products.ERP5Type.Utils import initializeLocalDocumentRegistry initializeLocalDocumentRegistry() - # Experimental Interactor - if USE_INTERACTOR: - import Interactor + # We can now setup global interactors + from Products.ERP5Type.InitGenerator import initializeProductInteractorRegistry + initializeProductInteractorRegistry() + # And local interactors + from Products.ERP5Type.Utils import initializeLocalInteractorRegistry + initializeLocalInteractorRegistry() + # We can now install all interactors + from Products.ERP5Type.InitGenerator import installInteractorClassRegistry + installInteractorClassRegistry() from AccessControl.SecurityInfo import allow_module from AccessControl.SecurityInfo import ModuleSecurityInfo -- 2.30.9