Creating Forms

Release:master
Date:December 02, 2014

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 sqlalchemy.schema import Column
from sqlalchemy.types import Unicode, Date
from camelot.admin.entity_admin import EntityAdmin
from camelot.core.orm import Entity
from camelot.view import forms

class Movie( Entity ):    
    title = Column( Unicode(60), nullable=False )
    short_description = Column( Unicode(512) )
    releasedate = Column( Date )
  
    class Admin(EntityAdmin):
        form_display = forms.Form( ['title', 'short_description', 'releasedate'] )
doc/../_static/form/form.png

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',])), ])
doc/../_static/form/nested_form.png

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'])) 
doc/../_static/form/inherited_form.png

Putting notes on forms

doc/../_static/editors/NoteEditor.png

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 :

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.

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 camelot.view.forms.Form subclass can be made to layout 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 the form. It should thus return a QtGui.QWidget to be used as the needed form.

The render method its first argument is the factory class camelot.view.controls.formview.FormEditors, through which editors and labels can be constructed. The editor widgets are bound to the data 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, editor_factory, 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 in self.get_fields():
            field_editor = editor_factory.create_editor( field_name, widget )
            field_label = editor_factory.create_label( field_name, field_editor, widget )
            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 QtGui.QFormLayout using a different background color, and adds some instructions for the user :

doc/../_static/form/custom_layout.png

Editors

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

Tooltips

Each field on the form can be given a dynamic tooltip, using the tooltip field attribute, see tooltip.

Buttons

Buttons bound to a specific action can be put on a form, using the form_actions attribute, 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