From 26891225a79886c4bfcbdd03d1b1da79a612f702 Mon Sep 17 00:00:00 2001 From: Kazuhiko Shiozaki <kazuhiko@nexedi.com> Date: Mon, 17 Feb 2014 14:09:08 +0100 Subject: [PATCH] use minitage.recipe.egg more to manage our patched eggs. --- .../egg-patch/Acquisition/aq_dynamic.patch | 292 ++++++++++++++++++ .../Products.DCWorkflow/workflow_method.patch | 120 +++++++ stack/erp5/buildout.cfg | 21 +- 3 files changed, 426 insertions(+), 7 deletions(-) create mode 100644 component/egg-patch/Acquisition/aq_dynamic.patch create mode 100644 component/egg-patch/Products.DCWorkflow/workflow_method.patch diff --git a/component/egg-patch/Acquisition/aq_dynamic.patch b/component/egg-patch/Acquisition/aq_dynamic.patch new file mode 100644 index 000000000..deb801fcf --- /dev/null +++ b/component/egg-patch/Acquisition/aq_dynamic.patch @@ -0,0 +1,292 @@ +diff -uNr Acquisition-2.13.8/src/Acquisition/_Acquisition.c Acquisition-2.13.8nxd001/src/Acquisition/_Acquisition.c +--- Acquisition-2.13.8/src/Acquisition/_Acquisition.c 2011-06-11 17:19:14.000000000 +0200 ++++ Acquisition-2.13.8nxd001/src/Acquisition/_Acquisition.c 2013-10-31 16:24:55.665085888 +0100 +@@ -448,6 +448,64 @@ + } + + static PyObject * ++Wrapper_GetAttr(PyObject *self, PyObject *attr_name, PyObject *orig) ++{ ++ /* This function retrieves an attribute from an object by PyObject_GetAttr. ++ ++ The main difference between Wrapper_GetAttr and PyObject_GetAttr is that ++ Wrapper_GetAttr calls _aq_dynamic to generate an attribute dynamically, if ++ the attribute is not found. ++ */ ++ PyObject *r, *v, *tb; ++ PyObject *d, *m; ++ PyObject *o; ++ ++ if (isWrapper (self)) ++ o = WRAPPER(self)->obj; ++ else ++ o = self; ++ ++ /* Try to get an attribute in the normal way first. */ ++ r = PyObject_GetAttr(o, attr_name); ++ if (r) ++ return r; ++ ++ /* If an unexpected error happens, return immediately. */ ++ PyErr_Fetch(&r,&v,&tb); ++ if (r != PyExc_AttributeError) ++ { ++ PyErr_Restore(r,v,tb); ++ return NULL; ++ } ++ ++ /* Try to get _aq_dynamic. */ ++ m = PyObject_GetAttrString(o, "_aq_dynamic"); ++ if (! m) { ++ PyErr_Restore(r,v,tb); ++ return NULL; ++ } ++ ++ /* Call _aq_dynamic in the context of the original acquisition wrapper. */ ++ if (PyECMethod_Check(m) && PyECMethod_Self(m)==o) ++ ASSIGN(m,PyECMethod_New(m,OBJECT(self))); ++ else if (has__of__(m)) ASSIGN(m,__of__(m,OBJECT(self))); ++ d = PyObject_CallFunction(m, "O", attr_name); ++ Py_DECREF(m); ++ ++ /* In the case of None, assume that the attribute is not found. */ ++ if (d == Py_None) { ++ Py_DECREF(d); ++ PyErr_Restore(r,v,tb); ++ return NULL; ++ } ++ ++ Py_XDECREF(r); ++ Py_XDECREF(v); ++ Py_XDECREF(tb); ++ return d; ++} ++ ++static PyObject * + Wrapper_acquire(Wrapper *self, PyObject *oname, + PyObject *filter, PyObject *extra, PyObject *orig, + int explicit, int containment); +@@ -545,8 +603,8 @@ + Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb); + r=NULL; + } +- /* normal attribute lookup */ +- else if ((r=PyObject_GetAttr(self->obj,oname))) ++ /* Give _aq_dynamic a chance, then normal attribute lookup */ ++ else if ((r=Wrapper_GetAttr(OBJECT(self),oname,orig))) + { + if (r==Acquired) + { +@@ -670,7 +728,7 @@ + Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb); + r=NULL; + +- if ((r=PyObject_GetAttr(self->container,oname))) { ++ if ((r=Wrapper_GetAttr(self->container,oname,orig))) { + if (r == Acquired) { + Py_DECREF(r); + } +@@ -707,7 +765,7 @@ + Wrapper_getattro(Wrapper *self, PyObject *oname) + { + if (self->obj || self->container) +- return Wrapper_findattr(self, oname, NULL, NULL, NULL, 1, 1, 0, 0); ++ return Wrapper_findattr(self, oname, NULL, NULL, OBJECT(self), 1, 1, 0, 0); + + /* Maybe we are getting initialized? */ + return Py_FindAttr(OBJECT(self),oname); +@@ -724,7 +782,7 @@ + return Py_FindAttr(OBJECT(self),oname); + + if (self->obj || self->container) +- return Wrapper_findattr(self, oname, NULL, NULL, NULL, 1, 0, 0, 0); ++ return Wrapper_findattr(self, oname, NULL, NULL, OBJECT(self), 1, 0, 0, 0); + + /* Maybe we are getting initialized? */ + return Py_FindAttr(OBJECT(self),oname); +diff -uNr Acquisition-2.13.8/src/Acquisition/test_dynamic_acquisition.py Acquisition-2.13.8nxd001/src/Acquisition/test_dynamic_acquisition.py +--- Acquisition-2.13.8/src/Acquisition/test_dynamic_acquisition.py 1970-01-01 01:00:00.000000000 +0100 ++++ Acquisition-2.13.8nxd001/src/Acquisition/test_dynamic_acquisition.py 2013-10-31 16:24:55.665085888 +0100 +@@ -0,0 +1,160 @@ ++############################################################################## ++# ++# Copyright (c) 1996-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 Acquisition ++ ++def checkContext(self, o): ++ # Python equivalent to aq_inContextOf ++ from Acquisition import aq_base, aq_parent, aq_inner ++ subob = self ++ o = aq_base(o) ++ while 1: ++ if aq_base(subob) is o: ++ return True ++ self = aq_inner(subob) ++ if self is None: break ++ subob = aq_parent(self) ++ if subob is None: break ++ return False ++ ++class B(Acquisition.Implicit): ++ color='red' ++ ++ def __init__(self, name='b'): ++ self.name = name ++ ++ def _aq_dynamic(self, attr): ++ if attr == 'bonjour': return None ++ ++ def dynmethod(): ++ chain = ' <- '.join(repr(obj) for obj in Acquisition.aq_chain(self)) ++ print repr(self) + '.' + attr ++ print 'chain:', chain ++ ++ return dynmethod ++ ++ def __repr__(self): ++ return "%s(%r)" % (self.__class__.__name__, self.name) ++ ++class A(Acquisition.Implicit): ++ ++ def __init__(self, name='a'): ++ self.name = name ++ ++ def hi(self): ++ print self, self.color ++ ++ def _aq_dynamic(self, attr): ++ return None ++ ++ def __repr__(self): ++ return "%s(%r)" % (self.__class__.__name__, self.name) ++ ++def test_dynamic(): ++ r''' ++ The _aq_dynamic functionality allows an object to dynamically provide an ++ attribute. ++ ++ If an object doesn't have an attribute, Acquisition checks to see if the ++ object has a _aq_dynamic method, which is then called. It is functionally ++ equivalent to __getattr__, but _aq_dynamic is called with 'self' as the ++ acquisition wrapped object where as __getattr__ is called with self as the ++ unwrapped object. ++ ++ Let's see how this works. In the examples below, the A class defines ++ '_aq_dynamic', but returns 'None' for all attempts, which means that no new ++ attributes should be generated dynamically. It also doesn't define 'color' ++ attribute, even though it uses it in the 'hi' method. ++ ++ >>> A().hi() ++ Traceback (most recent call last): ++ ... ++ AttributeError: color ++ ++ The class B, on the other hand, generates all attributes dynamically, ++ except if it is called 'bonjour'. ++ ++ First we need to check that, even if an object provides '_aq_dynamic', ++ "regular" Aquisition attribute access should still work: ++ ++ >>> b=B() ++ >>> b.a=A() ++ >>> b.a.hi() ++ A('a') red ++ >>> b.a.color='green' ++ >>> b.a.hi() ++ A('a') green ++ ++ Now, let's see some dynamically generated action. B does not define a ++ 'salut' method, but remember that it dynamically generates a method for ++ every attribute access: ++ ++ >>> b.a.salut() ++ B('b').salut ++ chain: B('b') ++ ++ >>> a=A('a1') ++ >>> a.b=B('b1') ++ >>> a.b.salut() ++ B('b1').salut ++ chain: B('b1') <- A('a1') ++ ++ >>> b.a.bonjour() ++ Traceback (most recent call last): ++ ... ++ AttributeError: bonjour ++ ++ >>> a.b.bonjour() ++ Traceback (most recent call last): ++ ... ++ AttributeError: bonjour ++ ++ ''' ++ ++def test_wrapper_comparissons(): ++ r''' ++ ++ Test wrapper comparisons in presence of _aq_dynamic ++ ++ >>> b=B() ++ >>> b.a=A() ++ >>> foo = b.a ++ >>> bar = b.a ++ >>> assert( foo == bar ) ++ >>> c = A('c') ++ >>> b.c = c ++ >>> b.c.d = c ++ >>> b.c.d == c ++ True ++ >>> b.c.d == b.c ++ True ++ >>> b.c == c ++ True ++ ++ Test contextuality in presence of _aq_dynamic ++ ++ >>> checkContext(b.c, b) ++ True ++ >>> checkContext(b.c, b.a) ++ False ++ ++ >>> assert b.a.aq_inContextOf(b) ++ >>> assert b.c.aq_inContextOf(b) ++ >>> assert b.c.d.aq_inContextOf(b) ++ >>> assert b.c.d.aq_inContextOf(c) ++ >>> assert b.c.d.aq_inContextOf(b.c) ++ >>> assert not b.c.aq_inContextOf(foo) ++ >>> assert not b.c.aq_inContextOf(b.a) ++ >>> assert not b.a.aq_inContextOf('somestring') ++''' ++ +diff -uNr Acquisition-2.13.8/src/Acquisition/tests.py Acquisition-2.13.8nxd001/src/Acquisition/tests.py +--- Acquisition-2.13.8/src/Acquisition/tests.py 2011-06-11 17:09:38.000000000 +0200 ++++ Acquisition-2.13.8nxd001/src/Acquisition/tests.py 2013-10-31 16:24:55.669085888 +0100 +@@ -2552,6 +2552,7 @@ + def test_suite(): + return unittest.TestSuite(( + DocTestSuite(), ++ DocTestSuite('Acquisition.test_dynamic_acquisition'), + DocFileSuite('README.txt', package='Acquisition'), + unittest.makeSuite(TestParent), + unittest.makeSuite(TestAcquire), +diff -uNr Acquisition-2.13.8/src/Acquisition.egg-info/SOURCES.txt Acquisition-2.13.8nxd001/src/Acquisition.egg-info/SOURCES.txt +--- Acquisition-2.13.8/src/Acquisition.egg-info/SOURCES.txt 2011-06-11 17:21:18.000000000 +0200 ++++ Acquisition-2.13.8nxd001/src/Acquisition.egg-info/SOURCES.txt 2013-10-31 16:24:55.669085888 +0100 +@@ -15,6 +15,7 @@ + src/Acquisition/_Acquisition.c + src/Acquisition/__init__.py + src/Acquisition/interfaces.py ++src/Acquisition/test_dynamic_acquisition.py + src/Acquisition/tests.py + src/Acquisition.egg-info/PKG-INFO + src/Acquisition.egg-info/SOURCES.txt diff --git a/component/egg-patch/Products.DCWorkflow/workflow_method.patch b/component/egg-patch/Products.DCWorkflow/workflow_method.patch new file mode 100644 index 000000000..cdf243ab9 --- /dev/null +++ b/component/egg-patch/Products.DCWorkflow/workflow_method.patch @@ -0,0 +1,120 @@ +diff -uNr Products.DCWorkflow-2.2.4/Products/DCWorkflow/DCWorkflow.py Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/DCWorkflow.py +--- Products.DCWorkflow-2.2.4/Products/DCWorkflow/DCWorkflow.py 2011-11-01 18:55:01.000000000 +0100 ++++ Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/DCWorkflow.py 2013-10-31 16:42:05.021141352 +0100 +@@ -40,6 +40,7 @@ + from Products.DCWorkflow.permissions import ManagePortal + from Products.DCWorkflow.Transitions import TRIGGER_AUTOMATIC + from Products.DCWorkflow.Transitions import TRIGGER_USER_ACTION ++from Products.DCWorkflow.Transitions import TRIGGER_WORKFLOW_METHOD + from Products.DCWorkflow.utils import Message as _ + from Products.DCWorkflow.utils import modifyRolesForGroup + from Products.DCWorkflow.utils import modifyRolesForPermission +@@ -281,6 +282,52 @@ + raise Unauthorized(action) + self._changeStateOf(ob, tdef, kw) + ++ security.declarePrivate('isWorkflowMethodSupported') ++ def isWorkflowMethodSupported(self, ob, method_id): ++ ''' ++ Returns a true value if the given workflow method ++ is supported in the current state. ++ ''' ++ sdef = self._getWorkflowStateOf(ob) ++ if sdef is None: ++ return 0 ++ if method_id in sdef.transitions: ++ tdef = self.transitions.get(method_id, None) ++ if (tdef is not None and ++ tdef.trigger_type == TRIGGER_WORKFLOW_METHOD and ++ self._checkTransitionGuard(tdef, ob)): ++ return 1 ++ return 0 ++ ++ security.declarePrivate('wrapWorkflowMethod') ++ def wrapWorkflowMethod(self, ob, method_id, func, args, kw): ++ ''' ++ Allows the user to request a workflow action. This method ++ must perform its own security checks. ++ ''' ++ sdef = self._getWorkflowStateOf(ob) ++ if sdef is None: ++ raise WorkflowException, 'Object is in an undefined state' ++ if method_id not in sdef.transitions: ++ raise Unauthorized(method_id) ++ tdef = self.transitions.get(method_id, None) ++ if tdef is None or tdef.trigger_type != TRIGGER_WORKFLOW_METHOD: ++ raise WorkflowException, ( ++ 'Transition %s is not triggered by a workflow method' ++ % method_id) ++ if not self._checkTransitionGuard(tdef, ob): ++ raise Unauthorized(method_id) ++ res = func(*args, **kw) ++ try: ++ self._changeStateOf(ob, tdef) ++ except ObjectDeleted: ++ # Re-raise with a different result. ++ raise ObjectDeleted(res) ++ except ObjectMoved, ex: ++ # Re-raise with a different result. ++ raise ObjectMoved(ex.getNewObject(), res) ++ return res ++ + security.declarePrivate('isInfoSupported') + def isInfoSupported(self, ob, name): + ''' +diff -uNr Products.DCWorkflow-2.2.4/Products/DCWorkflow/dtml/transition_properties.dtml Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/dtml/transition_properties.dtml +--- Products.DCWorkflow-2.2.4/Products/DCWorkflow/dtml/transition_properties.dtml 2011-11-01 18:55:01.000000000 +0100 ++++ Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/dtml/transition_properties.dtml 2013-10-31 16:42:05.021141352 +0100 +@@ -56,6 +56,16 @@ + </tr> + + <tr> ++<th></th> ++<td> ++<dtml-let checked="trigger_type==2 and 'checked' or ' '"> ++<input type="radio" name="trigger_type" value="2" &dtml-checked; /> ++Initiated by WorkflowMethod ++</dtml-let> ++</td> ++</tr> ++ ++<tr> + <th align="left">Script (before)</th> + <td> + <select name="script_name"> +diff -uNr Products.DCWorkflow-2.2.4/Products/DCWorkflow/dtml/transitions.dtml Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/dtml/transitions.dtml +--- Products.DCWorkflow-2.2.4/Products/DCWorkflow/dtml/transitions.dtml 2011-11-01 18:55:01.000000000 +0100 ++++ Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/dtml/transitions.dtml 2013-10-31 16:42:05.021141352 +0100 +@@ -17,7 +17,8 @@ + <td> + Destination state: <code><dtml-if new_state_id>&dtml-new_state_id;<dtml-else>(Remain in state)</dtml-if></code> <br /> + Trigger: <dtml-var expr="(trigger_type == 0 and 'Automatic') or +- (trigger_type == 1 and 'User action')"> ++ (trigger_type == 1 and 'User action') or ++ (trigger_type == 2 and 'WorkflowMethod')"> + <br /> + <dtml-if script_name> + Script (before): &dtml-script_name; +diff -uNr Products.DCWorkflow-2.2.4/Products/DCWorkflow/exportimport.py Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/exportimport.py +--- Products.DCWorkflow-2.2.4/Products/DCWorkflow/exportimport.py 2011-11-01 18:55:01.000000000 +0100 ++++ Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/exportimport.py 2013-10-31 16:42:09.221141578 +0100 +@@ -34,7 +34,7 @@ + from Products.GenericSetup.interfaces import ISetupEnviron + from Products.GenericSetup.utils import BodyAdapterBase + +-TRIGGER_TYPES = ( 'AUTOMATIC', 'USER' ) ++TRIGGER_TYPES = ( 'AUTOMATIC', 'USER', 'METHOD' ) + _FILENAME = 'workflows.xml' + + +diff -uNr Products.DCWorkflow-2.2.4/Products/DCWorkflow/Transitions.py Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/Transitions.py +--- Products.DCWorkflow-2.2.4/Products/DCWorkflow/Transitions.py 2011-11-01 18:55:01.000000000 +0100 ++++ Products.DCWorkflow-2.2.4nxd001/Products/DCWorkflow/Transitions.py 2013-10-31 16:42:12.389141749 +0100 +@@ -31,6 +31,7 @@ + + TRIGGER_AUTOMATIC = 0 + TRIGGER_USER_ACTION = 1 ++TRIGGER_WORKFLOW_METHOD = 2 + + + class TransitionDefinition (SimpleItem): diff --git a/stack/erp5/buildout.cfg b/stack/erp5/buildout.cfg index 261cbda13..29ca07116 100644 --- a/stack/erp5/buildout.cfg +++ b/stack/erp5/buildout.cfg @@ -417,7 +417,16 @@ initialization = [patched-eggs] recipe = minitage.recipe.egg -eggs = ZODB3 +eggs = + Acquisition + Products.DCWorkflow + ZODB3 +Acquisition-patches = ${:_profile_base_location_}/../../component/egg-patch/Acquisition/aq_dynamic.patch +Acquisition-patch-options = -p1 +Acquisition-patch-binary = ${patch:location}/bin/patch +Products.DCWorkflow-patches = ${:_profile_base_location_}/../../component/egg-patch/Products.DCWorkflow/workflow_method.patch +Products.DCWorkflow-patch-options = -p1 +Products.DCWorkflow-patch-binary = ${patch:location}/bin/patch ZODB3-patches = ${:_profile_base_location_}/../../component/egg-patch/ZODB3-3.10.5.patch ZODB3-patch-options = -p1 ZODB3-patch-binary = ${patch:location}/bin/patch @@ -570,9 +579,10 @@ scripts = zodbpack [versions] -# pin Acquisition and Products.DCWorkflow to Nexedi flavour of eggs -Acquisition = 2.13.8nxd001 -Products.DCWorkflow = 2.2.4nxd001 +# patched eggs +Acquisition = 2.13.8-ZMinitagePatched-AqDynamic +Products.DCWorkflow = 2.2.4-ZMinitagePatched-WorkflowMethod +ZODB3 = 3.10.5-ZMinitagePatched-ZODB33105 # specify dev version to be sure that an old released version is not used cloudooo = 1.2.5-dev @@ -625,9 +635,6 @@ Products.CMFDefault = 2.2.3 Products.CMFTopic = 2.2.1 Products.CMFUid = 2.2.1 -# patched eggs -ZODB3 = 3.10.5-ZMinitagePatched-ZODB33105 - # newer version requires zope.traversing>=4.0.0a2. zope.app.appsetup = 3.16.0 -- 2.30.9