[Erp5-report] r39371 nicolas.dumazet - in /erp5/trunk/products/ERP5Type: ./ tests/
nobody at svn.erp5.org
nobody at svn.erp5.org
Wed Oct 20 08:43:40 CEST 2010
Author: nicolas.dumazet
Date: Wed Oct 20 08:43:40 2010
New Revision: 39371
URL: http://svn.erp5.org?rev=39371&view=rev
Log:
Portal type classes.
- All ERP5 objects now become instances of erp5.portal_type.**
Being an instance of a portal type does no longer only mean
"having a portal_type attribute", it now also means deriving from
a specific, ad-hoc Python class for this portal type.
- erp5.portal_type module is built dynamically and its objects
are classes subclassing the physical Document classes on disk.
- ERP5Type.Document fate:
+ classes previously stored here are gone
+ newTempXXX methods stay, and will work correctly. But a call
to such a method will require an BaseType object in
portal_types module.
+ other stuff is gone
- Temporary documents will be instances of erp5.temp_portal_type.*
All classes in this submodule subclass the respective
erp5.portal_type.* persistent class
- Documents that were created dynamically without a product path
(for instance, those created with ClassTool) are now stored
in a specific module, erp5.document.*
Migration after this revision should be handled automatically,
but updating beyond this point should nonetheless not be done
carelessly.
Expected changes in XML for business templates:
- Classpath of documents:
ERP5Type.Document.XXX -> erp5.portal_type.XXX
- new "type_class" attribute on Portal Type Objects (BaseType Documents)
Modified:
erp5/trunk/products/ERP5Type/Base.py
erp5/trunk/products/ERP5Type/ERP5Type.py
erp5/trunk/products/ERP5Type/Utils.py
erp5/trunk/products/ERP5Type/__init__.py
erp5/trunk/products/ERP5Type/tests/testMigration.py
Modified: erp5/trunk/products/ERP5Type/Base.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/Base.py?rev=39371&r1=39370&r2=39371&view=diff
==============================================================================
--- erp5/trunk/products/ERP5Type/Base.py [utf8] (original)
+++ erp5/trunk/products/ERP5Type/Base.py [utf8] Wed Oct 20 08:43:40 2010
@@ -830,7 +830,11 @@ class Base( CopyContainer,
cache_factory='erp5_ui_long'))
def _aq_key(self):
- return (self.portal_type, self.__class__)
+ klass_list = self.__class__.__mro__
+ i = 0
+ while klass_list[i].__module__ in ('erp5.portal_type', 'erp5.temp_portal_type'):
+ i += 1
+ return (self.portal_type, klass_list[i])
def _propertyMap(self):
""" Method overload - properties are now defined on the ptype """
@@ -854,7 +858,11 @@ class Base( CopyContainer,
Test purpose
"""
ptype = self.portal_type
- klass = self.__class__
+ klass_list = self.__class__.__mro__
+ i = 0
+ while klass_list[i].__module__ in ('erp5.portal_type', 'erp5.temp_portal_type'):
+ i += 1
+ klass = klass_list[i]
aq_key = (ptype, klass) # We do not use _aq_key() here for speed
initializePortalTypeDynamicProperties(self, klass, ptype, aq_key, \
self.getPortalObject())
@@ -866,7 +874,11 @@ class Base( CopyContainer,
# and default properties can be associated per portal type
# and per class. Other uses are possible (ex. WebSection).
ptype = self.portal_type
- klass = self.__class__
+ klass_list = self.__class__.__mro__
+ i = 0
+ while klass_list[i].__module__ in ('erp5.portal_type', 'erp5.temp_portal_type'):
+ i += 1
+ klass = klass_list[i]
aq_key = (ptype, klass) # We do not use _aq_key() here for speed
# If this is a portal_type property and everything is already defined
@@ -898,15 +910,6 @@ class Base( CopyContainer,
Base.aq_method_generating.append(aq_key)
try:
# Proceed with property generation
- if self.isTempObject() and len(klass.__bases__) == 1:
- # If self is a simple temporary object (e.g. not a composed one),
- # generate methods for the base document class rather than for the
- # temporary document class.
- # Otherwise, instances of the base document class would fail
- # in calling such methods, because they are not instances of
- # the temporary document class.
- klass = klass.__bases__[0]
-
# Generate class methods
initializeClassDynamicProperties(self, klass)
Modified: erp5/trunk/products/ERP5Type/ERP5Type.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/ERP5Type.py?rev=39371&r1=39370&r2=39371&view=diff
==============================================================================
--- erp5/trunk/products/ERP5Type/ERP5Type.py [utf8] (original)
+++ erp5/trunk/products/ERP5Type/ERP5Type.py [utf8] Wed Oct 20 08:43:40 2010
@@ -29,7 +29,7 @@ import Products
from Products.CMFCore.TypesTool import FactoryTypeInformation
from Products.CMFCore.Expression import Expression
from Products.CMFCore.exceptions import AccessControl_Unauthorized
-from Products.CMFCore.utils import _checkPermission, getToolByName
+from Products.CMFCore.utils import getToolByName
from Products.ERP5Type import interfaces, Constraint, Permissions, PropertySheet
from Products.ERP5Type.Base import getClassPropertyList
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
@@ -304,48 +304,24 @@ class ERP5TypeInformation(XMLObject,
"""
return default
- # The following 2 methods should not be used.
- _getFactoryMethod = deprecated(FactoryTypeInformation._getFactoryMethod)
- _constructInstance = deprecated(FactoryTypeInformation._constructInstance)
-
- def _queryFactoryMethod(self, container, temp_object=0):
- product = self.product
- factory = self.factory
- if not product or not factory:
- return ValueError('Product factory for %s was undefined'
- % self.getId())
- try:
- p = container.manage_addProduct[product]
- except AttributeError:
- pass
- else:
- if temp_object:
- factory = factory[:3] == 'add' and 'newTemp' + factory[3:] or ''
- m = getattr(p, factory, None)
- if m is None:
- return ValueError('Product factory for %s was invalid'
- % self.getId())
- if temp_object:
- return m
- permission = self.permission
- if permission:
- if _checkPermission(permission, container):
- return m
- else:
- try:
- # validate() can either raise Unauthorized or return 0 to
- # mean unauthorized.
- if getSecurityManager().validate(p, p, factory, m):
- return m
- except zExceptions_Unauthorized, e:
- return e
- return AccessControl_Unauthorized('Cannot create %s' % self.getId())
-
security.declarePublic('isConstructionAllowed')
def isConstructionAllowed(self, container):
"""Test if user is allowed to create an instance in the given container
"""
- return not isinstance(self._queryFactoryMethod(container), Exception)
+ permission = self.permission or 'Add portal content'
+ return getSecurityManager().checkPermission(permission, container)
+
+ security.declarePublic('constructTempInstance')
+ def constructTempInstance(self, container, id, *args, **kw ):
+ """
+ All ERP5Type.Document.newTempXXXX are constructTempInstance methods
+ """
+ # you should not pass temp_object to constructTempInstance
+ ob = self.constructInstance(container, id, temp_object=1, *args, **kw)
+ if container.isTempObject():
+ container._setObject(id, ob.aq_base)
+ return ob
+
security.declarePublic('constructInstance')
def constructInstance(self, container, id, created_by_builder=0,
@@ -356,10 +332,37 @@ class ERP5TypeInformation(XMLObject,
Call the init_script for the portal_type.
Returns the object.
"""
- m = self._queryFactoryMethod(container, temp_object)
- if isinstance(m, Exception):
- raise m
- ob = m(id, **kw)
+ if not temp_object and not self.isConstructionAllowed(container):
+ raise AccessControl_Unauthorized('Cannot create %s' % self.getId())
+
+ portal = container.getPortalObject()
+ klass = portal.portal_types.getPortalTypeClass(
+ self.getId(),
+ temp=temp_object)
+ ob = klass(id)
+
+ if temp_object:
+ ob = ob.__of__(container)
+ for ignore in ('activate_kw', 'is_indexable', 'reindex_kw'):
+ kw.pop(ignore, None)
+ else:
+ activate_kw = kw.pop('activate_kw', None)
+ if activate_kw is not None:
+ ob.__of__(container).setDefaultActivateParameters(**activate_kw)
+ reindex_kw = kw.pop('reindex_kw', None)
+ if reindex_kw is not None:
+ ob.__of__(container).setDefaultReindexParameters(**reindex_kw)
+ is_indexable = kw.pop('is_indexable', None)
+ if is_indexable is not None:
+ ob.isIndexable = is_indexable
+ container._setObject(id, ob)
+ ob = container._getOb(id)
+ # if no activity tool, the object has already an uid
+ if getattr(aq_base(ob), 'uid', None) is None:
+ ob.uid = portal.portal_catalog.newUid()
+
+ if kw:
+ ob._edit(force_update=1, **kw)
# Portal type has to be set before setting other attributes
# in order to initialize aq_dynamic
@@ -375,7 +378,7 @@ class ERP5TypeInformation(XMLObject,
# notify workflow after generating local roles, in order to prevent
# Unauthorized error on transition's condition
- workflow_tool = getToolByName(self.getPortalObject(), 'portal_workflow', None)
+ workflow_tool = getToolByName(portal, 'portal_workflow', None)
if workflow_tool is not None:
for workflow in workflow_tool.getWorkflowsFor(ob):
workflow.notifyCreated(ob)
Modified: erp5/trunk/products/ERP5Type/Utils.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/Utils.py?rev=39371&r1=39370&r2=39371&view=diff
==============================================================================
--- erp5/trunk/products/ERP5Type/Utils.py [utf8] (original)
+++ erp5/trunk/products/ERP5Type/Utils.py [utf8] Wed Oct 20 08:43:40 2010
@@ -506,72 +506,6 @@ from Products.ERP5Type.Globals import In
from Accessor.Base import func_code
from Products.CMFCore.utils import manage_addContentForm, manage_addContent
from AccessControl.PermissionRole import PermissionRole
-from MethodObject import Method
-
-class DocumentConstructor(Method):
- func_code = func_code()
- func_code.co_varnames = ('folder', 'id', 'REQUEST', 'kw')
- func_code.co_argcount = 2
- func_defaults = (None,)
-
- def __init__(self, klass):
- self.klass = klass
-
- def __call__(self, folder, id, REQUEST=None,
- activate_kw=None, is_indexable=None, reindex_kw=None, **kw):
- o = self.klass(id)
- if activate_kw is not None:
- o.__of__(folder).setDefaultActivateParameters(**activate_kw)
- if reindex_kw is not None:
- o.__of__(folder).setDefaultReindexParameters(**reindex_kw)
- if is_indexable is not None:
- o.isIndexable = is_indexable
- folder._setObject(id, o)
- o = folder._getOb(id)
- # if no activity tool, the object has already an uid
- if getattr(aq_base(o), 'uid', None) is None:
- o.uid = folder.portal_catalog.newUid()
- if kw: o._edit(force_update=1, **kw)
- if REQUEST is not None:
- REQUEST['RESPONSE'].redirect( 'manage_main' )
- return o
-
-class TempDocumentConstructor(DocumentConstructor):
-
- def __init__(self, klass):
- # Create a new class to set permissions specific to temporary objects.
- class TempDocument(klass):
- isTempDocument = PropertyConstantGetter('isTempDocument', value=True)
- __roles__ = None
-
- # Replace some attributes.
- for name in ('isIndexable', 'reindexObject', 'recursiveReindexObject',
- 'activate', 'setUid', 'setTitle', 'getTitle', 'getUid'):
- setattr(TempDocument, name, getattr(klass, '_temp_%s' % name))
-
- # Make some methods public.
- for method_id in ('reindexObject', 'recursiveReindexObject',
- 'activate', 'setUid', 'setTitle', 'getTitle',
- 'edit', 'setProperty', 'getUid', 'setCriterion',
- 'setCriterionPropertyList'):
- setattr(TempDocument, '%s__roles__' % method_id, None)
-
- self.klass = TempDocument
-
- def __call__(self, folder, id, REQUEST=None,
- activate_kw=None, is_indexable=None, reindex_kw=None, **kw):
- o = self.klass(id)
- # Use the real container instead of the factory dispatcher.
- #
- # XXX some code use this constructor directly instead of
- # through the factory system.
- if getattr(aq_base(folder), 'Destination', None) is not None:
- folder = folder.Destination()
- o = o.__of__(folder)
- if kw:
- o._edit(force_update=1, **kw)
- return o
-
python_file_parser = re.compile('^(.*)\.py$')
@@ -942,6 +876,43 @@ def setDefaultClassProperties(property_h
)
}
+class PersistentMigrationMixin(object):
+ """
+ All classes issued from ERP5Type.Document.XXX submodules
+ will gain with mixin as a base class.
+
+ It allows us to migrate ERP5Type.Document.XXX.YYY classes to
+ erp5.portal_type.ZZZ namespace
+
+ Note that migration can be disabled by setting the migrate
+ class attribute to 0/False, as all old objects in the system
+ should inherit from this mixin
+ """
+ migrate = 1
+
+ def __setstate__(self, value):
+ if not PersistentMigrationMixin.migrate:
+ super(PersistentMigrationMixin, self).__setstate__(value)
+ return
+
+ portal_type = value.get('portal_type')
+ if portal_type is None:
+ portal_type = getattr(self.__class__, 'portal_type', None)
+ if portal_type is None:
+ LOG('ERP5Type', PROBLEM,
+ "no portal type was found for %s (class %s)" \
+ % (self, self.__class__))
+ super(PersistentMigrationMixin, self).__setstate__(value)
+ else:
+ # proceed with migration
+ import erp5.portal_type
+ klass = getattr(erp5.portal_type, portal_type)
+ self.__class__ = klass
+ self.__setstate__(value)
+ LOG('ERP5Type', INFO, "Migration for object %s" % self)
+
+from Globals import Persistent, PersistentMapping
+
def importLocalDocument(class_id, document_path = None):
"""Imports a document class and registers it in ERP5Type Document
repository ( Products.ERP5Type.Document )
@@ -949,103 +920,72 @@ def importLocalDocument(class_id, docume
import Products.ERP5Type.Document
import Permissions
- if document_path is None:
- instance_home = getConfiguration().instancehome
- path = os.path.join(instance_home, "Document")
- else:
- path = document_path
- path = os.path.join(path, "%s.py" % class_id)
+ from Products.ERP5Type import document_class_registry
- module_path = 'Products.ERP5Type.Document.' + class_id
- document_module = sys.modules.get(module_path)
- # Import Document Class and Initialize it
- f = open(path)
- try:
- document_module = imp.load_source(module_path, path, f)
- document_class = getattr(document_module, class_id)
- document_constructor = DocumentConstructor(document_class)
- document_constructor_name = "add%s" % class_id
- document_constructor.__name__ = document_constructor_name
- except Exception:
- f.close()
- if document_module is not None:
- sys.modules[module_path] = document_module
- raise
+ classpath = document_class_registry.get(class_id)
+ if classpath is None:
+ # if the document was not registered before, it means that it is
+ # a local document in INSTANCE_HOME/Document/
+ # (created by ClassTool?)
+ if document_path is None:
+ instance_home = getConfiguration().instancehome
+ path = os.path.join(instance_home, "Document")
+ else:
+ path = document_path
+ path = os.path.join(path, "%s.py" % class_id)
+ module_path = "erp5.document"
+ classpath = "%s.%s" % (module_path, class_id)
+ try:
+ module = imp.load_source(classpath, path)
+ except:
+ raise AttributeError("document was not registered: %s, %s" % (class_id, document_path))
+ document_class_registry[class_id] = classpath
else:
- f.close()
- setattr(Products.ERP5Type.Document, class_id, document_module)
- setattr(Products.ERP5Type.Document, document_constructor_name,
- document_constructor)
- setDefaultClassProperties(document_class)
- ModuleSecurityInfo('Products.ERP5Type.Document').declareProtected(
- Permissions.AddPortalContent, document_constructor_name,)
- InitializeClass(document_class)
-
- # Temp documents are created as standard classes with a different constructor
- # which patches some methods are the instance level to prevent reindexing
- temp_document_constructor = TempDocumentConstructor(document_class)
+ module_path = classpath.rsplit('.', 1)[0]
+ module = __import__(module_path, {}, {}, (module_path,))
+
+ ### Migration
+ module_name = "Products.ERP5Type.Document.%s" % class_id
+
+ # Most of Document modules define a single class
+ # (ERP5Type.Document.Person.Person)
+ # but some (eek) need to act as module to find other documents,
+ # e.g. ERP5Type.Document.BusinessTemplate.SkinTemplateItem
+ #
+ def migrate_me_document_loader(document_name):
+ klass = getattr(module, document_name)
+ if issubclass(klass, (Persistent, PersistentMapping)):
+ setDefaultClassProperties(klass)
+ InitializeClass(klass)
+
+ class MigrateMe(PersistentMigrationMixin, klass):
+ pass
+ MigrateMe.__name__ = document_name
+ MigrateMe.__module__ = module_name
+ return MigrateMe
+ else:
+ return klass
+ from Dynamic.dynamicmodule import dynamicmodule
+ document_module = dynamicmodule(module_name, migrate_me_document_loader)
+
+ setattr(Products.ERP5Type.Document, class_id, document_module)
+
+ ### newTempFoo
+ from Products.ERP5Type.ERP5Type import ERP5TypeInformation
+ klass = getattr(module, class_id)
+ temp_type = ERP5TypeInformation(klass.portal_type)
+ temp_document_constructor = temp_type.constructTempInstance
+
temp_document_constructor_name = "newTemp%s" % class_id
- temp_document_constructor.__name__ = temp_document_constructor_name
setattr(Products.ERP5Type.Document,
temp_document_constructor_name,
temp_document_constructor)
ModuleSecurityInfo('Products.ERP5Type.Document').declarePublic(
temp_document_constructor_name,) # XXX Probably bad security
- # Update Meta Types
- new_meta_types = []
- for meta_type in Products.meta_types:
- if meta_type['name'] != document_class.meta_type:
- new_meta_types.append(meta_type)
- else:
- # Update new_meta_types
- instance_class = None
- new_meta_types.append(
- { 'name': document_class.meta_type,
- 'action': ('manage_addProduct/%s/%s' % (
- 'ERP5Type', document_constructor_name)),
- 'product': 'ERP5Type',
- 'permission': document_class.add_permission,
- 'visibility': 'Global',
- 'interfaces': document_class.__implements__,
- 'instance': instance_class,
- 'container_filter': None
- },)
- Products.meta_types = tuple(new_meta_types)
- # Update Constructors
- m = Products.ERP5Type._m
- if hasattr(document_class, 'factory_type_information'):
- constructors = ( manage_addContentForm
- , manage_addContent
- , document_constructor
- , temp_document_constructor
- , ('factory_type_information',
- document_class.factory_type_information) )
- else:
- constructors = ( manage_addContentForm
- , manage_addContent
- , document_constructor
- , temp_document_constructor )
- initial = constructors[0]
- m[initial.__name__]=manage_addContentForm
- default_permission = ('Manager',)
- pr=PermissionRole(document_class.add_permission, default_permission)
- m[initial.__name__+'__roles__']=pr
- for method in constructors[1:]:
- if isinstance(method, tuple):
- name, method = method
- else:
- name=os.path.split(method.__name__)[-1]
- if name != 'factory_type_information':
- # Add constructor to product dispatcher
- m[name]=method
- else:
- # Append fti to product dispatcher
- if not m.has_key(name): m[name] = []
- m[name].append(method)
- m[name+'__roles__']=pr
+ # XXX really?
+ return klass, tuple()
- return document_class, constructors
def initializeLocalRegistry(directory_name, import_local_method,
path_arg_name='path'):
@@ -1132,26 +1072,12 @@ def initializeProduct( context,
product_name = module_name.split('.')[-1]
- # Define content constructors for Document content classes (RAD)
- initializeDefaultConstructors(content_classes)
- extra_content_constructors = []
- for content_class in content_classes:
- if hasattr(content_class, 'add' + content_class.__name__):
- extra_content_constructors += [
- getattr(content_class, 'add' + content_class.__name__)]
- if hasattr(content_class, 'newTemp' + content_class.__name__):
- extra_content_constructors += [
- getattr(content_class, 'newTemp' + content_class.__name__)]
-
# Define FactoryTypeInformations for all content classes
contentFactoryTypeInformations = []
for content in content_classes:
if hasattr(content, 'factory_type_information'):
contentFactoryTypeInformations.append(content.factory_type_information)
- # Aggregate
- content_constructors = list(content_constructors) + list(extra_content_constructors)
-
# Try to make some standard directories available
try:
@@ -1242,31 +1168,6 @@ def createConstraintList(property_holder
# Constructor initialization
#####################################################
-def initializeDefaultConstructors(klasses):
- for klass in klasses:
- if getattr(klass, 'isRADContent', 0) and hasattr(klass, 'security'):
- setDefaultConstructor(klass)
- klass.security.declareProtected(Permissions.AddPortalContent,
- 'add' + klass.__name__)
-
-def setDefaultConstructor(klass):
- """
- Create the default content creation method
- """
- document_constructor_name = 'add' + klass.__name__
- if not hasattr(klass, document_constructor_name):
- document_constructor = DocumentConstructor(klass)
- setattr(klass, document_constructor_name, document_constructor)
- document_constructor.__name__ = document_constructor_name
-
- temp_document_constructor_name = 'newTemp' + klass.__name__
- if not hasattr(klass, temp_document_constructor_name):
- temp_document_constructor = TempDocumentConstructor(klass)
- setattr(klass, temp_document_constructor_name, temp_document_constructor)
- temp_document_constructor.__name__ = temp_document_constructor_name
- klass.security.declarePublic(temp_document_constructor_name)
-
-
def createExpressionContext(object, portal=None):
"""
Return a context used for evaluating a TALES expression.
Modified: erp5/trunk/products/ERP5Type/__init__.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/__init__.py?rev=39371&r1=39370&r2=39371&view=diff
==============================================================================
--- erp5/trunk/products/ERP5Type/__init__.py [utf8] (original)
+++ erp5/trunk/products/ERP5Type/__init__.py [utf8] Wed Oct 20 08:43:40 2010
@@ -98,6 +98,10 @@ def initialize( context ):
portal_tools = portal_tools,
content_constructors = content_constructors,
content_classes = content_classes)
+
+ from Dynamic import portaltypeclass
+ portaltypeclass.initializeDynamicModules()
+
# Register our Workflow factories directly (if on CMF 2)
Products.ERP5Type.Workflow.registerAllWorkflowFactories(context)
# We should register local constraints at some point
Modified: erp5/trunk/products/ERP5Type/tests/testMigration.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/tests/testMigration.py?rev=39371&r1=39370&r2=39371&view=diff
==============================================================================
--- erp5/trunk/products/ERP5Type/tests/testMigration.py [utf8] (original)
+++ erp5/trunk/products/ERP5Type/tests/testMigration.py [utf8] Wed Oct 20 08:43:40 2010
@@ -4,7 +4,6 @@ import unittest
import transaction
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
-from Products.ERP5Type.tests.backportUnittest import skip
class TestNewStyleClasses(ERP5TypeTestCase):
@@ -127,8 +126,6 @@ class TestNewStyleClasses(ERP5TypeTestCa
# reset the type
person_type.setTypeClass('Person')
-TestNewStyleClasses = skip("portal type classes code is not yet committed")(TestNewStyleClasses)
-
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestNewStyleClasses))
More information about the Erp5-report
mailing list