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
not there because it is very specific, then create a new Property Sheet, through
Portal Classes or directly in INSTANCE_HOME/PropertySheets directory.
Table of Contents
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
HowTo Page.
Default attribute¶
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¶