Commit a94f18cd authored by Nicolas Dumazet's avatar Nicolas Dumazet

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)



git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@39371 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 007078b4
...@@ -830,7 +830,11 @@ class Base( CopyContainer, ...@@ -830,7 +830,11 @@ class Base( CopyContainer,
cache_factory='erp5_ui_long')) cache_factory='erp5_ui_long'))
def _aq_key(self): 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): def _propertyMap(self):
""" Method overload - properties are now defined on the ptype """ """ Method overload - properties are now defined on the ptype """
...@@ -854,7 +858,11 @@ class Base( CopyContainer, ...@@ -854,7 +858,11 @@ class Base( CopyContainer,
Test purpose Test purpose
""" """
ptype = self.portal_type 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 aq_key = (ptype, klass) # We do not use _aq_key() here for speed
initializePortalTypeDynamicProperties(self, klass, ptype, aq_key, \ initializePortalTypeDynamicProperties(self, klass, ptype, aq_key, \
self.getPortalObject()) self.getPortalObject())
...@@ -866,7 +874,11 @@ class Base( CopyContainer, ...@@ -866,7 +874,11 @@ class Base( CopyContainer,
# and default properties can be associated per portal type # and default properties can be associated per portal type
# and per class. Other uses are possible (ex. WebSection). # and per class. Other uses are possible (ex. WebSection).
ptype = self.portal_type 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 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 # If this is a portal_type property and everything is already defined
...@@ -898,15 +910,6 @@ class Base( CopyContainer, ...@@ -898,15 +910,6 @@ class Base( CopyContainer,
Base.aq_method_generating.append(aq_key) Base.aq_method_generating.append(aq_key)
try: try:
# Proceed with property generation # 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 # Generate class methods
initializeClassDynamicProperties(self, klass) initializeClassDynamicProperties(self, klass)
......
...@@ -29,7 +29,7 @@ import Products ...@@ -29,7 +29,7 @@ import Products
from Products.CMFCore.TypesTool import FactoryTypeInformation from Products.CMFCore.TypesTool import FactoryTypeInformation
from Products.CMFCore.Expression import Expression from Products.CMFCore.Expression import Expression
from Products.CMFCore.exceptions import AccessControl_Unauthorized 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 import interfaces, Constraint, Permissions, PropertySheet
from Products.ERP5Type.Base import getClassPropertyList from Products.ERP5Type.Base import getClassPropertyList
from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
...@@ -304,48 +304,24 @@ class ERP5TypeInformation(XMLObject, ...@@ -304,48 +304,24 @@ class ERP5TypeInformation(XMLObject,
""" """
return default 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') security.declarePublic('isConstructionAllowed')
def isConstructionAllowed(self, container): def isConstructionAllowed(self, container):
"""Test if user is allowed to create an instance in the given 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') security.declarePublic('constructInstance')
def constructInstance(self, container, id, created_by_builder=0, def constructInstance(self, container, id, created_by_builder=0,
...@@ -356,10 +332,37 @@ class ERP5TypeInformation(XMLObject, ...@@ -356,10 +332,37 @@ class ERP5TypeInformation(XMLObject,
Call the init_script for the portal_type. Call the init_script for the portal_type.
Returns the object. Returns the object.
""" """
m = self._queryFactoryMethod(container, temp_object) if not temp_object and not self.isConstructionAllowed(container):
if isinstance(m, Exception): raise AccessControl_Unauthorized('Cannot create %s' % self.getId())
raise m
ob = m(id, **kw) 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 # Portal type has to be set before setting other attributes
# in order to initialize aq_dynamic # in order to initialize aq_dynamic
...@@ -375,7 +378,7 @@ class ERP5TypeInformation(XMLObject, ...@@ -375,7 +378,7 @@ class ERP5TypeInformation(XMLObject,
# notify workflow after generating local roles, in order to prevent # notify workflow after generating local roles, in order to prevent
# Unauthorized error on transition's condition # 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: if workflow_tool is not None:
for workflow in workflow_tool.getWorkflowsFor(ob): for workflow in workflow_tool.getWorkflowsFor(ob):
workflow.notifyCreated(ob) workflow.notifyCreated(ob)
......
This diff is collapsed.
...@@ -98,6 +98,10 @@ def initialize( context ): ...@@ -98,6 +98,10 @@ def initialize( context ):
portal_tools = portal_tools, portal_tools = portal_tools,
content_constructors = content_constructors, content_constructors = content_constructors,
content_classes = content_classes) content_classes = content_classes)
from Dynamic import portaltypeclass
portaltypeclass.initializeDynamicModules()
# Register our Workflow factories directly (if on CMF 2) # Register our Workflow factories directly (if on CMF 2)
Products.ERP5Type.Workflow.registerAllWorkflowFactories(context) Products.ERP5Type.Workflow.registerAllWorkflowFactories(context)
# We should register local constraints at some point # We should register local constraints at some point
......
...@@ -4,7 +4,6 @@ import unittest ...@@ -4,7 +4,6 @@ import unittest
import transaction import transaction
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ERP5Type.tests.backportUnittest import skip
class TestNewStyleClasses(ERP5TypeTestCase): class TestNewStyleClasses(ERP5TypeTestCase):
...@@ -127,8 +126,6 @@ class TestNewStyleClasses(ERP5TypeTestCase): ...@@ -127,8 +126,6 @@ class TestNewStyleClasses(ERP5TypeTestCase):
# reset the type # reset the type
person_type.setTypeClass('Person') person_type.setTypeClass('Person')
TestNewStyleClasses = skip("portal type classes code is not yet committed")(TestNewStyleClasses)
def test_suite(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestNewStyleClasses)) suite.addTest(unittest.makeSuite(TestNewStyleClasses))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment