Personal tools
You are here: Home Documentation Tutorials Working with Forms in Grok Using multiple schemas with a Form

Using multiple schemas with a Form

Demonstrating using multiple schemas within the same form.
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 8 of 9.

Forms can use fields from multiple schemas. We have seen how grok.AutoFields() can be used to automatically provide all form fields from a class. If that class implements more than one schema, then all fields from all schemas will be used to generate form fields (without any overlap). Grok also provides the grok.Fields() convenience method which let's us build form fields from schema fields directly.

Let's say that in the Mammoth manager application we want to automatically send an email notification when a new Mammoth is added, and we'd like to provide a field in the form where a message can be appended to the email. Both grok.AutoFields and grok.Fields return objects that implement the IFormFields interface. This interface supports addition (+), so we can add two collections of form fields together.

We could change the form_fields attribute of the MammothForm class to read:

form_fields = grok.AutoFields(Mammoth) + grok.Fields(
    message = schema.Text(
        title=u'Email Message', required=False
    ),
)

Now when the form is submitted, the data dictionary will contain a key named message with the value entered into the "Email Message" field.

Editing two Model objects in one Form

With a simple grok.Form or grok.AddForm we can generate whatever set of form fields we need. But with a grok.EditForm it's necessary for the form to bind the fields to the object it's editing, so that the edit widgets are pre-filled with existing data.

What if we wanted to create a form capable of editing two different Model objects at the same time? How can we tell the form to read and write data to both Model objects?

The answer is to make an EditForm for one primary object, and then create an Adapter that allows us to adapt that primary object to our secondary object.

In the Mammoth manager application we'll add a location attribute to the MammothApplication. This way the cave men using the application can easily update the application with the location of the herd whenever they are making an edit to an existing mammoth. We'll need to make an adapter which can take a Mammoth object and extend it with the IHerdLocation interface. We can think of this as the primary object being edited (Mammoth) as providing the attribtues of the secondary object (MammothApplication) via our adapter.

First we update the application object:

class IHerdLocation(interface.Interface):
    "Where's the mammoth herd at?"
    location = schema.Text(
        title = u'Herd Location',
        required = False,
    )

class MammothApplication(grok.Application, grok.Container):
    """World's greatest Mammoth manager web application."""
    grok.implements(IHerdLocation)
    location = u''

Then add an EditMammothForm. The only thing special about this form is that it has form fields for both the Mammoth and IHerdLocation. Once we have the right Adapter in place, the Form class is smart enough that if a field is not provided by the primary object then it will try and adapt the primary object to the interface that contains the field of the secondary object.

class EditMammothForm(grok.EditForm):
    grok.context(Mammoth)
    grok.name('edit')
    form_fields = grok.AutoFields(Mammoth) + grok.Fields(IHerdLocation)
    label = 'Edit Mammoth'
    template = grok.PageTemplateFile('custom_edit_form.pt')

    @grok.action('Edit Mammoth')
    def edit(self, **data):
        self.applyData(self.context, **data)
        self.redirect(self.url(self.context))

Finally we need to provide an adapter that can take a Mammoth object, and provide the IHerdLocation interface. We'll implement this by creating a property in the adapter which delegates gettting/setting of the location attribute to the MammothApplication object.

class MammothHerdLocationAdapter(grok.Adapter):
    "Allows us to edit a MammothApplication via a Mammoth"
    grok.context(Mammoth)
    grok.implements(IHerdLocation)

    def __init__(self, context):
        self.context = context
        self.app = context.__parent__

    def _get_location(self): return self.app.location
    def _set_location(self, value): self.app.location = value
    location = property(_get_location, _set_location)

Now when we edit a Mammoth, the location field will be bound to an instance of the MammothHerdLocationAdapter.