[Erp5-report] r39000 arnaud.fontaine - in /erp5/trunk/products/ERP5Type: Dynamic/ Tool/
nobody at svn.erp5.org
nobody at svn.erp5.org
Fri Oct 8 13:45:40 CEST 2010
Author: arnaud.fontaine
Date: Fri Oct 8 13:45:38 2010
New Revision: 39000
URL: http://svn.erp5.org?rev=39000&view=rev
Log:
Generate accessor holders and allow export/import for web-based Property sheets
Modified:
erp5/trunk/products/ERP5Type/Dynamic/portaltypeclass.py
erp5/trunk/products/ERP5Type/Tool/PropertySheetTool.py
Modified: erp5/trunk/products/ERP5Type/Dynamic/portaltypeclass.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/Dynamic/portaltypeclass.py?rev=39000&r1=38999&r2=39000&view=diff
==============================================================================
--- erp5/trunk/products/ERP5Type/Dynamic/portaltypeclass.py [utf8] (original)
+++ erp5/trunk/products/ERP5Type/Dynamic/portaltypeclass.py [utf8] Fri Oct 8 13:45:38 2010
@@ -1,16 +1,43 @@
+##############################################################################
+#
+# Copyright (c) 2010 Nexedi SARL and Contributors. All Rights Reserved.
+# Nicolas Dumazet <nicolas.dumazet at nexedi.com>
+# Arnaud Fontaine <arnaud.fontaine at nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsibility of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# guarantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+##############################################################################
-import dynamicmodule
-
-import lazyclass
import sys
import inspect
+
from types import ModuleType
+import dynamicmodule
+import lazyclass
+
from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5Type.Utils import setDefaultClassProperties
-
from Products.ERP5Type import document_class_registry, mixin_class_registry
-
from ExtensionClass import Base as ExtensionBase
from zLOG import LOG, ERROR, BLATHER
@@ -29,6 +56,24 @@ def _import_class(classpath):
import traceback; traceback.print_exc()
raise ImportError('Could not import document class %s' % classpath)
+def _create_accessor_holder_class(property_sheet_tool,
+ property_sheet_name):
+ """
+ If the given Property Sheet exists in portal_property_sheets, then
+ generate its accessor holder
+ """
+ try:
+ return property_sheet_tool.createPropertySheetAccessorHolder(
+ getattr(property_sheet_tool,
+ property_sheet_name))
+
+ except AttributeError:
+ # Not too critical
+ LOG("ERP5Type.Dynamic", ERROR,
+ "Ignoring missing Property Sheet " + property_sheet_name)
+
+ return None
+
def portal_type_factory(portal_type_name):
"""
Given a portal type, look up in Types Tool the corresponding
@@ -41,6 +86,7 @@ def portal_type_factory(portal_type_name
type_class = None
mixin_list = []
interface_list = []
+ accessor_holder_list = []
# two exceptions that cant be loaded from types tool:
if portal_type_name == "Base Type":
# avoid chicken and egg issue:
@@ -52,6 +98,14 @@ def portal_type_factory(portal_type_name
# When installing a BT, Business Templates are loaded
# before creating any Base Type object
type_class = "BusinessTemplate"
+ elif portal_type_name == "Base Category":
+ # 'elementary_type' is a Base Category needed for Standard
+ # Property and Acquired Property
+ type_class = "BaseCategory"
+ elif portal_type_name == "Category":
+ # 'elementary_type' sub-categories are needed for Standard
+ # Property and Acquired Property
+ type_class = "Category"
else:
from Products.ERP5.ERP5Site import getSite
site = getSite()
@@ -69,7 +123,7 @@ def portal_type_factory(portal_type_name
# was not migrated yet)
type_class = portal_type.getTypeClass()
- # But no such getter exist for Mixins and Interfaces:
+ # But no such getter exists for Mixins and Interfaces:
# in reality, we can live with such a failure
try:
mixin_list = portal_type.getTypeMixinList()
@@ -80,27 +134,52 @@ def portal_type_factory(portal_type_name
"Could not load interfaces or Mixins for portal type %s" \
% portal_type)
+ # Initialize Property Sheets accessor holders
+ import erp5.accessor_holder
+
+ # Get the web-based Property Sheets for this Portal Type
+ property_sheet_name_set = set(
+ portal_type.getNewStyleTypePropertySheetList() or [])
+
+ for property_sheet_name in property_sheet_name_set:
+ try:
+ # Get the already generated accessor holder
+ accessor_holder_list.append(getattr(erp5.accessor_holder,
+ property_sheet_name))
+
+ except AttributeError:
+ # Generate the accessor holder as it has not been done yet
+ accessor_holder_class = \
+ _create_accessor_holder_class(site.portal_property_sheets,
+ property_sheet_name)
+
+ if accessor_holder_class is not None:
+ setattr(erp5.accessor_holder, property_sheet_name,
+ accessor_holder_class)
+
+ accessor_holder_list.append(accessor_holder_class)
+
+ LOG("ERP5Type.Dynamic", BLATHER,
+ "Created accessor holder for %s" % property_sheet_name)
+
if type_class is not None:
type_class = document_class_registry.get(type_class)
if type_class is None:
raise AttributeError('Document class is not defined on Portal Type %s' % portal_type_name)
+ type_class = _import_class(type_class)
+
mixin_path_list = []
if mixin_list:
mixin_path_list = map(mixin_class_registry.__getitem__, mixin_list)
+ mixin_class_list = map(_import_class, mixin_path_list)
+
+ baseclasses = [type_class] + accessor_holder_list + mixin_class_list
+
+ LOG("ERP5Type.Dynamic", BLATHER,
+ "Portal type %s loaded with bases %s" \
+ % (portal_type_name, repr(baseclasses)))
- ##
- # XXX initialize interfaces here too
- # XXX adding accesor_holder for property sheets should be done here
- ##
-
- classpath_list = [type_class] + mixin_path_list
-
- baseclasses = map(_import_class, classpath_list)
-
- #LOG("ERP5Type.Dynamic", BLATHER,
- # "Portal type %s loaded with bases %s" \
- # % (portal_type_name, repr(baseclasses)))
return tuple(baseclasses), dict(portal_type=portal_type_name)
def initializeDynamicModules():
@@ -114,8 +193,9 @@ def initializeDynamicModules():
holds document classes that have no physical import path,
for example classes created through ClassTool that are in
$INSTANCE_HOME/Document
+ erp5.accessor_holder
+ holds accessors of web-based Property Sheet
"""
-
def portal_type_loader(portal_type_name):
"""
Returns a lazily-loaded "portal-type as a class"
@@ -126,9 +206,12 @@ def initializeDynamicModules():
sys.modules["erp5"] = erp5
erp5.document = ModuleType("erp5.document")
sys.modules["erp5.document"] = erp5.document
+ erp5.accessor_holder = ModuleType("erp5.accessor_holder")
+ sys.modules["erp5.accessor_holder"] = erp5.accessor_holder
portal_type_container = dynamicmodule.dynamicmodule('erp5.portal_type',
- portal_type_loader)
+ portal_type_loader)
+
erp5.portal_type = portal_type_container
def temp_portal_type_loader(portal_type_name):
@@ -166,7 +249,6 @@ def initializeDynamicModules():
erp5.temp_portal_type = dynamicmodule.dynamicmodule('erp5.temp_portal_type',
temp_portal_type_loader)
-
last_sync = 0
def synchronizeDynamicModules(context, force=False):
"""
@@ -180,7 +262,7 @@ def synchronizeDynamicModules(context, f
and send out an invalidation to other nodes
"""
return # XXX disabled for now
- LOG("ERP5Type.Dynamic", 0, "Resetting dynamic classes")
+ LOG("ERP5Type.Dynamic", BLATHER, "Resetting dynamic classes")
portal = context.getPortalObject()
@@ -196,8 +278,10 @@ def synchronizeDynamicModules(context, f
return
last_sync = cookie
- import erp5.portal_type
- for class_name, klass in inspect.getmembers(erp5.portal_type, inspect.isclass):
+ import erp5
+
+ for class_name, klass in inspect.getmembers(erp5.portal_type,
+ inspect.isclass):
ghostbase = getattr(klass, '__ghostbase__', None)
if ghostbase is not None:
for attr in klass.__dict__.keys():
@@ -206,3 +290,7 @@ def synchronizeDynamicModules(context, f
klass.__bases__ = ghostbase
type(ExtensionBase).__init__(klass, klass)
+ # Clear accessor holders of web-based Property Sheets
+ for accessor_name in erp5.accessor_holder.__dict__.keys():
+ if not accessor_name.startswith('__'):
+ delattr(erp5.accessor_holder, accessor_name)
Modified: erp5/trunk/products/ERP5Type/Tool/PropertySheetTool.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/Tool/PropertySheetTool.py?rev=39000&r1=38999&r2=39000&view=diff
==============================================================================
--- erp5/trunk/products/ERP5Type/Tool/PropertySheetTool.py [utf8] (original)
+++ erp5/trunk/products/ERP5Type/Tool/PropertySheetTool.py [utf8] Fri Oct 8 13:45:38 2010
@@ -1,6 +1,8 @@
##############################################################################
#
-# Copyright (c) 2010 Nicolas Dumazet <nicolas.dumazet at nexedi.com>
+# Copyright (c) 2010 Nexedi SARL and Contributors. All Rights Reserved.
+# Nicolas Dumazet <nicolas.dumazet at nexedi.com>
+# Arnaud Fontaine <arnaud.fontaine at nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
@@ -30,15 +32,27 @@ from Products.ERP5Type.Tool.BaseTool imp
from Products.ERP5Type import Permissions
from Products.ERP5Type.Accessor import Translation
from Products.CMFCore.utils import getToolByName
+from Products.CMFCore.Expression import Expression
+from Products.ERP5Type.Base import Base, PropertyHolder
+from Products.ERP5Type.Utils import setDefaultClassProperties, setDefaultProperties
+from zLOG import LOG, ERROR, BLATHER
+
+class AccessorHolder(object):
+ """
+ Base class of an accessor holder class for Property Sheets
+ """
+ pass
class PropertySheetTool(BaseTool):
- """Provides a configurable registry of property sheets
+ """
+ Provides a configurable registry of property sheets
"""
id = 'portal_property_sheets'
meta_type = 'ERP5 Property Sheet Tool'
portal_type = 'Property Sheet Tool'
+ # Declarative security
security = ClassSecurityInfo()
security.declareObjectProtected(Permissions.AccessContentsInformation)
@@ -49,4 +63,167 @@ class PropertySheetTool(BaseTool):
for object_ in getToolByName(self, 'Localizer').objectValues()
if object_.meta_type=='MessageCatalog']+
[Translation.TRANSLATION_DOMAIN_CONTENT_TRANSLATION]
- )
+ )
+
+ @staticmethod
+ def _guessFilesytemPropertyPortalType(attribute_dict):
+ """
+ Guess the Portal Type of a filesystem-based Property Sheet from
+ the attributes of the given property
+ """
+ for key in attribute_dict:
+ if key.startswith('acqui') or \
+ key in ('alt_accessor_id',
+ # Specific to 'content' type
+ 'portal_type',
+ 'translation_acquired_property'):
+ return 'Acquired Property'
+
+ return 'Standard Property'
+
+ security.declareProtected(Permissions.ModifyPortalContent,
+ 'createPropertySheetFromFilesystemClass')
+ def createPropertySheetFromFilesystemClass(self, klass):
+ """
+ Create a new Property Sheet in portal_property_sheets from a given
+ filesystem-based Property Sheet definition.
+
+ XXX: Implement constraints
+ """
+ new_property_sheet = self.newContent(id=klass.__name__,
+ portal_type='Property Sheet')
+
+ for attribute_dict in getattr(klass, '_properties', []):
+ # The property could be either a Standard or an Acquired
+ # Property
+ portal_type = self._guessFilesytemPropertyPortalType(attribute_dict)
+
+ # Create the new property and set its attributes
+ new_property = new_property_sheet.newContent(portal_type=portal_type)
+ new_property.importFromFilesystemDefinition(attribute_dict)
+
+ for category in getattr(klass, '_categories', []):
+ # A category may be a TALES Expression rather than a plain
+ # string
+ if isinstance(category, Expression):
+ new_category = new_property_sheet.newContent(
+ portal_type='Dynamic Category Property')
+
+ # Set the category TALES expression
+ new_category.importFromFilesystemDefinition(category)
+
+ else:
+ new_property_sheet.newContent(id=category,
+ portal_type='Category Property')
+
+ return new_property_sheet
+
+ security.declareProtected(Permissions.ManagePortal,
+ 'createAllPropertySheetsFromFilesystem')
+ def createAllPropertySheetsFromFilesystem(self):
+ """
+ Create Property Sheets in portal_property_sheets from _all_
+ filesystem Property Sheets
+
+ XXX: only meaningful for testing?
+ """
+ from Products.ERP5Type import PropertySheet
+
+ # Get all the filesystem Property Sheets
+ for name, klass in PropertySheet.__dict__.iteritems():
+ if name[0] == '_':
+ continue
+
+ if name not in self.portal_property_sheets:
+ LOG("Tool.PropertySheetTool", BLATHER,
+ "Creating %s in portal_property_sheets" % repr(name))
+
+ self.createPropertySheetFromFilesystemClass(klass)
+
+ else:
+ LOG("Tool.PropertySheetTool", BLATHER,
+ "%s already exists in portal_property_sheets" % repr(name))
+
+ security.declareProtected(Permissions.AccessContentsInformation,
+ 'exportPropertySheetToFilesystemDefinitionTuple')
+ def exportPropertySheetToFilesystemDefinitionTuple(self, property_sheet):
+ """
+ Export a given web-based Property Sheet to its filesystem
+ definition as tuple (properties, categories, constraints)
+
+ XXX: Implement constraints
+ """
+ properties = []
+ constraints = []
+ categories = []
+
+ for property in property_sheet.contentValues():
+ portal_type = property.getPortalType()
+
+ if portal_type == "Standard Property" or \
+ portal_type == "Acquired Property":
+ properties.append(property.exportToFilesystemDefinition())
+
+ elif portal_type == "Category Property":
+ categories.append(property.getId())
+
+ elif portal_type == "Dynamic Category Property":
+ categories.append(property.exportToFilesystemDefinition())
+
+ return (properties, categories, constraints, )
+
+ security.declarePrivate('createPropertySheetAccessorHolder')
+ def createPropertySheetAccessorHolder(self, property_sheet):
+ """
+ Create a new accessor holder from the given Property Sheet (the
+ accessors are created through a Property Holder)
+
+ XXX: Workflows?
+ """
+ property_holder = PropertyHolder()
+
+ definition_tuple = \
+ self.exportPropertySheetToFilesystemDefinitionTuple(property_sheet)
+
+ # Prepare the Property Holder
+ property_holder._properties, \
+ property_holder._categories, \
+ property_holder._constraints = definition_tuple
+
+ setDefaultClassProperties(property_holder)
+
+ try:
+ setDefaultProperties(property_holder, portal=self.getPortalObject())
+ except:
+ import traceback
+ LOG("Tool.PropertySheetTool", ERROR,
+ "Could not generate accessor holder class for %s: %s" % \
+ (property_sheet.getTitle(), traceback.format_exc()))
+
+ return None
+
+ # Create the new accessor holder class and set its module properly
+ accessor_holder_class = type(property_sheet.getId(),
+ (AccessorHolder,), {})
+
+ accessor_holder_class.__module__ = 'erp5.accessor_holder'
+
+ # Set all the accessors (defined by a tuple) from the Property
+ # Holder to the new accessor holder class (code coming from
+ # createAccessor in Base.PropertyHolder)
+ for id, fake_accessor in property_holder._getItemList():
+ if not isinstance(fake_accessor, tuple):
+ continue
+
+ if fake_accessor is ('Base._doNothing',):
+ # Case 1 : a workflow method only
+ accessor = Base._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(accessor_holder_class, id, accessor)
+
+ return accessor_holder_class
More information about the Erp5-report
mailing list