From bb176d5059bb98bdc90c2348fae93eaead24abac Mon Sep 17 00:00:00 2001 From: Arnaud Fontaine <arnaud.fontaine@nexedi.com> Date: Wed, 2 Mar 2011 10:53:51 +0000 Subject: [PATCH] Allow a StandardProperty to generate its accessors without relying on Utils setDefaultProperties git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@43881 20353a03-c40f-0410-a6d1-a30d3c3de9de --- product/ERP5Type/Core/StandardProperty.py | 595 +++++++++++++++++++++- 1 file changed, 570 insertions(+), 25 deletions(-) diff --git a/product/ERP5Type/Core/StandardProperty.py b/product/ERP5Type/Core/StandardProperty.py index b08758a596..7777215891 100644 --- a/product/ERP5Type/Core/StandardProperty.py +++ b/product/ERP5Type/Core/StandardProperty.py @@ -30,12 +30,31 @@ from AccessControl import ClassSecurityInfo from Products.CMFCore.Expression import Expression from Products.ERP5Type import Permissions, PropertySheet from Products.ERP5Type.XMLObject import XMLObject -from Products.ERP5Type.Accessor.Base import Getter as BaseGetter + +from zLOG import LOG, WARNING, INFO +from Products.ERP5Type.Accessor import Base, List, Alias, Translation +from Products.ERP5Type.Accessor.TypeDefinition import type_definition, list_types +from Products.ERP5Type.Utils import UpperCase, createExpressionContext, \ + evaluateExpressionFromString from Products.ERP5Type.id_as_reference import IdAsReferenceMixin class StandardProperty(IdAsReferenceMixin('_property'), XMLObject): """ - Define an Acquired Property Document for a ZODB Property Sheet + Define a Standard Property Document for a ZODB Property Sheet + + A Standard Property contains the following attributes: + - reference: string + - description: string + - elementary_type: string + - storage_id: string + - multivalued: boolean (default: False) + - property_default: TALES Expression as a string + - range: boolean (default: False) + - preference: boolean (default: False) + - read_permission: string (default: Permissions.AccessContentsInformation) + - write_permission: string (default: Permissions.ModifyPortalContent) + - translatable: boolean (default: False) + - translation_domain: string """ meta_type = 'ERP5 Standard Property' portal_type = 'Standard Property' @@ -64,8 +83,8 @@ class StandardProperty(IdAsReferenceMixin('_property'), XMLObject): # There is no need to define the setter as this static definition of # the getter is only meaningful for the Standard Properties defined # within an Standard Property. - getDescription = BaseGetter('getDescription', 'description', 'string', - default='') + getDescription = Base.Getter('getDescription', 'description', 'string', + default='') def getElementaryType(self): """ @@ -78,38 +97,41 @@ class StandardProperty(IdAsReferenceMixin('_property'), XMLObject): return getattr(self, 'elementary_type', None) - getStorageId = BaseGetter('getStorageId', 'storage_id', 'string') + # The following getters have been defined to address bootstrap + # issues + getStorageId = Base.Getter('getStorageId', 'storage_id', 'string') - getMultivalued = BaseGetter('getMultivalued', 'multivalued', 'boolean', - default=False) + getMultivalued = Base.Getter('getMultivalued', 'multivalued', 'boolean', + default=False) # Define as a TALES expression string, use for Expression # instanciation when exporting the property to the filesystem # definition - getPropertyDefault = BaseGetter('getPropertyDefault', 'property_default', - 'string') + getPropertyDefault = Base.Getter('getPropertyDefault', 'property_default', + 'string') - getRange = BaseGetter('getRange', 'range', 'boolean', default=False) + getRange = Base.Getter('getRange', 'range', 'boolean', default=False) - getPreference = BaseGetter('getPreference', 'preference', 'boolean', - default=False) + getPreference = Base.Getter('getPreference', 'preference', 'boolean', + default=False) - getReadPermission = BaseGetter( + getReadPermission = Base.Getter( 'getReadPermission', 'read_permission', 'string', default=Permissions.AccessContentsInformation) - getWritePermission = BaseGetter('getWritePermission', - 'write_permission', - 'string', - default=Permissions.ModifyPortalContent) + getWritePermission = Base.Getter('getWritePermission', + 'write_permission', + 'string', + default=Permissions.ModifyPortalContent) - getTranslatable = BaseGetter('getTranslatable', 'translatable', 'boolean', - default=False) + getTranslatable = Base.Getter('getTranslatable', 'translatable', 'boolean', + default=False) - getTranslationDomain = BaseGetter('getTranslationDomain', - 'translation_domain', - 'string') + getTranslationDomain = Base.Getter('getTranslationDomain', + 'translation_domain', + 'string') + # TODO: REMOVE @staticmethod def _getExpressionFromString(expression_string): """ @@ -120,11 +142,15 @@ class StandardProperty(IdAsReferenceMixin('_property'), XMLObject): return Expression(expression_string) + # TODO: REMOVE security.declareProtected(Permissions.AccessContentsInformation, 'exportToFilesystemDefinition') def exportToFilesystemDefinition(self): """ Return the filesystem definition of this ZODB property + + NOTE: Only meaningful for testing export of filesystem Property + Sheet to the ZODB """ property_default_value = self._getExpressionFromString(self.getPropertyDefault()) @@ -143,11 +169,519 @@ class StandardProperty(IdAsReferenceMixin('_property'), XMLObject): 'translatable': self.getTranslatable(), 'translation_domain': self.getTranslationDomain()} + @classmethod + def _asPropertyMap(cls, property_dict): + """ + Return the Zope definition of this ZODB property, as used by the + PropertyManager (for the ZMI for example). + + ERP5 _properties and Zope _properties are somehow different. The + id is converted to the Zope standard - we keep the original id as + base_id. + + @param property_dict: ZODB property dict + @type property_dict: dict + @return: PropertyManager definition + @rtype: dict + """ + property_dict['type'] = property_dict.pop('elementary_type') + property_dict['default'] = property_dict.pop('property_default') + + property_dict['id'] = property_dict.pop('reference') + + # In case, this property is a list, then display it as a list + if property_dict['type'] in list_types or property_dict['multivalued']: + property_dict['base_id'] = property_dict['id'] + property_dict['id'] = property_dict['id'] + '_list' + + return property_dict + + @staticmethod + def _applyDefinitionFormatDictOnAccessorHolder(reference, + definition_dict, + accessor_holder, + argument_list, + permission): + """ + Apply a definition dict, a format string defining the accessor + name as the key (formatted with the given reference) and the + accessor class to be used as the value, on the given accessor + holder class. + + The accessor class given in the definition dict is instanciated + with the given argument list and the the accessor is protected + using the given permission (which may be either read if it is a + getter or tester, or write if it is a setter), it is then + registered on the accessor holder. + + For most cases, the reference is used in the accessor name but + there are exceptions, such as translation accessors. + + @param reference: Reference to be used to format accessor name + @type reference: str + @param definition_dict: Definition of accessors being created + @type definition_dict: dict + @param accessor_holder: Accessor holder to applied the accessors on + @type accessor_holder: Products.ERP5Type.dynamic.accessor_holder.AccessorHolderType + @param argument_list: Arguments to be given to the accessor class constructor + @type argument_list: list + @param permission: Permission to be applied on the accessor + @type permission: str + """ + uppercase_reference = UpperCase(reference) + for format, klass in definition_dict.iteritems(): + name = format % uppercase_reference + + instance = klass(name, reference, *argument_list) + accessor_holder.registerAccessor(instance, permission) + + # Public setters actually just calls the private one and then + # perform a re-indexing + if name.startswith('_set'): + instance = Alias.Reindex(name[1:], name) + accessor_holder.registerAccessor(instance, permission) + + @classmethod + def _applyRangeOnAccessorHolder(cls, + property_dict, + accessor_holder, + kind, + portal): + """ + Apply range accessors. + + @param property_dict: Property to generate getter for + @type property_dict: dict + @param accessor_holder: Accessor holder to applied the accessors on + @type accessor_holder: Products.ERP5Type.dynamic.accessor_holder.AccessorHolderType + @param kind: 'min' or 'max' + @type kind: string + @param portal: Portal object + @type portal: Products.ERP5.ERP5Site.ERP5Site + """ + property_dict['reference'] = '%s_range_%s' % (property_dict['reference'], + kind) + + # Override storage_id to not store the value on the same attribute + # as the "normal" accessor + property_dict['storage_id'] = None + + # Set range to False to avoid infinite recursion upon + # applyDefinitionOnAccessorHolder call + property_dict['range'] = False + + cls.applyDefinitionOnAccessorHolder(property_dict, + accessor_holder, + portal, + do_register=False) + + _translation_language_getter_definition_dict = { + 'get%s': Translation.TranslatedPropertyGetter, + '_baseGet%s': Translation.TranslatedPropertyGetter + } + + _translation_language_getter_definition_dict = { + 'get%s': Translation.TranslatedPropertyGetter, + '_baseGet%s': Translation.TranslatedPropertyGetter + } + + _translation_language_tester_definition_dict = { + 'has%s': Translation.TranslatedPropertyTester + } + + _translation_language_setter_definition_dict = { + '_set%s': Translation.TranslationPropertySetter + } + + @classmethod + def _applyTranslationLanguageOnAccessorHolder(cls, + property_dict, + accessor_holder, + portal): + """ + Apply translation language accessors. + + @param property_dict: Property to generate getter for + @type property_dict: dict + @param accessor_holder: Accessor holder to applied the accessors on + @type accessor_holder: Products.ERP5Type.dynamic.accessor_holder.AccessorHolderType + @param portal: Portal object + @type portal: Products.ERP5.ERP5Site.ERP5Site + """ + try: + localizer = portal._getOb('Localizer') + except AttributeError: + if not getattr(portal, '_v_bootstrapping', False): + LOG("ERP5Type.Core.StandardProperty", WARNING, + "Localizer is missing. Accessors can not be generated") + + return + + # Apply language-specific accessors + for language in localizer.get_languages(): + translation_language_id = '%s_translated_%s' % \ + (language.replace('-', '_'), + property_dict['reference']) + + # Prepare accessor arguments for getters + getter_argument_list = (property_dict['reference'], + property_dict['elementary_type'], + language, + property_dict['property_default']) + + cls._applyDefinitionFormatDictOnAccessorHolder( + translation_language_id, + cls._translation_language_getter_definition_dict, + accessor_holder, + getter_argument_list, + property_dict['read_permission']) + + # Prepare accessor arguments for testers and setters + tester_setter_argument_list = (property_dict['reference'], + property_dict['elementary_type'], + language) + + cls._applyDefinitionFormatDictOnAccessorHolder( + translation_language_id, + cls._translation_language_tester_definition_dict, + accessor_holder, + tester_setter_argument_list, + property_dict['read_permission']) + + cls._applyDefinitionFormatDictOnAccessorHolder( + translation_language_id, + cls._translation_language_setter_definition_dict, + accessor_holder, + tester_setter_argument_list, + property_dict['write_permission']) + + _primitive_getter_definition_dict = { + 'get%s': Base.Getter, + '_baseGet%s': Base.Getter + } + + _list_getter_definition_dict = { + 'get%s': List.Getter, + '_baseGet%s': List.Getter, + 'getDefault%s': List.DefaultGetter, + '_baseGetDefault%s': List.DefaultGetter, + 'get%sList': List.ListGetter, + '_baseGet%sList': List.ListGetter, + 'get%sSet': List.SetGetter, + '_baseGet%sSet': List.SetGetter + } + + @classmethod + def _applyGetterDefinitionDictOnAccessorHolder(cls, + property_dict, + accessor_holder): + """ + Apply getters for the given property on the given accessor holder. + This method is overriden in AcquiredProperty for example to add + accessors specific to Acquired Properties. + + @param property_dict: Property to generate getter for + @type property_dict: dict + @param accessor_holder: Accessor holder to applied the accessors on + @type accessor_holder: Products.ERP5Type.dynamic.accessor_holder.AccessorHolderType + + @see _applyDefinitionFormatDictOnAccessorHolder + """ + argument_list = (property_dict['elementary_type'], + property_dict['property_default'], + property_dict['storage_id']) + + if property_dict['elementary_type'] in list_types or \ + property_dict['multivalued']: + definition_dict = cls._list_getter_definition_dict + else: + definition_dict = cls._primitive_getter_definition_dict + + cls._applyDefinitionFormatDictOnAccessorHolder( + property_dict['reference'], definition_dict, accessor_holder, + argument_list, property_dict['read_permission']) + + _list_setter_definition_dict = { + '_set%s': List.Setter, + '_baseSet%s': List.Setter, + '_setDefault%s': List.DefaultSetter, + '_baseSetDefault%s': List.DefaultSetter, + '_set%sList': List.ListSetter, + '_baseSet%sList': List.ListSetter, + '_set%sSet': List.SetSetter, + '_baseSet%sSet': List.SetSetter + } + + _primitive_setter_definition_dict = { + '_set%s': Base.Setter, + '_baseSet%s': Base.Setter + } + + @classmethod + def _applySetterDefinitionDictOnAccessorHolder(cls, + property_dict, + accessor_holder): + """ + Apply setters for the given property on the given accessor holder. + + @param property_dict: Property to generate getter for + @type property_dict: dict + @param accessor_holder: Accessor holder to applied the accessors on + @type accessor_holder: Products.ERP5Type.dynamic.accessor_holder.AccessorHolderType + + @see _applyGetterDefinitionDictOnAccessorHolder + """ + argument_list = (property_dict['elementary_type'], + property_dict['storage_id']) + + if property_dict['elementary_type'] in list_types or \ + property_dict['multivalued']: + definition_dict = cls._list_setter_definition_dict + else: + definition_dict = cls._primitive_setter_definition_dict + + cls._applyDefinitionFormatDictOnAccessorHolder( + property_dict['reference'], definition_dict, accessor_holder, + argument_list, property_dict['write_permission']) + + _tester_definition_dict = { + 'has%s': Base.Tester, + '_baseHas%s': Base.Tester, + 'has%sList': List.Tester, + '_baseHas%sList': List.Tester, + 'hasDefault%s': List.Tester, + '_baseHasDefault%s': List.Tester + } + + _boolean_definition_dict = { + 'is%s': Base.Getter, + '_baseIs%s': Base.Getter + } + + @classmethod + def _applyTesterDefinitionDictOnAccessorHolder(cls, + property_dict, + accessor_holder): + """ + Apply testers and boolean accessors for the given property on the + given accessor holder. + + @param property_dict: Property to generate getter for + @type property_dict: dict + @param accessor_holder: Accessor holder to applied the accessors on + @type accessor_holder: Products.ERP5Type.dynamic.accessor_holder.AccessorHolderType + + @see _applyGetterDefinitionDictOnAccessorHolder + """ + tester_argument_list = (property_dict['elementary_type'], + property_dict['storage_id']) + + cls._applyDefinitionFormatDictOnAccessorHolder( + property_dict['reference'], cls._tester_definition_dict, accessor_holder, + tester_argument_list, property_dict['read_permission']) + + boolean_argument_list = (property_dict['elementary_type'], + property_dict['property_default'], + property_dict['storage_id']) + + cls._applyDefinitionFormatDictOnAccessorHolder( + property_dict['reference'], cls._boolean_definition_dict, accessor_holder, + boolean_argument_list, property_dict['read_permission']) + + _translated_getter_definition_dict = { + 'get%s': Translation.TranslatedPropertyGetter, + '_baseGet%s': Translation.TranslatedPropertyGetter + } + + security.declareProtected(Permissions.ModifyPortalContent, + 'applyDefinitionOnAccessorHolder') + @classmethod + def applyDefinitionOnAccessorHolder(cls, + property_dict, + accessor_holder, + portal, + do_register=True): + """ + Apply getters, setters and testers for a list property or a + primitive property. + + This class method may be called to apply a property dictionnary on + an accessor holder. While applyOnAccessorHolder is commonly used + to apply a property on an *existing* ZODB Property Sheet, this + method can be used to apply accessors from a Property not defined + in a ZODB Property Sheet. + + The property dictionnary must define all the attributes listed in + the class docstring. + + The TALES Expression in the given property dict are considered to + have been already evaluated, as performed through + applyOnAccessorHolder by asDict method. + + @param property_dict: Property to generate getter for + @type property_dict: dict + @param accessor_holder: Accessor holder to applied the accessors on + @type accessor_holder: Products.ERP5Type.dynamic.accessor_holder.AccessorHolderType + @param portal: Portal object + @type portal: Products.ERP5.ERP5Site.ERP5Site + @param do_register: Register the property in the Zope property map + @type do_register: bool + """ + # Some attributes are required to generate accessors, if they have + # not been set properly, then don't generate them at all for this + # Property + if property_dict['reference'] is None or \ + property_dict['elementary_type'] is None or \ + property_dict['elementary_type'] not in type_definition: + raise ValueError("Invalid type or reference") + + # Create range accessors if relevant + if property_dict['range']: + for kind in ('min', 'max'): + cls._applyRangeOnAccessorHolder(property_dict.copy(), + accessor_holder, kind, portal) + + # Create translation accessors if relevant + if property_dict['translatable']: + translated_property_dict = property_dict.copy() + + if translated_property_dict['property_default'] is None: + translated_property_dict['property_default'] = '' + + # Make accessors such as getTranslatedProperty + translated_reference = 'translated_' + property_dict['reference'] + + argument_list = (translated_property_dict['reference'], + translated_property_dict['elementary_type'], + None, + translated_property_dict['property_default']) + + cls._applyDefinitionFormatDictOnAccessorHolder( + translated_reference, cls._translated_getter_definition_dict, + accessor_holder, argument_list, + translated_property_dict['read_permission']) + + cls._applyTranslationLanguageOnAccessorHolder(translated_property_dict, + accessor_holder, portal) + + # make accessor to translation_domain + # first create default one as a normal property + translation_domain_reference = translated_property_dict['reference'] + \ + '_translation_domain' + + translation_domain_property_dict = { + 'reference': translation_domain_reference, + 'elementary_type': 'string', + 'property_default': '', + 'multivalued': False, + 'storage_id': None, + 'range': False, + 'translatable': False, + 'read_permission': translated_property_dict['read_permission'], + 'write_permission': translated_property_dict['write_permission']} + + # This will always be a StandardProperty, so avoid calling + # super() here + StandardProperty.applyDefinitionOnAccessorHolder( + translation_domain_property_dict, accessor_holder, portal, + do_register=False) + + # Then override getPropertyTranslationDomain accessor + accessor = Translation.PropertyTranslationDomainGetter( + 'get' + UpperCase(translation_domain_reference), + translation_domain_reference, 'string', + property_dict['translation_domain']) + + accessor_holder.registerAccessor( + accessor, translated_property_dict['read_permission']) + + # After applying specific getters, setters and testers, apply + # common getters, setters and testers + cls._applyGetterDefinitionDictOnAccessorHolder(property_dict, + accessor_holder) + + cls._applySetterDefinitionDictOnAccessorHolder(property_dict, + accessor_holder) + + cls._applyTesterDefinitionDictOnAccessorHolder(property_dict, + accessor_holder) + + # By default, register the property as a Zope property map, by + # adding it to _properties, which will be later used by + # PropertyManager + if do_register: + property_map = cls._asPropertyMap(property_dict) + if property_map: + accessor_holder._properties.append(property_map) + + security.declareProtected(Permissions.AccessContentsInformation, + 'asDict') + def asDict(self, expression_context=None): + """ + Convert the current property to a dict, which is then applied on + the accessor holder. + + @param expression_context: Expression context for TALES Expression + @type expression_context: Products.PageTemplates.Expressions.ZopeContext + @return: The current property as a dict + @rtype: dict + """ + # If no expression context has been given, create one, meaningful + # when being called from the browser for example + if expression_context is None: + expression_context = createExpressionContext(self.getPortalObject()) + + property_default = evaluateExpressionFromString(expression_context, + self.getPropertyDefault()) + + return {'reference': self.getReference(), + 'description': self.getDescription(), + 'elementary_type': self.getElementaryType(), + 'storage_id': self.getStorageId(), + 'multivalued': self.getMultivalued(), + 'property_default': property_default, + 'range': self.getRange(), + 'preference': self.getPreference(), + 'read_permission': self.getReadPermission(), + 'write_permission': self.getWritePermission(), + 'translatable': self.getTranslatable(), + 'translation_domain': self.getTranslationDomain()} + + security.declareProtected(Permissions.ModifyPortalContent, + 'applyOnAccessorHolder') + def applyOnAccessorHolder(self, + accessor_holder, + expression_context, + portal): + """ + Apply the ZODB Property to the given accessor holder + + @param accessor_holder: Accessor holder to applied the accessors on + @type accessor_holder: Products.ERP5Type.dynamic.accessor_holder.AccessorHolderType + @param expression_context: Expression context for TALES Expression + @type expression_context: Products.PageTemplates.Expressions.ZopeContext + @param portal: Portal object + @type portal: Products.ERP5.ERP5Site.ERP5Site + + @see applyDefinitionOnAccessorHolder + """ + self.applyDefinitionOnAccessorHolder(self.asDict(expression_context), + accessor_holder, + portal) + @classmethod def _convertFromFilesystemPropertyDict(cls, filesystem_property_dict): """ Convert a property dict coming from a Property Sheet on the - filesystem to a ZODB property dict + filesystem to a ZODB property dict. + + This method is just kept for backward-compatibility with + filesystem Property Sheets used before ZODB Property Sheets. + + @param filesystem_property_dict: Filesystem property definition + @type filesystem_property_dict: dict + @return: ZODB property definition + @rtype: dict """ # Prepare a dictionnary of the ZODB property zodb_property_dict = {} @@ -173,12 +707,23 @@ class StandardProperty(IdAsReferenceMixin('_property'), XMLObject): return zodb_property_dict - security.declareProtected(Permissions.AccessContentsInformation, + security.declareProtected(Permissions.ModifyPortalContent, 'importFromFilesystemDefinition') @classmethod def importFromFilesystemDefinition(cls, context, filesystem_property_dict): """ - Set attributes from the filesystem definition of a property + Create a new property on the given context from the given + filesystem definition dict. + + This method is just kept for backward-compatibility with + filesystem Property Sheets used before ZODB Property Sheets. + + @param context: Context to create the property in + @type context: Products.ERP5Type.Core.PropertySheet + @param filesystem_property_dict: Filesystem definition + @param filesystem_property_dict: dict + @return: The new Standard Property + @rtype: StandardProperty """ return context.newContent( portal_type=cls.portal_type, -- 2.30.9