Creating Forms — Camelot documentation

### Navigation

- index
- modules |
- next |
- previous |
- Camelot documentation »
- Camelot Documentation contents »
- Camelot Documentation »

# Creating Forms

Release: | trunk
Date: | November 03, 2011

This section describes how to place fields on forms and applying various layouts. It also covers how to customize forms to your specific needs. As with everything in Camelot, the goal of the framework is that you can create 80% of your forms with minimal effort, while the framework should allow you to really customize the other 20% of your forms.

## Form

A form is a collection of fields organized within a layout. Each field is represented by its editor.

Usually forms are defined by specifying the ‘form_display’ attribute of an Admin class :

    from elixir import Entity, Field, ManyToOne
    from sqlalchemy.types import Unicode, Date
    from camelot.admin.entity_admin import EntityAdmin
    from camelot.view import forms

    class Movie(Entity):
        title = Field(Unicode(60), required=True)
        short_description = Field(Unicode(512))
        releasedate = Field(Date)
        director = ManyToOne('Person')

        class Admin(EntityAdmin):
            form_display = forms.Form(['title', 'short_description', 'director', 'releasedate'])

The ‘form_display’ attribute should either be a list of fields to display or an instance of camelot.view.forms.Form or its subclasses.

Forms can be nested into each other :

    from camelot.admin.entity_admin import EntityAdmin
    from camelot.view import forms
    from camelot.core.utils import ugettext_lazy as _

    class Admin(EntityAdmin):
        verbose_name = _('person')
        verbose_name_plural = _('persons')
        list_display = ['first_name', 'last_name', ]
        form_display = forms.TabForm([('Basic', forms.Form(['first_name', 'last_name', 'contact_mechanisms',])),
                                      ('Official', forms.Form(['birthdate', 'social_security_number', 'passport_number',
                                                               'passport_expiry_date','addresses',])), ])

## Inheritance and Forms

Just as Entities support inheritance, forms support inheritance as well. This avoids duplication of effort when designing and maintaining forms. Each of the Form subclasses has a set of methods to modify its content. In the example below a new tab is added to the form defined in the previous section.

    from copy import deepcopy

    from camelot.view import forms
    from nested_form import Admin

    class InheritedAdmin(Admin):
        form_display = deepcopy(Admin.form_display)
        form_display.add_tab('Work', forms.Form(['employers', 'directed_organizations', 'shares']))

## Putting notes on forms

A note on a form is nothing more than a property with the NoteDelegate as its delegate and where the widget is inside a WidgetOnlyForm.

In the case of a Person, we display a note if another person with the same name already exists :

    class Person( Party ):
        """Person represents natural persons
        """
        using_options( tablename = 'person', inheritance = 'multi' )
        first_name = Field( Unicode( 40 ), required = True )
        last_name = Field( Unicode( 40 ), required = True )
    # end short person definition
        middle_name = Field( Unicode( 40 ) )
        personal_title = Field( Unicode( 10 ) )
        suffix = Field( Unicode( 3 ) )
        sex = Field( Unicode( 1 ), default = u'M' )
        birthdate = Field( Date() )
        martial_status = Field( Unicode( 1 ) )
        social_security_number = Field( Unicode( 12 ) )
        passport_number = Field( Unicode( 20 ) )
        passport_expiry_date = Field( Date() )
        is_staff = Field( Boolean, default = False, index = True )
        is_superuser = Field( Boolean, default = False, index = True )
        picture = Field( camelot.types.Image( upload_to = 'person-pictures' ), deferred = True )
        comment = Field( camelot.types.RichText() )
        employers = OneToMany( 'EmployerEmployee', inverse = 'established_to', cascade='all, delete, delete-orphan' )

        @property
        def note(self):
            for person in self.__class__.query.filter_by(first_name=self.first_name, last_name=self.last_name):
                if person != self:
                    return _('A person with the same name already exists')

        @property
        def name( self ):
            # we don't use full name in here, because for new objects, full name will be None, since
            # it needs to be fetched from the db first
            return u'%s %s' % ( self.first_name, self.last_name )

        def __unicode__( self ):
            return self.name or ''

        class Admin( Party.Admin ):
            verbose_name = _( 'Person' )
            verbose_name_plural = _( 'Persons' )
            list_display = ['first_name', 'last_name', 'contact_mechanisms_email', 'contact_mechanisms_phone']
            form_display = TabForm( [( _('Basic'), Form( [HBoxForm( [ Form( [WidgetOnlyForm('note'),
                                                                      'first_name',
                                                                      'last_name',
                                                                      'sex',
                                                                      'contact_mechanisms_email',
                                                                      'contact_mechanisms_phone',
                                                                      'contact_mechanisms_fax'] ),
                                                              Form( ['picture', ] ),
                                                             ] ),
                                                             'comment', ], scrollbars = False ) ),
                                    ( _('Official'), Form( ['birthdate', 'social_security_number', 'passport_number',
                                                            'passport_expiry_date', 'addresses', 'contact_mechanisms',], scrollbars = False ) ),
                                    ( _('Work'), Form( ['employers', 'directed_organizations', 'shares'], scrollbars = False ) ),
                                    ( _('Category'), Form( ['categories',] ) ),
                                    ] )
            field_attributes = dict( Party.Admin.field_attributes )
            field_attributes['note'] = {'delegate':delegates.NoteDelegate}

## Available Form Subclasses

camelot.view.forms.Form has several subclasses that can be used to create various layouts. Each subclass maps to a QT Layout class.

Classes to layout fields on a form. These are mostly used for specifying the form_display attribute in Admin classes, but they can be used on their own as well. Form classes can be used recursive.

class `camelot.view.forms.` `DelayedTabWidget`(widgets, tabs, parent=None)

  Helper class for `TabForm` to delay the creation of tabs to the moment the tab is shown.

  `DelayedTabWidget.` `render_tab`(index)

    Render the tab at index

class `camelot.view.forms.` `Form`(content, scrollbars=False, columns=1)

  Base Form class to put fields on a form. The base class of a form is a list. So the form itself is nothing more than a list of field names or sub-forms. A form can thus be manipulated using the list’s method such as append or insert.

  A form can be converted to a QT widget by calling its render method. The base form uses the QFormLayout to render a form:

      class Admin(EntityAdmin):
        form_display = Form(['title', 'short_description', 'director', 'release_date'])

  ..image:: /_static/form/form.png

  `Form.` `get_fields`()

    Returns: | the fields, visible in this form

  `Form.` `remove_field`(original_field)

    Remove a field from the form, This function can be used to modify inherited forms.

    Parameters: | original_field – the name of the field to be removed
    Returns: | True if the field was found and removed

  `Form.` `render`(*args, **kwargs)

    Parameters: | widgets – a `camelot.view.controls.formview.FormEditors` object that is able to create the widgets for this form parent – the `QtGui.QWidget` in which the form is placed toplevel – a `boolean` indicating if this form is toplevel, or a child form of another form. A toplevel form will be expanding, while a non toplevel form is only expanding if it contains other expanding elements.
    Returns: | a QWidget into which the form is rendered

  `Form.` `render_ooxml`(obj, delegates)

    Generator for lines of text in Office Open XML representing this form, using tables :param obj: the object or entity that will be rendered :param delegates: a dictionary mapping field names to their delegate

  `Form.` `replace_field`(original_field, new_field)

    Replace a field on this form with another field. This function can be used to modify inherited forms.

    :param original_field : the name of the field to be replace :param new_field : the name of the new field :return: True if the original field was found and replaced.

class `camelot.view.forms.` `GridForm`(grid, nomargins=False)

  Put different fields into a grid, without a label. Row or column labels can be added using the Label form:

      GridForm([['title', 'short_description'], ['director','release_date']])

  `GridForm.` `append_column`(column)

    Parameters: | column – the list of fields that should come in the additional column

    use this method to modify inherited grid forms

  `GridForm.` `append_row`(row)

    Parameters: | row – the list of fields that should come in the additional row

    use this method to modify inherited grid forms

class `camelot.view.forms.` `GroupBoxForm`(title, content, scrollbars=None, min_width=None, min_height=None, columns=1)

  Renders a form within a QGroupBox:

      class Admin(EntityAdmin):
        form_display = GroupBoxForm('Movie', ['title', 'short_description'])

class `camelot.view.forms.` `HBoxForm`(columns, scrollbars=False)

  Render different forms in a horizontal box:

      form = forms.HBoxForm([['title', 'short_description'], ['director', 'release_date']])

class `camelot.view.forms.` `Label`(label, alignment='left', style=None)

  Render a label with a QLabel

  `Label.` `render_ooxml`()

    Generator for label text in Office Open XML representing this form

class `camelot.view.forms.` `TabForm`(tabs, position='North')

  Render forms within a QTabWidget:

      from = TabForm([('First tab', ['title', 'short_description']),
                      ('Second tab', ['director', 'release_date'])])

  `TabForm.` `add_tab`(tab_label, tab_form)

    Add a tab to the form

    Parameters: | tab_label – the name of the tab tab_form – the form to display in the tab or a list of field names.

  `TabForm.` `add_tab_at_index`(tab_label, tab_form, index)

    Add a tab to the form at the specified index

    Parameters: | tab_label – the name to the tab tab_form – the form to display in the tab or a list of field names. index – the position of tab in the tabs list.

  `TabForm.` `get_tab`(tab_label)

    Get the tab form of associated with a tab_label, use this function to modify the underlying tab_form in case of inheritance

    :param tab_label : a label of a tab as passed in the construction method :return: the tab_form corresponding to tab_label

class `camelot.view.forms.` `VBoxForm`(rows)

  Render different forms or widgets in a vertical box:

      form = forms.VBoxForm([['title', 'short_description'], ['director', 'release_date']])

class `camelot.view.forms.` `WidgetOnlyForm`(field)

  Renders a single widget without its label, typically a one2many widget

`camelot.view.forms.` `structure_to_form`(structure)

  Convert a python data structure to a form, using the following rules :

  > - if structure is an instance of Form, return structure
  > - if structure is a list, create a Form from this list

  This function is mainly used in the Admin class to construct forms out of the form_display attribute

## Customizing Forms

Several options exist for completely customizing the forms of an application.

### Layout

When the desired layout cannot be achieved with Camelot’s form classes, a custom Form subclass can be made to lay out the widgets.

When subclassing the Form class, it’s ‘render’ method should be reimplemented to put the labels and the editors in a custom layout. The ‘render’ method will be called by Camelot each time it needs a form for the related entity. It should thus return a QWidget to be used as the needed form.

The ‘render’ method its most important argument is widgets which is a dictionary containing for each field of the form a widget representing the label of the field and a widget for editing the field. The editor widgets are bound to the model.

    from PyQt4 import QtGui

    from camelot.view import forms
    from camelot.admin.entity_admin import EntityAdmin

    class CustomForm(forms.Form):

        def __init__(self):
            super(CustomForm, self).__init__(['first_name', 'last_name'])

        def render( self, widgets, parent = None, nomargins = False ):
            widget = QtGui.QWidget(parent)
            layout = QtGui.QFormLayout()
            layout.addRow(QtGui.QLabel('Please fill in the complete name :', widget))
            for _field_name,(field_label, field_editor) in widgets.items():
                layout.addRow(field_label, field_editor)
            widget.setLayout(layout)
            widget.setBackgroundRole(QtGui.QPalette.ToolTipBase)
            widget.setAutoFillBackground(True)
            return widget

    class Admin(EntityAdmin):
        list_display = ['first_name', 'last_name']
        form_display = CustomForm()
        form_size = (300,100)

The form defined above puts the widgets into a QFormLayout using a different background color, and adds some instructions for the user :

### Editors

The editor of a specific field can be changed, by specifying an alternative delegate for that field, using the field attributes, see Specifying delegates.

### Tooltips

Each field on the form can be given a dynamic tooltip, using the ‘tooltip’ field attribute : tooltip.

### Buttons

Buttons bound to a specific action can be put on a form, using the ‘form_actions’ attribute of the Admin class : form-actions.

### Validation

Validation is done at the object level. Before a form is closed validation of the bound object takes place, an invalid object will prevent closing the form. A custom validator can be defined : Validators

### Behaviour

To change what happens when Camelot requires a form for an object, some methods on the Admin class can be overwritten :

> - create_form_view
> - create_new_view

### Navigation

- index
- modules |
- next |
- previous |
- Camelot documentation »
- Camelot Documentation contents »
- Camelot Documentation »

Comments
blog comments powered by Disqus