Zope 3 schemas

Zope 3 schema is database-neutral and form library-neutral way to describe Python data models. Various Plone form engines use zope.schema package to describe data structures.

The schema is model describing how data is stored. Usually form fields can be generated based on this model.

Schema is not used only forms, but it is also used

  • To describe validation and defaults of persistent data
  • To describe fields in ZCML

Field constructor parameters

Field base class defines list of standard parameters you can use to construct schema fields. Each field subclass have its own set of possible parameters on the top of this.

See full list here.

  • Title - field title as unicode string
  • Description - field description as unicode string
  • required - boolean, whether the field is required
  • default - Default value if the attribute is not present
  • etc.

Warning

Do not initialize any non-primitive values using default keyword parameter of schema fields. Python and ZODB stores objects by their reference. Python code will construct only one field value, during schema contstruction, and share its content across all objects. Instead, initialize objects in __init__() method of your schema implementor.

Dangerous defaults: default=[], default={}, default=SomeObject()

Schema introspection

zope.schema._schema module provides some introspection functions:

* getFieldNames(schema)
  • getFields
  • getFieldsInOrder(schema)
  • getFieldNamesInOrder(schema)

Example:

import zope.schema
import zope.interface

class IMyInterface(zope.interface.Interface):

        text = zope.schema.TextLine()


fields = zope.schema.getFields(IMyInterface)

Field order

The order attribute can be used to determine the order in which fields in a schema were defined. If one field is created after another (in the same thread), its order will be greater.

Default values

To make default values of schema effective, class attributes must be implemented using FieldProperty.

Example:

    import zope.interface
    from zope import schema
    from zope.schema.fieldproperty import FieldProperty


class ISomething(zope.interface.Interface):
    """ Sample schema """
    some_value = schema.Bool(default=True)


class SomeStorage(object):

    some_value = FieldProperty(ISomething["some_value"])


    something = SomeStorage()
    assert something.some_value == True

Persistent objects and schema

ZODB persistent objects do not provide facilities for setting field defaults or validating the data input.

When you create a persistent class, you need to provide field properties for it, which will sanify the incoming and outgoing data.

When the persistent object is created it has no attributes. When you try to access the attribute through a named zope.schema.fieldproperty.FieldProperty accessor it first checks the existing of the attribute. If attribute is not there, it is created and the default value is returned.

Example:

from persistent import Persistent
from zope import schema
from zope.interface import implements, alsoProvides
from zope.component import adapts
from zope.schema.fieldproperty import FieldProperty

...

class IHeaderBehavior(form.Schema):
    """ Sample schema """
    inheritable = schema.Bool(title=u"Inherit header", description=u"This header is visible on child content", required=False, default=False)

    block_parents = schema.Bool(title=u"Block parent headers", description=u"Do not show parent headers for this content", required=False, default=False)

    # Contains list of HeaderAnimation objects
    alternatives = schema.List(title=u"Available headers and animations",
                               description=u"Headers and animations uploaded here",
                               required=False,
                               default=[],
                               value_type=schema.Object(IHeaderAnimation)
                               )

alsoProvides(IHeaderAnimation, form.IFormFieldProvider)


class HeaderBehavior(Persistent):
    """ Sample persistent object for the schema """
    implements(IHeaderBehavior)

    #
    # zope.schema magic happens here - see FieldProperty!
    #

    # We need to declare field properties so that objects will
    # have input data validation and default values taken from schema above
    inheritable = FieldProperty(IHeaderBehavior["inheritable"])
    block_parents = FieldProperty(IHeaderBehavior["block_parents"])
    alternatives = FieldProperty(IHeaderBehavior["alternatives"])

Now you see the magic:

header = HeaderBehavior()
# it hits alternatives accessor which returns the default value which is empty list
assert header.alternatives = []

Collections (and multichoice fields)

Collections are fields composed of several other fields. Collections also act as multi-choice fields.

For more information see

Single choice example

Only one value can be chosen.

Below is code to create Python logging level choice:

import logging

from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm

def _createLoggingVocabulary():
    """ Create zope.schema vocabulary from Python logging levels.

    Note that term.value is int, not string.

    _levelNames looks like::

        {0: 'NOTSET', 'INFO': 20, 'WARNING': 30, 40: 'ERROR', 10: 'DEBUG', 'WARN': 30, 50:
        'CRITICAL', 'CRITICAL': 50, 20: 'INFO', 'ERROR': 40, 'DEBUG': 10, 'NOTSET': 0, 30: 'WARNING'}

    @return: Iterable of SimpleTerm objects
    """
    for level, name in logging._levelNames.items():

        # logging._levelNames dictionary is bidirectional, let's
        # get numeric keys only

        if type(level) == int:
            term = SimpleTerm(value=level, token=str(level), title=name)
            yield term

# Construct SimpleVocabulary objects of log level -> name mappings
logging_vocabulary = SimpleVocabulary(list(_createLoggingVocabulary()))

class ISyncRunOptions(Interface):

    log_level = schema.Choice(vocabulary=logging_vocabulary,
                                          title=u"Log level",
                                          description=u"One of python logging module constants",
                                          default=logging.INFO)

Multi-choice example

Using zope.schema.List, many values can be chosen once. Each value is atomically constrained by value_type schema field.

Example:

    from zope import schema
    from plone.directives import form

class IMultiChoice(form.Schema):
    ...

    # Contains lists of values from Choice list using special "get_field_list" vocabulary
    # We also give a plone.form.directives hint to render this as
    # multiple checbox choices
    form.widget(enabled_overrides=CheckBoxFieldWidget)
    alternatives = schema.List(title=u"Available headers and animations",
                               description=u"Headers and animations uploaded here",
                               required=False, default=[],
                               value_type=zope.schema.Choice(source=get_field_list),
                               )

Dictionaries

Read about problem of zope.schema and PersistentDict persistent dictionaries.