Implement Archetypes ComputedField and ComputedWidget on your Product and reference other Fields
Motivation
There are many reasons why this how-to exists:
- Almost no Archetypes examples using ComputedField and ComputedWidget
- We want our product to process some data on itself, but reload isn't a matter of concern
- We love PZP (Python-Zope-Plone)!
What do we need?
- A Plone installation
- A nice text editor (my wintel box runs SciTE)
- Some product (for real dummies like me, try http://plone.org/documentation/tutorial/anonymously-adding-custom-content-types-with-argouml-and-archgenxml/view)
What we are going to achieve?
- Make a page process it's own information
Let's
say you created a product, maybe using ArgoUML (an UML editor) and
ArchGenXML. One thing you might realize it's missing on creating UML's
is that: we create classes (Plone products), their types are also
classes (Archetypes' fields and widgets) and Attributes (Fields and
Widgets' properties) as TD's (tagged data) for Archetypes' types, but
we have no methods!
But we could do more if we inserted
code: classes are made of attributes and methods (code). But as UML
editors are not that Python friendly, we do that by hand.
So, how?
If
you already have navigated the path of a product, you've stumbled on
some source files (.py) inside, so take some time to read their source (Read the source, Luke!). Probably you've seen some like this (for example, MyOrder.py):
from AccessControl import ClassSecurityInfo
from Products.Archetypes.atapi import *
from Products.Laborde.config import *
from Products.DataGridField import DataGridField, DataGridWidget # we talk about this later
from Products.DataGridField.Column import Column #really!
schema = Schema((
StringField(
name='PurchaseOrderID',
widget=StringWidget(
label="PurchaseOrderID",
description="Enter this purchase order unique identification number.",
label_msgid='MyOrder_label_PurchaseOrderID',
description_msgid='MyOrder_help_PurchaseOrderID',
i18n_domain='MyOrder',
),
required=True,
searchable= True
),
DataGridField(
name='PurchaseOrderItems',
required=True,
searchable=True,
widget=DataGridWidget(),
allow_empty_rows = False,
columns=(
"Maker",
"Model",
"Description",
"UnitaryCost",
"Quantity"
),
),
ComputedField(
name='TotalCost',
searchable=True,
expression="context.calculateTotal()",
widget=ComputedWidget(
label="Total",
modes=('view')
),
),
),
)
PurchaseOrder_schema = BaseSchema.copy() + \
schema.copy()
class PurchaseOrder(BaseContent):
"""
"""
# some class defitnitions
# a function that calculates total
# but it doesn't even check (try-except) data it uses
def calculateTotal(self):
Total = 0.0
for n in self.PurchaseOrderItems:
Quantity = float(n['Quantity'])
UnitaryCost = float(n['UnitaryCost'])
Total = Total + Quantity * UnitaryCost
Total = '%1.2f' % Total # this makes our total have 2 decimals for display
return Total
registerType(PurchaseOrder, PROJECTNAME)
Aargh! I've just core dumped and almost killed 30!
The
above code can be divided in two parts: Schema and Class (Product). We
have declared 3 different fields in the schema: the first is a bultin
trivial Archetype field; the second is imported from the Product
DataGridField (you need it installled on your Plone instance to work);
the third is our the field we want to change as someone changes values
on the form.
expression="dir()" # useful to check avaible objects
,
expression="1+1" # 10 if you have two neurons, like me. Otherwise, 2.
,
expression="dir(context)" # avaible context child objects
or
expression="context.calculateTotal()" # Voilá! Reference to some real(?) code!
We've just called something (a function, in fact) named calculateTotal.
But
smart as we are, we realized that expresssions called this way must be
somewhere in our context scope. I mean, inside our class definition.
The
function definition itself isn't that simple: it adds up the total and
returns its value. What isn't simple? Our generous DataGridField
returns a tuple of dictionaries like:
(
{"Maker":"HP","Model":"scanjet 4670","Description":"scanner","UnitaryCost":"99.00","Quantity":"1"},
{"Maker":"LG","Model":"L173SA","Description":"17 LCD monitor","UnitaryCost":"299.95","Quantity":"2"},
{"Maker":"Seagate","Model":"SA32300","Description":"Hard drive","UnitaryCost":"134.50","Quantity":"2"}
)
The for
loop iterates over every item on the tuple and searches for two
dictionary items. Other field are rather simple to retrieve data: just
use field's name attribute.
The widget=SomeWidget(modes='view',...) realizes
the feat of showing this field only on the view mode: not when adding
the item and editting, nor when editting an existing item.
What's next?
- What could we do with PhotoField (ImageWidget)?
- try-except is always recomended
- Could this better than mutate?
- Can we make a file avaible for download with some strange mime type based on the information of this product?

