Implement Archetypes ComputedField and ComputedWidget on your Product and reference other Fields

by Plone Documentation Team last modified Dec 06, 2009 09:44 PM
Contributors: Mikko Ohtamma, Martin Aspeli, Kamon Ayeva, Israel Saeta Pérez
A simple use of ComputedField and ComputedWidget referencing other fields, built-in or 3rd party, in the same Plone product

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?