Most Powerful Open Source ERP

Technical Note on Property Sheets

Information on what property sheets are and how to use them
  • Last Update:2016-06-29
  • Version:001
  • Language:en

Document explaining the use of property sheets to help developers manage and create their own property sheets when working with ERP5.

Table of Contents

How To Use Property Sheets

The need

is very rare. Most properties are already defined in exising property sheets, of which there is plenty. If you don't see a property you need, look closer, and think about a more generic name to it. If you are 100% sure it is n ot there because it is very specific, then create a new Property Sheet, through Portal Classes or directly in INSTANCE_HOME/PropertySheets directory.

Property definitions

Let's look at an excerpt from Person property sheet (the parts which do not use programmable acquisition, which is described later):

class Person:
  """
    Person properties and categories
  """
  _properties = (
    { 'id'         : 'prefix'
    , 'description': 'Name prefix.'
    , 'type'       : 'string'
    , 'mode'       : 'w'
    },
    )

  _categories = ('nationality', )

This is pretty straightforward: any person has a property "prefix" which is string (mode is required but not used - whether you can set it or no depends on security). Type is meant rather for conversion then checking - you can set it to 1, but you will get '1' anyway.

Every person has also a base category "nationality", so it can be classified to one of subcategories of portal_categories/nationality.

Accessors

The properties and categories define the data model, and also create and API. From the above definitions, the Person gets a number of methods:

.setPrefix
.getPrefix
.setNationality
.setNationalityList
.getNationality
.getNationalityList
.getNationalityTitle
.getNationalityTitleList

Which do what their names suggest. Hint: if you set a list of nationalities whether it makes any sense or not is out of scope of this article), getNationality will return the first one.

Hooking up to portal types

"The core way" is to add it to class definition - Products/ERP5/Document/Person.py has it:

property_sheets = ( PropertySheet.Base
                  , PropertySheet.XMLObject
                  , PropertySheet.CategoryCore
                  , PropertySheet.DublinCore
                  , PropertySheet.Reference
                  , PropertySheet.Person
                  , PropertySheet.Mapping
                  , PropertySheet.Task
                  )

There is also "the ZMI way" - in portal_types there is a box where you can choose additional property sheets for your type definition. This has the same effect.

Useful tricks

Attribute with a default value equal to another attribute

Let's say we have a form which displays two names of something: "long_name" and "short_name". short_name should by default be equal to long_name, unless the user overwrites it. We could do it by adding formulas to form fields, or by using interaction workflow etc, but the simplest and the most generic way would be to define a property in a property sheet. We can achieve this effect by using dynamic acquisition with masking:

class TwoNames:
    """
        Defines 
    """

    _properties = (
        {   'id'          : 'long_name',
            'description' : 'normal name',
            'type'        : 'string',
            'mode'        : ''},

        {   'id'          : 'short_name',
            'description' : 'nickname',
            'type'        : 'string',
            'acquisition_base_category':('object',),
            'acquisition_portal_type':('Type',),
            'acquisition_accessor_id':'getLongName',
            'acquisition_mask_value':1,
            'alt_accessor_id' : ('getLongName',),
            'mode'        : '' },
    )

This way, the property short_name if not set will acquire value from the object itself, using getLongName accessor; and if set, it will "mask", or overwrite, the acquired value.

All magic is done by defining alt_accessor_id to methods tuple, which shall get value if none is set on object.

Programmable Acquisition in Property Sheets

Using special techniques it is possible to extend Zope's acquisition by special definitions in Property Sheets. All information are provided on another wikipage.

Default attribute

(DefProp.py)

class DefProp:
  """
    Property with default value
  """
  _properties = (
    {
      'id'          : 'def_prop',
      'description' : 'Property with default value',
      'type'        : 'string',
      'mode'        : 'w',
      'default'     : '',
    },
  )
  _categories = ( )

Above example will create property def_prop, which default value will be '' - empty string. You may access it by object.getProperty('def_prop') or object.getDefProp().

Keep in mind, that user interface in ERP5 is setting properties to None, when user clear field on form and not - as some developers think - to its default value. Look at this code snippet:

# associate DefProp PropertSheet with portal type,
# create new object of that type
# create my_def_prop StringField in form associated with that portal type
# and now fetch def_prop from created object:

print repr(object.getDefProp()) # will print ''

# now by user interface set def_prop to 'aaaa', and:

print repr(object.getDefProp()) # will print 'aaaa'

# and now using UI reset def_prop value to empty one, and:

print repr(object.getDefProp()) # will print None

Own getter for special attribute

Let assume that your have two properties on object: colour_reference and size_reference. But you'd like to have special accessor getSizeCoulourReference, which will be composition of those two.

The simplest approach would be to modify object class and add new accessor. But if that class is defined in ERP5 code, than you'd have to modify this code, and updates will be much more difficult.

So define property in such way:

from Products.CMFCore.Expression import Expression

class SizeColourComposition:
  """
      Size colour composition reference.
  """
  _properties = (
    {
      'id'          : 'size_colour_reference',
      'description' : 'Reference made as size-colour composition',
      'type'        : 'string',
      'mode'        : 'w',
      'acquisition_base_category' : ('object',),
      'acquisition_portal_type' : Expression('python: []'),
      'acquisition_accessor_id' : 'getSizeColourReference',
      'alt_accessor_id' : ('Object_getSizeColourReference',),
      'acquisition_mask_value' : 0,
      'default'     : '',
    },
  )
  _categories = ( )

And Object_getSizeColourReference is a Script (Python) in your ZODB, which do all that magic:

return 's:%s c:%s'%(context.getSizeReference(),context.getColourReference())

Try to invoke object/getSizeColourReference - voilà - you have composition of size and colour.

/!\ Note:The acquisition_mask_value is set to 0 - this makes sure the property getter will ALWAYS call the script, event if you explicitly set property size_colour_reference.

Replacing an accessor

A propertysheet can override a property that have previously been added by another propertysheet. This will happen if you specify override=1 in the property sheet definition. For example, you can override getTitle for a given portal type with a property sheet similar to this one:

class FunkyTitle:

     _properties = (
         {   'id'          : 'title',
             'type'        : 'string',
             'default'     : '',
             'acquisition_base_category'     : (),
             'acquisition_portal_type'       : (),
             'acquisition_copy_value'        : 0,
             'acquisition_mask_value'        : 1,
             'acquisition_depends'           : None,
             'acquisition_accessor_id'       : 'getTitle',
             'acquisition_depends'           : None,
             'alt_accessor_id'               : ('Base_getFunkyTitle', ),
             'override'    : 1,
             'mode'        : 'w' },
     )

The general problem with override is that it can only be used once per property, if two propertysheets wants to override the same accessor, the behaviour will be undefined (because it depends on accessors generation order). It's better to avoid using override on stock propertysheets, so that properties can still be overriden by customisation propertysheets if needed.

Related Articles