Commit d02ba206 authored by Nicolas Dumazet's avatar Nicolas Dumazet

Last chunk of portal type classes / zodb property sheets.

After this, all ERP5 objects become instances of portal type classes

Preferences:
* all the trickery for preferences is gone and is handled by a specific
  accessor holder holding all preference methods

Property holders
* our Base.aq_portal_type property holders are not used anymore:
  the "property holder" becomes the portal type class itself and the
  set of accessor_holder classes in the mro of the portal type class:
  portal-type-specific methods are on the portal type class, while
  portal-type-independant method are put on the accessor holder ancestors
* the portal type meta class now also inherits from "PropertyHolder" to
  provide the same introspection interface and methods.
  (In the future this class / interface will need to be refined)

Bootstrap/migration:
* bootstrapping/migration from older instances: provide with code able to
  import XML from ERP5/bootstrap/ to load necessary tools from almost any
  instance state
* migrate in BusinessTemplate installation code all non-portal type classes
  objects to portal type classes
* Change the way Tools are installed when creating a site, so that we create
  directly portal type classes objects instead of Documents

Accessors:
* add a generatePortalTypeAccessors method on the portal type class to generate
  portal-type-specific accessors
* associate BaseAccessorHolder to all portal type classes to contain
  all common category related accessors
* change the way workflow methods are generated to bind them directly on
  the portal type class
* disable Base._aq_dynamic (while still keeping its code for debugging and
  reference, this can be cleanup up later)


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@42902 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent b49d48ed
......@@ -502,6 +502,8 @@ class ActivityTool (Folder, UniqueObject):
allowed_types = ( 'CMF Active Process', )
security = ClassSecurityInfo()
isIndexable = False
manage_options = tuple(
[ { 'label' : 'Overview', 'action' : 'manage_overview' }
, { 'label' : 'Activities', 'action' : 'manageActivities' }
......
......@@ -816,11 +816,18 @@ class ObjectTemplateItem(BaseTemplateItem):
if context.getTemplateFormatVersion() == 1:
upgrade_list = []
type_name = self.__class__.__name__.split('TemplateItem')[-2]
for path in self._objects:
for path, obj in self._objects.iteritems():
if installed_item._objects.has_key(path):
upgrade_list.append((path, installed_item._objects[path]))
else: # new object
modified_object_list[path] = 'New', type_name
# if that's an old style class, use a portal type class instead
migrateme = getattr(obj, '_migrateToPortalTypeClass', None)
if migrateme is not None:
migrateme()
self._objects[path] = obj
# update _p_jar property of objects cleaned by removeProperties
transaction.savepoint(optimistic=True)
for path, old_object in upgrade_list:
......@@ -1006,7 +1013,7 @@ class ObjectTemplateItem(BaseTemplateItem):
workflow_history = None
old_obj = container._getOb(object_id, None)
object_existed = old_obj is not None
if old_obj is not None:
if object_existed:
# Object already exists
recurse(saveHook, old_obj)
if getattr(aq_base(old_obj), 'groups', None) is not None:
......@@ -1920,10 +1927,24 @@ class PortalTypeTemplateItem(ObjectTemplateItem):
PersistentMigrationMixin._no_migration -= 1
return object_key_list
# XXX : this method is kept temporarily, but can be removed once all bt5 are
# re-exported with separated workflow-chain information
def install(self, context, trashbin, **kw):
if context.getTemplateFormatVersion() == 1:
object_list = self._objects
else:
object_list = self._archive
for path, obj in object_list.iteritems():
# if that's an old style class, use a portal type class instead
# XXX PortalTypeTemplateItem-specific
migrateme = getattr(obj, '_migrateToPortalTypeClass', None)
if migrateme is not None:
migrateme()
object_list[path] = obj
ObjectTemplateItem.install(self, context, trashbin, **kw)
# XXX : following be removed once all bt5 are
# re-exported with separated workflow-chain information
update_dict = kw.get('object_to_update')
force = kw.get('force')
# We now need to setup the list of workflows corresponding to
......@@ -1933,10 +1954,6 @@ class PortalTypeTemplateItem(ObjectTemplateItem):
# best solution, by default it is 'default_workflow', which is
# not very usefull
default_chain = ''
if context.getTemplateFormatVersion() == 1:
object_list = self._objects
else:
object_list = self._archive
for path in object_list.keys():
if update_dict.has_key(path) or force:
if not force:
......@@ -3478,7 +3495,7 @@ class PropertySheetTemplateItem(DocumentTemplateItem,
# If set to False, then the migration of Property Sheets will never
# be performed, required until the code of ZODB Property Sheets is
# stable and completely documented
_perform_migration = False
_perform_migration = True
# Only meaningful for filesystem Property Sheets
local_file_reader_name = staticmethod(readLocalPropertySheet)
......@@ -4336,6 +4353,7 @@ Business Template is a set of definitions, such as skins, portal types and categ
, 'icon' : 'file_icon.gif'
, 'product' : 'ERP5Type'
, 'factory' : 'addBusinessTemplate'
, 'type_class' : 'BusinessTemplate'
, 'immediate_view' : 'BusinessTemplate_view'
, 'allow_discussion' : 1
, 'allowed_content_types': (
......
......@@ -139,6 +139,14 @@ def getCatalogStorageList(*args, **kw):
result.append((item, title))
return result
def addERP5Tool(portal, id, portal_type):
if portal.hasObject(id):
return
import erp5.portal_type
klass = getattr(erp5.portal_type, portal_type)
obj = klass()
portal._setObject(id, obj)
class ReferCheckerBeforeTraverseHook:
"""This before traverse hook checks the HTTP_REFERER argument in the request
and refuses access to anything else that portal_url.
......@@ -313,6 +321,7 @@ class ERP5Site(FolderMixIn, CMFSite, CacheCookieMixin):
return CMFSite.manage_renameObject(self, id=id, new_id=new_id,
REQUEST=REQUEST)
def _getAcquireLocalRoles(self):
"""
Prevent local roles from being acquired outside of Portal object.
......@@ -1287,7 +1296,7 @@ class ERP5Site(FolderMixIn, CMFSite, CacheCookieMixin):
module_id = expected_module_id
# then look for module where the type is allowed
else:
for expected_module_id in portal_object.objectIds(spec=('ERP5 Folder',)):
for expected_module_id in portal_object.objectIds(('ERP5 Folder',)):
module = portal_object._getOb(expected_module_id, None)
if module is not None:
if portal_type in self.portal_types[module.getPortalType()].\
......@@ -1465,7 +1474,6 @@ class PortalGenerator:
addCMFCoreTool('CMF Catalog', None)
addCMFCoreTool('CMF Member Data Tool', None)
addCMFCoreTool('CMF Skins Tool', None)
addCMFCoreTool('CMF Types Tool', None)
addCMFCoreTool('CMF Undo Tool', None)
addCMFCoreTool('CMF URL Tool', None)
addCMFCoreTool('CMF Workflow Tool', None)
......@@ -1610,23 +1618,17 @@ class ERP5Generator(PortalGenerator):
# Add several other tools, only at the end in order
# to make sure that they will be reindexed
addTool = p.manage_addProduct['ERP5'].manage_addTool
if not p.hasObject('portal_rules'):
addTool('ERP5 Rule Tool', None)
if not p.hasObject('portal_simulation'):
addTool('ERP5 Simulation Tool', None)
if not p.hasObject('portal_deliveries'):
addTool('ERP5 Delivery Tool', None)
if not p.hasObject('portal_orders'):
addTool('ERP5 Order Tool', None)
addERP5Tool(p, 'portal_rules', 'Rule Tool')
addERP5Tool(p, 'portal_simulation', 'Simulation Tool')
addERP5Tool(p, 'portal_deliveries', 'Delivery Tool')
addERP5Tool(p, 'portal_orders', 'Order Tool')
def setupTemplateTool(self, p, **kw):
"""
Setup the Template Tool. Security must be set strictly.
"""
addTool = p.manage_addProduct['ERP5'].manage_addTool
addTool('ERP5 Template Tool', None)
addERP5Tool(p, 'portal_templates', 'Template Tool')
context = p.portal_templates
permission_list = context.possible_permissions()
for permission in permission_list:
......@@ -1638,7 +1640,6 @@ class ERP5Generator(PortalGenerator):
"""
if not 'portal_actions' in p.objectIds():
PortalGenerator.setupTools(self, p)
p._delObject('portal_types')
# It is better to remove portal_catalog
# which is ZCatalog as soon as possible,
......@@ -1660,51 +1661,31 @@ class ERP5Generator(PortalGenerator):
pass
# Add ERP5 Tools
addTool = p.manage_addProduct['ERP5'].manage_addTool
if not p.hasObject('portal_categories'):
addTool('ERP5 Categories', None)
if not p.hasObject('portal_ids'):
addTool('ERP5 Id Tool', None)
addERP5Tool(p, 'portal_categories', 'Category Tool')
addERP5Tool(p, 'portal_ids', 'Id Tool')
if not p.hasObject('portal_templates'):
self.setupTemplateTool(p)
if not p.hasObject('portal_trash'):
addTool('ERP5 Trash Tool', None)
if not p.hasObject('portal_alarms'):
addTool('ERP5 Alarm Tool', None)
if not p.hasObject('portal_domains'):
addTool('ERP5 Domain Tool', None)
if not p.hasObject('portal_tests'):
addTool('ERP5 Test Tool', None)
if not p.hasObject('portal_password'):
addTool('ERP5 Password Tool', None)
if not p.hasObject('portal_acknowledgements'):
addTool('ERP5 Acknowledgement Tool', None)
addERP5Tool(p, 'portal_trash', 'Trash Tool')
addERP5Tool(p, 'portal_alarms', 'Alarm Tool')
addERP5Tool(p, 'portal_domains', 'Domain Tool')
addERP5Tool(p, 'portal_tests', 'Test Tool')
addERP5Tool(p, 'portal_password', 'Password Tool')
addERP5Tool(p, 'portal_acknowledgements', 'Acknowledgement Tool')
# Add ERP5Type Tool
addTool = p.manage_addProduct['ERP5Type'].manage_addTool
if not p.hasObject('portal_caches'):
addTool('ERP5 Cache Tool', None)
if not p.hasObject('portal_memcached'):
addTool('ERP5 Memcached Tool', None)
if not p.hasObject('portal_types'):
addTool('ERP5 Types Tool', None)
if not p.hasObject('portal_property_sheets'):
addTool('ERP5 Property Sheet Tool', None)
addERP5Tool(p, 'portal_caches', 'Cache Tool')
addERP5Tool(p, 'portal_memcached', 'Memcached Tool')
try:
addTool = p.manage_addProduct['ERP5Subversion'].manage_addTool
if not p.hasObject('portal_subversion'):
addTool('ERP5 Subversion Tool', None)
addERP5Tool(p, 'portal_subversion', 'Subversion Tool')
except AttributeError:
pass
# Add ERP5Type Tools
addTool = p.manage_addProduct['ERP5Type'].manage_addTool
if not p.hasObject('portal_classes'):
if allowClassTool():
addTool('ERP5 Class Tool', None)
else:
addTool('ERP5 Dummy Class Tool', None)
if allowClassTool():
addERP5Tool(p, 'portal_classes', 'Class Tool')
else:
addERP5Tool(p, 'portal_classes', 'Dummy Class Tool')
# Add ERP5 SQL Catalog Tool
addTool = p.manage_addProduct['ERP5Catalog'].manage_addTool
......@@ -1758,17 +1739,12 @@ class ERP5Generator(PortalGenerator):
pass
# Add ERP5Form Tools
addTool = p.manage_addProduct['ERP5Form'].manage_addTool
if not p.hasObject('portal_selections'):
addTool('ERP5 Selections', None)
if not p.hasObject('portal_preferences'):
addTool('ERP5 Preference Tool', None)
addERP5Tool(p, 'portal_selections', 'Selection Tool')
addERP5Tool(p, 'portal_preferences', 'Preference Tool')
try:
# Add ERP5SyncML Tools
addTool = p.manage_addProduct['ERP5SyncML'].manage_addTool
if not p.hasObject('portal_synchronizations'):
addTool('ERP5 Synchronizations', None)
addERP5Tool(p, 'portal_synchronizations', 'Synchronization Tool')
except AttributeError:
pass
......@@ -2032,10 +2008,6 @@ class ERP5Generator(PortalGenerator):
self.setupPermissions(p)
self.setupDefaultSkins(p)
# ERP5 Design Choice is that all content should be user defined
# Content is disseminated through business templates
self.setupPortalTypes(p)
if not p.hasObject('content_type_registry'):
self.setupMimetypes(p)
if not update:
......@@ -2054,18 +2026,6 @@ class ERP5Generator(PortalGenerator):
if not update:
self.setupIndex(p, **kw)
def setupPortalTypes(self, p):
"""
Install the portal_type of Business Template
"""
tool = getToolByName(p, 'portal_types', None)
if tool is None:
return
for t in (BusinessTemplate, ):
t = t.factory_type_information
if not tool.hasObject(t['id']):
tool._setObject(t['id'], ERP5TypeInformation(uid=None, **t))
def setupERP5Core(self,p,**kw):
"""
Install the core part of ERP5
......
......@@ -93,11 +93,3 @@ class Preference( Folder ):
def disable(self):
"""Workflow method"""
self._clearCache()
def _aq_dynamic(self, id):
""" force _aq_dynamic on preference tool, because list of property sheet of
preferences depends on the code of PreferenceTool._aq_dynamic"""
if not PreferenceTool.aq_preference_generated:
portal = self.getPortalObject()
portal.portal_preferences._aq_dynamic('dummy')
return Preference.inheritedAttribute('_aq_dynamic')(self, id)
......@@ -30,17 +30,15 @@
from AccessControl import ClassSecurityInfo
from AccessControl.SecurityManagement import getSecurityManager,\
setSecurityManager, newSecurityManager
from AccessControl.PermissionRole import PermissionRole
from MethodObject import Method
from Products.ERP5Type.Globals import InitializeClass, DTMLFile
from zLOG import LOG, PROBLEM
from Products.CMFCore.utils import getToolByName
from Products.ERP5Type.Tool.BaseTool import BaseTool
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5Type import Permissions
from Products.ERP5Type.Cache import CachingMethod
from Products.ERP5Type.Utils import convertToUpperCase
from Products.ERP5Type.Accessor.TypeDefinition import list_types
from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
from Products.ERP5Form import _dtmldir
......@@ -52,76 +50,6 @@ class Priority:
GROUP = 2
USER = 3
def updatePreferenceClassPropertySheetList():
# XXX obsolete, and now handled in dynamic.portal_type_class
from Products.ERP5Form.Document.Preference import Preference
# 'Static' property sheets defined on the class
class_property_sheet_list = Preference.property_sheets
# Time to lookup for preferences defined on other modules
property_sheets = list(class_property_sheet_list)
for id in dir(PropertySheet):
if id.endswith('Preference'):
ps = getattr(PropertySheet, id)
if not isinstance(ps, basestring) and ps not in property_sheets:
property_sheets.append(ps)
class_property_sheet_list = tuple(property_sheets)
Preference.property_sheets = class_property_sheet_list
def createPreferenceToolAccessorList(portal) :
"""
Initialize all Preference methods on the preference tool.
This method must be called on startup.
This tool is capable of updating the list of Preference
property sheets by looking at all registered property sheets
and considering those which name ends with 'Preference'
"""
property_list = []
# 'Static' property sheets defined on the class
# The Preference class should be imported from the common location
# in ERP5Type since it could be overloaded in another product
from Products.ERP5Type.Document.Preference import Preference
for property_sheet in Preference.property_sheets:
if not isinstance(property_sheet, basestring):
property_list += property_sheet._properties
if not len(property_list):
return
# 'Dynamic' property sheets added by portal_type
pref_portal_type = portal.portal_types.getTypeInfo('Preference')
if pref_portal_type is None:
LOG('ERP5Form.PreferenceTool', PROBLEM,
'Preference type information is not installed.')
else:
pref_portal_type.updatePropertySheetDefinitionDict(
{'_properties': property_list})
# Generate common method names
for prop in property_list:
if prop.get('preference'):
# XXX read_permission and write_permissions defined at
# property sheet are not respected by this.
# only properties marked as preference are used
attribute = prop['id']
attr_list = [ 'get%s' % convertToUpperCase(attribute)]
if prop['type'] == 'boolean':
attr_list.append('is%s' % convertToUpperCase(attribute))
if prop['type'] in list_types :
attr_list.append('get%sList' % convertToUpperCase(attribute))
for attribute_name in attr_list:
method = PreferenceMethod(attribute_name, prop.get('default'))
setattr(PreferenceTool, attribute_name, method)
read_permission = prop.get('read_permission')
if read_permission:
setattr(PreferenceTool, attribute_name + '__roles__',
PermissionRole(read_permission))
class func_code: pass
class PreferenceMethod(Method):
......@@ -207,23 +135,6 @@ class PreferenceTool(BaseTool):
return method(default)
return default
def _aq_dynamic(self, id):
# XXX as soon as zodb property sheets are put everywhere, this can
# be safely deleted
base_value = PreferenceTool.inheritedAttribute('_aq_dynamic')(self, id)
if not PreferenceTool.aq_preference_generated:
updatePreferenceClassPropertySheetList()
portal = self.getPortalObject()
while portal.portal_type != 'ERP5 Site':
portal = portal.aq_parent.aq_inner.getPortalObject()
createPreferenceToolAccessorList(portal)
PreferenceTool.aq_preference_generated = True
if base_value is None:
return getattr(self, id)
return base_value
security.declareProtected(Permissions.ModifyPortalContent, "setPreference")
def setPreference(self, pref_name, value) :
""" set the preference on the active Preference object"""
......
......@@ -339,7 +339,9 @@ class PropertyHolder(object):
Invokes appropriate factory and create an accessor
"""
fake_accessor = getattr(self, id)
ptype = self._portal_type
ptype = getattr(self, '_portal_type', None)
if ptype is None:
ptype = self.portal_type
if fake_accessor is PropertyHolder.WORKFLOW_METHOD_MARKER:
# Case 1 : a workflow method only
accessor = Base._doNothing
......@@ -416,6 +418,7 @@ class PropertyHolder(object):
self.workflow_method_registry[id] = signature_list + (signature,)
if getattr(self, id, None) is None:
setattr(self, id, PropertyHolder.WORKFLOW_METHOD_MARKER)
self.createAccessor(id)
def declareProtected(self, permission, accessor_name):
"""
......@@ -476,10 +479,8 @@ class PropertyHolder(object):
"""
result = {}
if inherited:
base_list = list(klass.__bases__)
base_list.reverse()
for klass in base_list:
result.update(self._getClassDict(klass, inherited=1, local=1))
for parent in reversed(klass.mro()):
result.update(parent.__dict__)
if local:
result.update(klass.__dict__)
return result
......@@ -495,7 +496,7 @@ class PropertyHolder(object):
Return a list of tuple (id, method, module) for every class method
"""
return [x for x in self._getClassItemList(klass, inherited=inherited,
local=local) if callable(x[1]) and not isinstance(x[1], Method)]
local=local) if callable(x[1])]
def getClassMethodIdList(self, klass, inherited=1, local=1):
"""
......@@ -542,6 +543,8 @@ def initializeClassDynamicProperties(self, klass):
Base.aq_method_generated.add(klass)
def initializePortalTypeDynamicProperties(self, klass, ptype, aq_key, portal):
raise ValueError("No reason to go through this no more with portal type classes")
## Init CachingMethod which implements caching for ERP5
from Products.ERP5Type.Cache import initializePortalCachingProperties
initializePortalCachingProperties(portal)
......@@ -615,8 +618,7 @@ def initializePortalTypeDynamicProperties(self, klass, ptype, aq_key, portal):
#klass.__ac_permissions__ = prop_holder.__ac_permissions__
Base.aq_portal_type[aq_key] = prop_holder
def initializePortalTypeDynamicWorkflowMethods(self, klass, ptype, prop_holder,
portal):
def initializePortalTypeDynamicWorkflowMethods(ptype_klass, portal_workflow):
"""We should now make sure workflow methods are defined
and also make sure simulation state is defined."""
# aq_inner is required to prevent extra name lookups from happening
......@@ -624,12 +626,13 @@ def initializePortalTypeDynamicWorkflowMethods(self, klass, ptype, prop_holder,
# wrapper contains an object with _aq_dynamic defined, the workflow id
# is looked up with _aq_dynamic, thus causes infinite recursions.
portal_workflow = aq_inner(getToolByName(portal, 'portal_workflow'))
portal_workflow = aq_inner(portal_workflow)
portal_type = ptype_klass.__name__
dc_workflow_dict = dict()
interaction_workflow_dict = dict()
for wf in portal_workflow.getWorkflowsFor(self):
for wf in portal_workflow.getWorkflowsFor(portal_type):
wf_id = wf.id
wf_type = wf.__class__.__name__
if wf_type == "DCWorkflowDefinition":
......@@ -643,11 +646,11 @@ def initializePortalTypeDynamicWorkflowMethods(self, klass, ptype, prop_holder,
WorkflowState.TranslatedGetter),
('getTranslated%sTitle' % UpperCase(state_var),
WorkflowState.TranslatedTitleGetter)):
if not hasattr(prop_holder, method_id):
if not hasattr(ptype_klass, method_id):
method = getter(method_id, wf_id)
# Attach to portal_type
setattr(prop_holder, method_id, method)
prop_holder.security.declareProtected(
setattr(ptype_klass, method_id, method)
ptype_klass.security.declareProtected(
Permissions.AccessContentsInformation,
method_id )
......@@ -673,40 +676,38 @@ def initializePortalTypeDynamicWorkflowMethods(self, klass, ptype, prop_holder,
transition_id_set, trigger_dict = v
for tr_id, tdef in trigger_dict.iteritems():
method_id = convertToMixedCase(tr_id)
method = getattr(klass, method_id, _MARKER)
method = getattr(ptype_klass, method_id, _MARKER)
if method is _MARKER:
prop_holder.security.declareProtected(Permissions.AccessContentsInformation,
ptype_klass.security.declareProtected(Permissions.AccessContentsInformation,
method_id)
prop_holder.registerWorkflowMethod(method_id, wf_id, tr_id)
ptype_klass.registerWorkflowMethod(method_id, wf_id, tr_id)
continue
# Wrap method
if not callable(method):
LOG('initializePortalTypeDynamicWorkflowMethods', 100,
'WARNING! Can not initialize %s on %s' % \
(method_id, str(klass)))
(method_id, portal_type))
continue
if not isinstance(method, WorkflowMethod):
method = WorkflowMethod(method)
setattr(klass, method_id, method)
setattr(ptype_klass, method_id, method)
else:
# We must be sure that we
# are going to register class defined
# workflow methods to the appropriate transition
transition_id = method.getTransitionId()
if transition_id in transition_id_set:
method.registerTransitionAlways(ptype, wf_id, transition_id)
method.registerTransitionAlways(ptype, wf_id, tr_id)
method.registerTransitionAlways(portal_type, wf_id, transition_id)
method.registerTransitionAlways(portal_type, wf_id, tr_id)
if not interaction_workflow_dict:
return
class_method_list = prop_holder.getClassMethodIdList(klass)
# only compute once this (somehow) costly list
all_method_id_list = prop_holder.getAccessorMethodIdList() + \
prop_holder.getWorkflowMethodIdList() + \
class_method_list
# all methods in mro of portal type class: that contains all
# workflow methods and accessors you could possibly ever need
class_method_id_list = ptype_klass.getClassMethodIdList(ptype_klass)
interaction_queue = []
# XXX This part is (more or less...) a copy and paste
......@@ -714,7 +715,7 @@ def initializePortalTypeDynamicWorkflowMethods(self, klass, ptype, prop_holder,
transition_id_set, trigger_dict = v
for tr_id, tdef in trigger_dict.iteritems():
if (tdef.portal_type_filter is not None and \
ptype not in tdef.portal_type_filter):
portal_type not in tdef.portal_type_filter):
continue
for imethod_id in tdef.method_id:
if wildcard_interaction_method_id_match(imethod_id):
......@@ -730,21 +731,21 @@ def initializePortalTypeDynamicWorkflowMethods(self, klass, ptype, prop_holder,
method_id_matcher))
# XXX - class stuff is missing here
method_id_list = filter(method_id_matcher, all_method_id_list)
method_id_list = filter(method_id_matcher, class_method_id_list)
else:
# Single method
# XXX What if the method does not exist ?
# It's not consistent with regexp based filters.
method_id_list = [imethod_id]
for method_id in method_id_list:
method = getattr(klass, method_id, _MARKER)
method = getattr(ptype_klass, method_id, _MARKER)
if method is _MARKER:
# set a default security, if this method is not already
# protected.
if method_id not in prop_holder.security.names:
prop_holder.security.declareProtected(
if method_id not in ptype_klass.security.names:
ptype_klass.security.declareProtected(
Permissions.AccessContentsInformation, method_id)
prop_holder.registerWorkflowMethod(method_id, wf_id, tr_id,
ptype_klass.registerWorkflowMethod(method_id, wf_id, tr_id,
tdef.once_per_transaction)
continue
......@@ -752,42 +753,46 @@ def initializePortalTypeDynamicWorkflowMethods(self, klass, ptype, prop_holder,
if not callable(method):
LOG('initializePortalTypeDynamicWorkflowMethods', 100,
'WARNING! Can not initialize %s on %s' % \
(method_id, str(klass)))
(method_id, portal_type))
continue
if not isinstance(method, WorkflowMethod):
method = WorkflowMethod(method)
setattr(klass, method_id, method)
setattr(ptype_klass, method_id, method)
else:
# We must be sure that we
# are going to register class defined
# workflow methods to the appropriate transition
transition_id = method.getTransitionId()
if transition_id in transition_id_set:
method.registerTransitionAlways(ptype, wf_id, transition_id)
method.registerTransitionAlways(portal_type, wf_id, transition_id)
if tdef.once_per_transaction:
method.registerTransitionOncePerTransaction(ptype, wf_id, tr_id)
method.registerTransitionOncePerTransaction(portal_type, wf_id, tr_id)
else:
method.registerTransitionAlways(ptype, wf_id, tr_id)
method.registerTransitionAlways(portal_type, wf_id, tr_id)
if not interaction_queue:
return
new_method_set = set(prop_holder.getClassMethodIdList(klass))
added_method_set = new_method_set.difference(class_method_list)
# the only methods that could have appeared since last check are
# workflow methods
# TODO we could just queue the ids of methods that are attached to the
# portal type class in the previous loop, to improve performance
new_method_set = set(ptype_klass.getWorkflowMethodIdList())
added_method_set = new_method_set.difference(class_method_id_list)
# We need to run this part twice in order to handle interactions of interactions
# ex. an interaction workflow creates a workflow method which matches
# the regexp of another interaction workflow
for wf_id, tr_id, transition_id_set, once, method_id_matcher in interaction_queue:
for method_id in filter(method_id_matcher, added_method_set):
# method must already exist and be a workflow method
method = getattr(klass, method_id)
method = getattr(ptype_klass, method_id)
transition_id = method.getTransitionId()
if transition_id in transition_id_set:
method.registerTransitionAlways(ptype, wf_id, transition_id)
method.registerTransitionAlways(portal_type, wf_id, transition_id)
if once:
method.registerTransitionOncePerTransaction(ptype, wf_id, tr_id)
method.registerTransitionOncePerTransaction(portal_type, wf_id, tr_id)
else:
method.registerTransitionAlways(ptype, wf_id, tr_id)
method.registerTransitionAlways(portal_type, wf_id, tr_id)
class Base( CopyContainer,
PortalContent,
......@@ -906,23 +911,15 @@ class Base( CopyContainer,
def _propertyMap(self):
""" Method overload - properties are now defined on the ptype """
# Get all the accessor holders for ZODB Property Sheets
if hasattr(self.__class__, 'getAccessorHolderPropertyList'):
accessor_holder_property_list = \
tuple(self.__class__.getAccessorHolderPropertyList())
# Temporary portal type (such as 'TempBase' meaningful to display
# the objects being created/updated/removed on SVN update) does
# not inherit from any class of erp5.portal_type
else:
accessor_holder_property_list = ()
klass = self.__class__
property_list = []
# Get all the accessor holders for this portal type
if hasattr(klass, 'getAccessorHolderPropertyList'):
property_list += \
self.__class__.getAccessorHolderPropertyList()
self._aq_dynamic('id') # Make sure aq_dynamic has been called once
property_holder = Base.aq_portal_type.get(self._aq_key())
if property_holder is None:
return ERP5PropertyManager._propertyMap(self)
return (tuple(getattr(property_holder, '_properties', ())) +
tuple(getattr(self, '_local_properties', ())) +
accessor_holder_property_list)
property_list += getattr(self, '_local_properties', [])
return tuple(property_list)
def manage_historyCompare(self, rev1, rev2, REQUEST,
historyComparisonResults=''):
......@@ -945,6 +942,9 @@ class Base( CopyContainer,
createPreferenceToolAccessorList(self.getPortalObject())
def _aq_dynamic(self, id):
# ahah! disabled, thanks to portal type classes
return None
# _aq_dynamic has been created so that callable objects
# and default properties can be associated per portal type
# and per class. Other uses are possible (ex. WebSection).
......@@ -1432,28 +1432,12 @@ class Base( CopyContainer,
except TypeError:
pass
return method(**kw)
# Try to get a portal_type property (Implementation Dependent)
aq_key = self._aq_key()
aq_portal_type = Base.aq_portal_type
if aq_key not in aq_portal_type:
try:
self._aq_dynamic(accessor_name)
except AttributeError:
pass
if hasattr(aq_portal_type[aq_key], accessor_name):
method = getattr(self, accessor_name)
if d is not _MARKER:
try:
return method(d, **kw)
except TypeError:
pass
return method(**kw)
# Try a mono valued accessor if it is available
# and return it as a list
if accessor_name.endswith('List'):
mono_valued_accessor_name = accessor_name[:-4]
if hasattr(aq_portal_type[aq_key], mono_valued_accessor_name):
method = getattr(self, mono_valued_accessor_name)
method = getattr(self, mono_valued_accessor_name, None)
if method is not None:
# We have a monovalued property
if d is _MARKER:
result = method(**kw)
......@@ -1519,27 +1503,15 @@ class Base( CopyContainer,
if getattr(aq_self, public_accessor_name, None) is not None:
method = getattr(self, public_accessor_name)
return method(value, **kw)
# Try to get a portal_type property (Implementation Dependent)
aq_key = self._aq_key()
aq_portal_type = Base.aq_portal_type
if not aq_portal_type.has_key(aq_key):
self._aq_dynamic('id') # Make sure _aq_dynamic has been called once
if getattr(aq_portal_type[aq_key], accessor_name, None) is not None:
method = getattr(self, accessor_name)
# LOG("Base.py", 0, "method = %s, name = %s" %(method, accessor_name))
return method(value, **kw)
if getattr(aq_portal_type[aq_key], public_accessor_name, None) is not None:
method = getattr(self, public_accessor_name)
return method(value, **kw)
# Try a mono valued setter if it is available
# and call it
if accessor_name.endswith('List'):
mono_valued_accessor_name = accessor_name[:-4]
mono_valued_public_accessor_name = public_accessor_name[:-4]
method = None
if hasattr(aq_portal_type[aq_key], mono_valued_accessor_name):
if hasattr(self, mono_valued_accessor_name):
method = getattr(self, mono_valued_accessor_name)
elif hasattr(aq_portal_type[aq_key], mono_valued_public_accessor_name):
elif hasattr(self, mono_valued_public_accessor_name):
method = getattr(self, mono_valued_public_accessor_name)
if method is not None:
if isinstance(value, (list, tuple)):
......@@ -1584,18 +1556,6 @@ class Base( CopyContainer,
method = getattr(self, public_accessor_name)
method(value, **kw)
return
# Try to get a portal_type property (Implementation Dependent)
aq_key = self._aq_key()
if not Base.aq_portal_type.has_key(aq_key):
self._aq_dynamic('id') # Make sure _aq_dynamic has been called once
if hasattr(Base.aq_portal_type[aq_key], accessor_name):
method = getattr(self, accessor_name)
method(value, **kw)
return
if hasattr(Base.aq_portal_type[aq_key], public_accessor_name):
method = getattr(self, public_accessor_name)
method(value, **kw)
return
# Finaly use standard PropertyManager
#LOG("Changing attr: ",0, key)
#try:
......@@ -1692,20 +1652,16 @@ class Base( CopyContainer,
unordered_key_list = [k for k in key_list if k not in edit_order]
ordered_key_list = [k for k in edit_order if k in key_list]
restricted_method_set = set()
default_permission_set = set(('Access contents information',
'Modify portal content'))
if restricted:
# retrieve list of accessors which doesn't use default permissions
aq_key = self._aq_key()
aq_portal_type = Base.aq_portal_type
if aq_key not in aq_portal_type:
try:
self._aq_dynamic("")
except AttributeError:
pass
prop_holder = aq_portal_type[aq_key]
for permissions in prop_holder.__ac_permissions__:
if permissions[0] not in ('Access contents information', 'Modify portal content'):
for method in permissions[1]:
restricted_method_set.add(method)
for ancestor in self.__class__.mro():
for permissions in getattr(ancestor, '__ac_permissions__', ()):
if permissions[0] not in default_permission_set:
for method in permissions[1]:
if method.startswith('set'):
restricted_method_set.add(method)
getProperty = self.getProperty
hasProperty = self.hasProperty
......
......@@ -396,9 +396,8 @@ class ERP5TypeInformation(XMLObject,
return ob
def _getPropertyHolder(self):
ob = self.constructTempInstance(self, self.getId())
ob._aq_dynamic('id')
return ob.aq_portal_type[ob._aq_key()]
import erp5.portal_type as module
return getattr(module, self.getId())
security.declarePrivate('updatePropertySheetDefinitionDict')
def updatePropertySheetDefinitionDict(self, definition_dict):
......@@ -499,7 +498,7 @@ class ERP5TypeInformation(XMLObject,
"""
Returns the list of properties which are specific to the portal type.
"""
return self.constructTempInstance(self, self.getId()).propertyMap()
return self.__class__.propertyMap()
security.declareProtected(Permissions.AccessContentsInformation,
'PrincipiaSearchSource')
......
......@@ -62,7 +62,7 @@ class TranslationProviderBase(object):
"""
property_domain_dict = {}
for prop in self._getPropertyHolder()._properties:
for prop in self._getPropertyHolder().getAccessorHolderPropertyList():
prop_id = prop['id']
if prop.get('translatable') and prop_id not in property_domain_dict:
domain_name = prop.get('translation_domain')
......
......@@ -1301,7 +1301,8 @@ def getExistingBaseCategoryList(portal, base_cat_list):
category_tool = getattr(portal, 'portal_categories', None)
if category_tool is None:
# most likely, accessor generation when bootstrapping a site
warnings.warn("Category Tool is missing. Accessors can not be generated.")
if not getattr(portal, '_v_bootstrapping', False):
warnings.warn("Category Tool is missing. Accessors can not be generated.")
return ()
new_base_cat_list = []
......@@ -1577,13 +1578,6 @@ def setDefaultProperties(property_holder, object=None, portal=None):
portal=portal)
# Create Category Accessors
createAllCategoryAccessors(portal, property_holder, cat_list, econtext)
if object is not None and property_holder.__name__ == "Base":
# XXX use if possible is and real class
if portal is not None:
portal_categories = getattr(portal, 'portal_categories', None)
else:
portal_categories = None
createRelatedAccessors(portal_categories, property_holder, econtext)
property_holder.constraints = []
for constraint in constraint_list:
......@@ -1642,29 +1636,6 @@ def setDefaultProperties(property_holder, object=None, portal=None):
# # setattr(property_holder, prop['id'], defaults[prop['type']])
# pass
# Create for every portal type group an accessor (like isPortalDeliveryType)
# In the future, this should probably use categories
if portal is not None and object is not None: # we can not do anything without portal
# import lately in order to avoid circular dependency
from Products.ERP5Type.ERP5Type import ERP5TypeInformation
portal_type = object.portal_type
for group in ERP5TypeInformation.defined_group_list:
value = portal_type in portal._getPortalGroupedTypeSet(group)
prop = {
'id' : group,
'description' : "accessor to know the membership of portal group %s" \
% group,
'type' : 'group_type',
'default' : value,
'group_type' : group,
}
createDefaultAccessors(
property_holder,
prop['id'],
prop=prop,
read_permission=Permissions.AccessContentsInformation,
portal=portal)
#####################################################
# Accessor initialization
#####################################################
......@@ -2767,6 +2738,7 @@ def createGroupTypeAccessors(property_holder, prop,
Generate accessors that allows to know if we belongs to a particular
group of portal types
"""
raise ValueError("This method is not used. Remove it?")
# Getter
group = prop['group_type']
accessor_name = 'is' + UpperCase(group) + 'Type'
......@@ -2933,7 +2905,8 @@ def createTranslationLanguageAccessors(property_holder, property,
localizer = getattr(portal, 'Localizer', None)
if localizer is None:
warnings.warn("Localizer is missing. Accessors can not be generated.")
if not getattr(portal, '_v_bootstrapping', False):
warnings.warn("Localizer is missing. Accessors can not be generated.")
return
for language in localizer.get_languages():
......
......@@ -2,12 +2,17 @@
import sys
from Products.ERP5Type import Permissions
from Products.ERP5Type.Accessor.Constant import Getter as ConstantGetter
from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5Type.Base import Base as ERP5Base
from Products.ERP5Type.Base import PropertyHolder, initializePortalTypeDynamicWorkflowMethods
from Products.ERP5Type.Utils import createAllCategoryAccessors, createExpressionContext, UpperCase
from ExtensionClass import ExtensionClass, pmc_init_of
from zope.interface import classImplements
from ZODB.broken import Broken, PersistentBroken
from AccessControl import ClassSecurityInfo
from zLOG import LOG, WARNING, BLATHER
from portal_type_class import generatePortalTypeClass
......@@ -87,7 +92,7 @@ class GhostBaseMetaClass(ExtensionClass, AccessorHolderType):
InitGhostBase = GhostBaseMetaClass('InitGhostBase', (ERP5Base,), {})
class PortalTypeMetaClass(GhostBaseMetaClass):
class PortalTypeMetaClass(GhostBaseMetaClass, PropertyHolder):
"""
Meta class that is used by portal type classes
......@@ -114,6 +119,9 @@ class PortalTypeMetaClass(GhostBaseMetaClass):
if issubclass(type(parent), PortalTypeMetaClass):
PortalTypeMetaClass.subclass_register.setdefault(parent, []).append(cls)
cls.security = ClassSecurityInfo()
cls.workflow_method_registry = {}
cls.__isghost__ = True
super(GhostBaseMetaClass, cls).__init__(name, bases, dictionary)
......@@ -166,11 +174,14 @@ class PortalTypeMetaClass(GhostBaseMetaClass):
for attr in cls.__dict__.keys():
if attr not in ('__module__',
'__doc__',
'security',
'workflow_method_registry',
'__isghost__',
'portal_type'):
delattr(cls, attr)
# generate a ghostbase that derives from all previous bases
ghostbase = GhostBaseMetaClass('GhostBase', cls.__bases__, {})
cls.workflow_method_registry.clear()
cls.__bases__ = (ghostbase,)
cls.__isghost__ = True
cls.resetAcquisitionAndSecurity()
......@@ -191,6 +202,69 @@ class PortalTypeMetaClass(GhostBaseMetaClass):
raise AttributeError
def generatePortalTypeAccessors(cls, site):
createAllCategoryAccessors(site,
cls,
cls._categories,
createExpressionContext(site, site))
# make sure that category accessors from the portal type definition
# are generated, no matter what
# XXX this code is duplicated here, in PropertySheetTool, and in Base
# and anyway is ugly, as tuple-like registration does not help
for id, fake_accessor in cls._getPropertyHolderItemList():
if not isinstance(fake_accessor, tuple):
continue
if fake_accessor is PropertyHolder.WORKFLOW_METHOD_MARKER:
# Case 1 : a workflow method only
accessor = ERP5Base._doNothing
else:
# Case 2 : a workflow method over an accessor
(accessor_class, accessor_args, key) = fake_accessor
accessor = accessor_class(id, key, *accessor_args)
# Add the accessor to the accessor holder
setattr(cls, id, accessor)
portal_workflow = getattr(site, 'portal_workflow', None)
if portal_workflow is None:
if not getattr(site, '_v_bootstrapping', False):
LOG("ERP5Type.Dynamic", WARNING,
"Could not generate workflow methods for %s"
% cls.__name__)
else:
initializePortalTypeDynamicWorkflowMethods(cls, portal_workflow)
# portal type group methods, isNodeType, isResourceType...
from Products.ERP5Type.ERP5Type import ERP5TypeInformation
# XXX possible optimization:
# generate all methods on Base accessor holder, with all methods
# returning False, and redefine on portal types only those returning True,
# aka only those for the group they belong to
for group in ERP5TypeInformation.defined_group_list:
value = cls.__name__ in site._getPortalGroupedTypeSet(group)
accessor_name = 'is' + UpperCase(group) + 'Type'
setattr(cls, accessor_name, ConstantGetter(accessor_name, group, value))
cls.declareProtected(Permissions.AccessContentsInformation,
accessor_name)
from Products.ERP5Type.Cache import initializePortalCachingProperties
initializePortalCachingProperties(site)
# TODO in reality much optimization can be done for all
# PropertyHolder methods:
# - workflow methods are only on the MetaType erp5.portal_type method
# Iterating over the complete MRO is nonsense and inefficient
def _getPropertyHolderItemList(cls):
cls.loadClass()
result = PropertyHolder._getPropertyHolderItemList(cls)
for parent in cls.mro():
if parent.__module__ == 'erp5.accessor_holder':
for x in parent.__dict__.items():
if x[0] not in PropertyHolder.RESERVED_PROPERTY_SET:
result.append(x)
return result
def loadClass(cls):
"""
- mro before load:
......@@ -218,6 +292,7 @@ class PortalTypeMetaClass(GhostBaseMetaClass):
site = getSite()
try:
try:
class_definition = generatePortalTypeClass(site, portal_type)
except AttributeError:
LOG("ERP5Type.Dynamic", WARNING,
......@@ -238,11 +313,14 @@ class PortalTypeMetaClass(GhostBaseMetaClass):
for key, value in attribute_dict.iteritems():
setattr(klass, key, value)
# XXX disabled
#klass._categories = base_category_list
klass._categories = base_category_list
for interface in interface_list:
classImplements(klass, interface)
klass.generatePortalTypeAccessors(site)
except:
import traceback; traceback.print_exc()
finally:
ERP5Base.aq_method_lock.release()
......
......@@ -28,6 +28,7 @@
##############################################################################
import sys
import os
import inspect
from types import ModuleType
......@@ -291,6 +292,16 @@ def generatePortalTypeClass(site, portal_type_name):
erp5.accessor_holder,
property_sheet_tool)
if "Base" in property_sheet_set:
accessor_holder = None
# useless if Base Category is not yet here
if hasattr(erp5.accessor_holder, "Base Category"):
accessor_holder = _generateBaseAccessorHolder(
site,
erp5.accessor_holder)
if accessor_holder is not None:
accessor_holder_list.append(accessor_holder)
# XXX a hook to add per-portal type accessor holders maybe?
if portal_type_name == "Preference Tool":
accessor_holder = _generatePreferenceToolAccessorHolder(
......@@ -397,6 +408,8 @@ def initializeDynamicModules():
erp5.temp_portal_type = registerDynamicModule('erp5.temp_portal_type',
loadTempPortalTypeClass)
required_tool_list = [('portal_types', 'Base Type'),
('portal_property_sheets', 'BaseType')]
last_sync = -1
def synchronizeDynamicModules(context, force=False):
"""
......@@ -423,12 +436,63 @@ def synchronizeDynamicModules(context, force=False):
return
last_sync = cookie
LOG("ERP5Type.dynamic", 0, "Resetting dynamic classes")
import erp5
Base.aq_method_lock.acquire()
try:
migrated = False
for tool_id, line_id in required_tool_list:
# if the instance has no property sheet tool, or incomplete
# property sheets, we need to import some data to bootstrap
# (only likely to happen on the first run ever)
tool = getattr(portal, tool_id, None)
if tool is not None:
if getattr(tool, line_id, None) is None:
# tool exists, but is incomplete
portal._delObject(tool_id)
else:
# tool exists, Base Type is represented; probably OK
continue
if not migrated:
# XXX: if some portal types are missing, for instance
# if some Tools have no portal types, this is likely to fail with an
# error. On the other hand, we can't proceed without this change,
# and if we dont import the xml, the instance wont start.
portal.migrateToPortalTypeClass()
migrated = True
LOG('ERP5Site', INFO, 'importing transitional %s tool'
' from Products.ERP5.bootstrap to be able to load'
' core items...' % tool_id)
from Products.ERP5.ERP5Site import getBootstrapDirectory
bundle_path = os.path.join(getBootstrapDirectory(),
'%s.xml' % tool_id)
assert os.path.exists(bundle_path), 'Please update ERP5 product'
try:
tool = portal._importObjectFromFile(
bundle_path,
id=tool_id,
verify=False,
set_owner=False,
suppress_events=True)
from Products.ERP5.Document.BusinessTemplate import _recursiveRemoveUid
_recursiveRemoveUid(tool)
portal._setOb(tool_id, tool)
except:
import traceback; traceback.print_exc()
raise
if not getattr(portal, '_v_bootstrapping', False):
LOG('ERP5Site', INFO, 'Transition successful, please update your'
' business templates')
LOG("ERP5Type.dynamic", 0, "Resetting dynamic classes")
for class_name, klass in inspect.getmembers(erp5.portal_type,
inspect.isclass):
klass.restoreGhostState()
......
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