Common Needs

This chapter collects solutions for requirements that will often crop up once you start using Deform for real world applications.

Customizing widget templates

To override, add, or customize widget templates, see the chapter Templates.

Complex data loading, changing widgets, or validating

For complex loading of data, changing widgets, or validating beyond the basic declarative schema, you can use Colander's schema binding feature. See the chapter in the Colander's documentation Schema Binding.

Changing the Default Widget Associated With a Field

Let's take another look at our familiar schema:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import colander

class Person(colander.MappingSchema):
    name = colander.SchemaNode(colander.String())
    age = colander.SchemaNode(colander.Integer(),
                              validator=colander.Range(0, 200))

class People(colander.SequenceSchema):
    person = Person()

class Schema(colander.MappingSchema):
    people = People()

schema = Schema()

This schema renders as a sequence of mapping objects. Each mapping has two leaf nodes in it: a string and an integer. If you play around with the demo at https://deformdemo.pylonsproject.org/sequence_of_mappings/ you will notice that, although we do not actually specify a particular kind of widget for each of these fields, a sensible default widget is used. This is true of each of the default types in Colander. Here is how they are mapped by default. In the following list, the schema type which is the header uses the widget underneath it by default.

colander.Mapping
deform.widget.MappingWidget
colander.Sequence
deform.widget.SequenceWidget
colander.String
deform.widget.TextInputWidget
colander.Integer
deform.widget.TextInputWidget
colander.Float
deform.widget.TextInputWidget
colander.Decimal
deform.widget.TextInputWidget
colander.Boolean
deform.widget.CheckboxWidget
colander.Date
deform.widget.DateInputWidget
colander.DateTime
deform.widget.DateTimeInputWidget
colander.Tuple
deform.widget.Widget

Note

Not just any widget can be used with any schema type; the documentation for each widget usually indicates what type it can be used against successfully. If all existing widgets provided by Deform are insufficient, you can use a custom widget. See Writing Your Own Widget for more information about writing a custom widget.

If you are creating a schema that contains a type which is not in this list, or if you would like to use a different widget for a particular field, or you want to change the settings of the default widget associated with the type, you need to associate the field with the widget "by hand". There are a number of ways to do so, as outlined in the sections below.

As an argument to a colander.SchemaNode constructor

As of Deform 0.8, you may specify the widget as part of the schema:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import colander

from deform import Form
from deform.widget import TextAreaWidget

class Person(colander.MappingSchema):
    name = colander.SchemaNode(colander.String(),
                               widget=TextAreaWidget())
    age = colander.SchemaNode(colander.Integer(),
                              validator=colander.Range(0, 200))

class People(colander.SequenceSchema):
    person = Person()

class Schema(colander.MappingSchema):
    people = People()

schema = Schema()

myform = Form(schema, buttons=('submit',))

Note above that we passed a widget argument to the name schema node in the Person class above. When a schema containing a node with a widget argument to a schema node is rendered by Deform, the widget specified in the node constructor is used as the widget which should be associated with that node's form rendering. In this case, we will use a deform.widget.TextAreaWidget as the name widget.

Note

Widget associations done in a schema are always overridden by explicit widget assignments performed via deform.Field.__setitem__() and deform.Field.set_widgets().

Using dictionary access to change the widget

After the deform.Form constructor is called with the schema, you can change the widget used for a particular field by using dictionary access to get to the field in question. A deform.Form is just another kind of deform.Field, so the method works for either kind of object. For example:

1
2
3
4
5
from deform import Form
from deform.widget import TextInputWidget

myform = Form(schema, buttons=('submit',))
myform['people']['person']['name'].widget = TextInputWidget(size=10)

This associates the String field named name in the rendered form with an explicitly created deform.widget.TextInputWidget by finding the name field via a series of __getitem__ calls through the field structure, then by assigning an explicit widget attribute to the name field.

You might want to do this in order to pass a size argument to the explicit widget creation, indicating that the size of the name input field should be 10em rather than the default.

Although in the example above, we associated the name field with the same type of widget as its default, we could have associated the name field with a completely different widget using the same pattern. For example:

1
2
3
4
5
from deform import Form
from deform.widget import TextAreaWidget

myform = Form(schema, buttons=('submit',))
myform['people']['person']['name'].widget = TextAreaWidget()

The above renders an HTML textarea input element for the name field instead of an input type=text field. This probably does not make much sense for a field called name (names are not usually multiline paragraphs), but it does let us demonstrate how different widgets can be used for the same field.

Using the deform.Field.set_widgets() method

Equivalently, you can also use the deform.Field.set_widgets() method to associate multiple widgets with multiple fields in a form. For example:

1
2
3
4
5
6
from deform import Form
from deform.widget import TextAreaWidget

myform = Form(schema, buttons=('submit',))
myform.set_widgets({'people.person.name':TextAreaWidget(),
                    'people.person.age':TextAreaWidget()})

Each key in the dictionary passed to deform.Field.set_widgets() is a "dotted name" which resolves to a single field element. Each value in the dictionary is a widget instance. See deform.Field.set_widgets() for more information about this method and dotted name resolution, including special cases which involve the "splat" (*) character and the empty string as a key name.

Using arbitrary HTML5 form attributes

HTML5 introduced many attributes to HTML forms. For the full specification, see https://www.w3.org/TR/2017/REC-html52-20171214/sec-forms.html#sec-forms For implementations and demos, the Mozilla Developer Network is one useful resource.

Starting with Deform 2.0.7, all of the Deform widgets support arbitrary HTML5 attributes. This is useful, for example, when using HTML5 default browser number widgets. You can also set some client-side validation requirements without JavaScript. The following Python code will generate the subsequent HTML and rendered form input.

from decimal import Decimal

total_employee_hours = colander.SchemaNode(
    colander.Decimal(),
    widget=widget.TextInputWidget(
        attributes={
            "type": "number",
            "inputmode": "decimal",
            "step": "0.01",
            "min": "0",
            "max": "99.99",
        }
    ),
    validator=colander.Range(min=0, max=Decimal("99.99")),
    default=30.00,
    missing=colander.drop,
)
<input
    name="total_employee_hours"
    value="30.00"
    id="total_employee_hours"
    class=" form-control "
    type="number"
    inputmode="decimal"
    step="0.01"
    min="0"
    max="99.99">

Total employee hours

Using Date, DateTime, and Time Inputs

The deform.widget.DateInputWidget, deform.widget.DateTimeInputWidget, and deform.widget.TimeInputWidget inputs all use the jQuery plugin pickadate. This plugin is included with Deform in the directory static/pickadate.

Arbitrary options may be passed into the widget as a Python object, which will be automatically converted to a JSON object by the widget. These options are named date_options and time_options. This is useful to set a minimum or maximum date or time, and many other options. For the complete options, see date options or time options.

Use of these widgets is not a replacement for server-side validation of the field. It is purely a UI affordance. If the data must be checked at input time, a separate validator should be attached to the related schema node.

Using Text Input Masks

The deform.widget.TextInputWidget and deform.widget.CheckedInputWidget widgets allow for the use of a fixed-length text input mask. Use of a text input mask causes placeholder text to be placed in the text field input, and restricts the type and length of the characters input into the text field.

For example:

form['ssn'].widget = TextInputWidget(mask='999-99-9999')

When using a text input mask:

  • a represents an alpha character (A-Z,a-z).
  • 9 represents a numeric character (0-9).
  • * represents an alphanumeric character (A-Z,a-z,0-9).

All other characters in the mask will be considered mask literals.

By default the placeholder text for non-literal characters in the field will be _ (the underscore character). To change this for a given input field, use the mask_placeholder argument to the TextInputWidget:

form['date'].widget = TextInputWidget(mask='99/99/9999',
                                      mask_placeholder="-")

Example masks:

Date
99/99/9999
North American Phone Number
  1. 999-9999
United States Social Security Number
999-99-9999

When this option is used, the jquery.maskedinput library must be loaded into the page serving the form for the mask argument to have any effect. A copy of this library is available in the static/scripts directory of the deform package itself.

See https://deformdemo.pylonsproject.org/text_input_masks/ for a working example.

Use of a text input mask is not a replacement for server-side validation of the field. It is purely a UI affordance. If the data must be checked at input time, a separate validator should be attached to the related schema node.

Using the AutocompleteInputWidget

The deform.widget.AutocompleteInputWidget widget allows for client side autocompletion from provided choices in a text input field. To use this you MUST ensure that jQuery and the jQuery UI plugin are available to the page where the deform.widget.AutocompleteInputWidget widget is rendered.

For convenience a version of the jQuery UI (which includes the autocomplete sublibrary) is included in the deform static directory. Additionally, the jQuery UI styles for the selection box are also included in the deform static directory. See Serving up the Rendered Form and The (High-Level) deform.Field.get_widget_resources() Method for more information about using the included libraries for your application.

A very simple example of using deform.widget.AutocompleteInputWidget follows:

form['frozznobs'].widget = AutocompleteInputWidget(
                             values=['spam', 'eggs', 'bar', 'baz'])

Instead of a list of values, a URL can be provided to values:

form['frobsnozz'].widget = AutocompleteInputWidget(
                             values='http://example.com/someapi')

In the above case, a call to the URL should provide results in a JSON-compatible format or JSONP-compatible response if on a different host than the application. Something like either of these structures in JSON are suitable.

// Items are used as both value and label
['item-one', 'item-two', 'item-three']

// Separate values and labels
[
    {'value': 'item-one', 'label': 'Item One'},
    {'value': 'item-two', 'label': 'Item Two'},
    {'value': 'item-three', 'label': 'Item Three'}
]

The autocomplete plugin will add a query string to the request URL with the variable term which contains the user's input at that moment. The server may use this to filter the returned results.

For more information, see https://api.jqueryui.com/autocomplete/#option-source — specifically, the section concerning the String type for the source option.

Some options for the jquery.autocomplete plugin are mapped and can be passed to the widget. See deform.widget.AutocompleteInputWidget for details regarding the available options. Passing options looks like:

form['nobsfrozz'].widget = AutocompleteInputWidget(
                            values=['spam, 'eggs', 'bar', 'baz'],
                            min_length=1)

See https://deformdemo.pylonsproject.org/autocomplete_input/ and https://deformdemo.pylonsproject.org/autocomplete_remote_input/ for working examples. A working example of a remote URL providing completion data can be found at https://deformdemo.pylonsproject.org/autocomplete_input_values/.

Use of deform.widget.AutocompleteInputWidget is not a replacement for server-side validation of the field. It is purely a UI affordance. If the data must be checked at input time, a separate validator should be attached to the related schema node.

Creating a New Schema Type

Sometimes the default schema types offered by Colander may not be sufficient to model all the structures in your application.

If this happens, refer to the Colander documentation on Defining a New Type.