diff --git a/product/CMFActivity/ActivityTool.py b/product/CMFActivity/ActivityTool.py index 9f38e0e0f85098ea79efe1ebb0b23bdb0bb64769..6962db64f0e0db2a80eef65e30882761a0679c8e 100644 --- a/product/CMFActivity/ActivityTool.py +++ b/product/CMFActivity/ActivityTool.py @@ -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' } diff --git a/product/ERP5/Document/BusinessTemplate.py b/product/ERP5/Document/BusinessTemplate.py index 8de63306461d78b091d965a4c0974bb560c31887..34586ce97c22a4dc7c9f9a9e9507940b7c74fa6a 100644 --- a/product/ERP5/Document/BusinessTemplate.py +++ b/product/ERP5/Document/BusinessTemplate.py @@ -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': ( diff --git a/product/ERP5/ERP5Site.py b/product/ERP5/ERP5Site.py index 3764ed233a705e2dd6691ee3dd5d2f937eae63a6..b3f6abaa737f49e8e56dc9914029aceefc13ec1a 100644 --- a/product/ERP5/ERP5Site.py +++ b/product/ERP5/ERP5Site.py @@ -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 diff --git a/product/ERP5Form/Document/Preference.py b/product/ERP5Form/Document/Preference.py index b5e2fe17fcfddb30e66538a8a6cd42ddc31be107..5733f668828f29ff28335dbe861a5de9d74a6187 100644 --- a/product/ERP5Form/Document/Preference.py +++ b/product/ERP5Form/Document/Preference.py @@ -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) diff --git a/product/ERP5Form/PreferenceTool.py b/product/ERP5Form/PreferenceTool.py index 68716a05ffdfe2acd752c8e4328835bd10c5b98f..7d0b6f608e0fa5f6ddc06d72db939f18883990bb 100644 --- a/product/ERP5Form/PreferenceTool.py +++ b/product/ERP5Form/PreferenceTool.py @@ -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""" diff --git a/product/ERP5Type/Base.py b/product/ERP5Type/Base.py index 1d5564d0f00b01b2bea631b8eeb7ddeeffead0c8..0c0e33e2d133d46a69d6a53fdb72045cb4f546a6 100644 --- a/product/ERP5Type/Base.py +++ b/product/ERP5Type/Base.py @@ -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 diff --git a/product/ERP5Type/ERP5Type.py b/product/ERP5Type/ERP5Type.py index c0107880feacc3a0e1aff49a0dc7db0c186a643b..7e7d1c42cc7cdc564207600f81d4af9629497d6e 100644 --- a/product/ERP5Type/ERP5Type.py +++ b/product/ERP5Type/ERP5Type.py @@ -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') diff --git a/product/ERP5Type/TranslationProviderBase.py b/product/ERP5Type/TranslationProviderBase.py index 71361fdcde282033f67b17bd3fd8394ac2c9bd7b..baae16869226050a8904d70b731b3dd6a33860c8 100644 --- a/product/ERP5Type/TranslationProviderBase.py +++ b/product/ERP5Type/TranslationProviderBase.py @@ -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') diff --git a/product/ERP5Type/Utils.py b/product/ERP5Type/Utils.py index e3fff27b3e59f2ed1fa35ede26b679ffb6b23d6e..8eff16a5e2d40523a99823da7ea1c0869daf5e34 100644 --- a/product/ERP5Type/Utils.py +++ b/product/ERP5Type/Utils.py @@ -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(): diff --git a/product/ERP5Type/dynamic/lazy_class.py b/product/ERP5Type/dynamic/lazy_class.py index 24cde15756f164d66bad7c5a95d0d2bc76a22362..e80dd93b3965a391234388fde416300c691bf51f 100644 --- a/product/ERP5Type/dynamic/lazy_class.py +++ b/product/ERP5Type/dynamic/lazy_class.py @@ -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() diff --git a/product/ERP5Type/dynamic/portal_type_class.py b/product/ERP5Type/dynamic/portal_type_class.py index d2eb0c2bdbb9d580ef70feb84ec3850199cf75ba..468ae162829113c1c700da2e3043d458414a8a22 100644 --- a/product/ERP5Type/dynamic/portal_type_class.py +++ b/product/ERP5Type/dynamic/portal_type_class.py @@ -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()