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,
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)
......
......@@ -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)
......
This diff is collapsed.
......@@ -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
......
......@@ -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(ERP5TypeTestCase):
# 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))
......
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