Personal tools
You are here: Home Documentation Tutorials Working with Forms in Grok Defining Schema Fields

Defining Schema Fields

Schema Fields are used to describe the schema of an ordinary Python object.
A walkthrough of the basics of automatically generating HTML forms using Grok, as well as a discussion of a few more advanced Form manipulations.
Page 2 of 9.

Before we dig into the details of Forms, it's helpful to familiarize yourself with how Grok and Zope 3 allow you to describe the schema of any ordinary Python object.

Typically in a web application, one will have the concept of a model layer - this is where the data model is described and implemented. The model layer is separate from the View and the Template layers. It knows nothing about HTML or responding to HTTP Requests, and is only concerned with maintaining the integrity of your applications data and providing core behaviour.

In Grok your model layer is typically defined as inheriting from classes which inherit from grok.Application, grok.Container and grok.Model. All of these base classes allow objects to be seamlessly stored in a database. Depending upon if you are making the distinction between the part of your Model layer which is only concerned with storing data (data model), or you are viewing the Model layer as everything which defines core application functionality (application model), then you may also consider your model layer as also including Adapters, Utilities and Subscribers.

Objects in your data model layer are usually going to want to provide a formal description of the type of data that they contain. In Grok this formal description of the data that a model object contains is called a schema. In relational database terms, you can think of a schema as being similar to a CREATE TABLE statement. It describes a collection of data fields.

When creating a schema in a relation database you might write:

CREATE TABLE mammoth (
    furryness   text NOT NULL DEFAULT 'Brown. Average quality.',
    weight      integer NOT NULL,
    owner       varchar(200),
);

The equivalent declaration as Zope schema would be:

from zope import schema
from zope import interface

class IMammoth(interface.Interface):
    furryness = schema.Text(
        title = u'Furryness',
        default = u'Brown. Average quality.',
    )
    weight = schema.Int(
        title = u'Weight',
    )
    owner = schema.TextLine(
        title = u'Owner',
        required = False,
    )

However, Grok goes one step farther in it's seperation of concerns than most other web frameworks and doesn't tightly tie the use of data schemas to just objects that map to a database and are part of a specific Object Relation Mapper (ORM). Schemas can be used with any ordinary Python object.

Consider three sources of Python objects which contain and deal with data: a Model object which is stored in a database, a Form object which is submitted from an HTTP Request, and a call to an external web service which pulls data in to the application via HTTP. All three types of objects contain data, so it's helpful to be able to use the same system for formally describing the data in any Python object.

This schema feature is provided by the zope.schema package. This package extends the notion of Interfaces as defined in the zope.interface package. Remember, Interfaces are a way of formally describing a set of method signatures and attributes. An orindinary Attribute defined by zope.interface only allows you to descirbe the name and doc string of an Attribute. Schemas extend the basic Attribute of interfaces to allow for more detailed descriptions. An Attribute which has this extended description is called a Schema Field (or just Field). It allows for additional descriptions of an Attribute such as title, required and default.

If we were only working with simple Interfaces, and we wanted to describe a the data that a Mammoth object provided we would write:

from zope import interface

class IMammoth(interface.Interface):
    "Describes a Mammoth"
    furryness = interface.Attribute("Furryness.")
    weight = interface.Attribute("Weight")
    owner = interface.Attribute("Owner")

This is very generic though. Let's go back to the zope.schema version of our Mammoth and enrich the description of the data even further:

class IMammoth(interface.Interface):
    "Describes a Mammoth"
    furryness = schema.Text(
        title = u'Description of the fur.',
        description = u"""
This field is primarily used by cavemen to aid in sorting and processing
the mammoths in the spring during fur harvesting season.""",
        required = True,
        default = u'Brown. Average quality.',
    )
    weight = schema.Int(
        title = u'Weight',
        description = u'Measured in Kilograms',
        required = True,
    )
    owner = schema.TextLine(
        title = u'Name of the owner.',
        description = u'Kept as a pet unless the owner is very hungry.',
        required = False,
    )

By extending the description of an ordinary Python attribute, we can use that information to automatically map data into an ORM, a web service call, or generate an HTML Form.

Further Reading and Reference

For more reading see the documentation that is part of the zope.schema package.

For now though it is helpful to remember that a Schema Field is simply an extensible description of a Python attribute. Every Schema Field description in an Interface must inherit from zope.schema.interfaces.IField. This interface looks like:

class IField(Interface):
    """Basic Schema Field Interface.

    Fields are used for Interface specifications.  They at least provide
    a title, description and a default value.  You can also
    specify if they are required and/or readonly.

    The Field Interface is also used for validation and specifying
    constraints.

    We want to make it possible for a IField to not only work
    on its value but also on the object this value is bound to.
    This enables a Field implementation to perform validation
    against an object which also marks a certain place.

    Note that many fields need information about the object
    containing a field. For example, when validating a value to be
    set as an object attribute, it may be necessary for the field to
    introspect the object's state. This means that the field needs to
    have access to the object when performing validation::

         bound = field.bind(object)
         bound.validate(value)

    """

    def bind(object):
        """Return a copy of this field which is bound to context.

        The copy of the Field will have the 'context' attribute set
        to 'object'.  This way a Field can implement more complex
        checks involving the object's location/environment.

        Many fields don't need to be bound. Only fields that condition
        validation or properties on an object containing the field
        need to be bound.
        """

    title = TextLine(
        title=_(u"Title"),
        description=_(u"A short summary or label"),
        default=u"",
        required=False,
        )

    description = Text(
        title=_(u"Description"),
        description=_(u"A description of the field"),
        default=u"",
        required=False,
        )

    required = Bool(
        title=_(u"Required"),
        description=(
        _(u"Tells whether a field requires its value to exist.")),
        default=True)

    readonly = Bool(
        title=_(u"Read Only"),
        description=_(u"If true, the field's value cannot be changed."),
        required=False,
        default=False)

    default = Field(
        title=_(u"Default Value"),
        description=_(u"""The field default value may be None or a legal
                        field value""")
        )

    missing_value = Field(
        title=_(u"Missing Value"),
        description=_(u"""If input for this Field is missing, and that's ok,
                          then this is the value to use""")
        )

    order = Int(
        title=_(u"Field Order"),
        description=_(u"""
        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.

        (Fields in separate threads could have the same order.)
        """),
        required=True,
        readonly=True,
        )

    def constraint(value):
        u"""Check a customized constraint on the value.

        You can implement this method with your Field to
        require a certain constraint.  This relaxes the need
        to inherit/subclass a Field you to add a simple constraint.
        Returns true if the given value is within the Field's constraint.
        """

    def validate(value):
        u"""Validate that the given value is a valid field value.

        Returns nothing but raises an error if the value is invalid.
        It checks everything specific to a Field and also checks
        with the additional constraint.
        """

    def get(object):
        """Get the value of the field for the given object."""

    def query(object, default=None):
        """Query the value of the field for the given object.

        Return the default if the value hasn't been set.
        """

    def set(object, value):
        """Set the value of the field for the object

        Raises a type error if the field is a read-only field.
        """