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:
1import colander
2
3class Person(colander.MappingSchema):
4 name = colander.SchemaNode(colander.String())
5 age = colander.SchemaNode(colander.Integer(),
6 validator=colander.Range(0, 200))
7
8class People(colander.SequenceSchema):
9 person = Person()
10
11class Schema(colander.MappingSchema):
12 people = People()
13
14schema = 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
colander.Sequence
colander.String
colander.Integer
colander.Float
colander.Decimal
colander.Boolean
colander.Date
colander.DateTime
colander.Tuple
colander.Time
colander.Money
colander.Set
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:
1import colander
2
3from deform import Form
4from deform.widget import TextAreaWidget
5
6class Person(colander.MappingSchema):
7 name = colander.SchemaNode(colander.String(),
8 widget=TextAreaWidget())
9 age = colander.SchemaNode(colander.Integer(),
10 validator=colander.Range(0, 200))
11
12class People(colander.SequenceSchema):
13 person = Person()
14
15class Schema(colander.MappingSchema):
16 people = People()
17
18schema = Schema()
19
20myform = 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:
1from deform import Form
2from deform.widget import TextInputWidget
3
4myform = Form(schema, buttons=('submit',))
5myform['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:
1from deform import Form
2from deform.widget import TextAreaWidget
3
4myform = Form(schema, buttons=('submit',))
5myform['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:
1from deform import Form
2from deform.widget import TextAreaWidget
3
4myform = Form(schema, buttons=('submit',))
5myform.set_widgets({'people.person.name':TextAreaWidget(),
6 '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 form attributes¶
HTML5 introduced many attributes to HTML forms, most of which appear in the HTML <input>
element as a type
attribute.
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 form attributes.
They also support any arbitrary attribute, such as readonly
or disabled
.
This is useful, for example, when you want to use any of the following new HTML5 input types.
color
date
datetime
datetime-local
email
month
number
range
search
tel
time
url
week
You can also set placeholders, use multiple file uploads, and set some client-side validation requirements without JavaScript.
The following Python code will generate the subsequent HTML and rendered HTML5 number 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
New in version 2.0.7: Arbitrary form control attributes, providing support for HTML5 forms.
Using readonly
in HTML form control attributes¶
Note
Naming things is hard.
In Deform an unfortunate naming decision was made for readonly
when rendering a form without any form controls using a "readonly" template.
Oops.
Looking back, we ought to have named it viewonly
, static
, immutable
, or readable
to avoid confusion with the HTML attribute readonly.
The readonly
HTML form control attribute makes the element not mutable, meaning the user cannot edit the control.
When "readonly": "readonly"
is one of the items in a dict passed into the attributes
option when creating a widget, the rendered widget both prevents the user from changing the value, and if the form does not pass validation after submitted then the field value will be displayed.
readonly
is supported by most form controls, but not all.
Deform adds some logic to add read-only support for a few of those form controls, as described below.
CheckboxWidget
andCheckboxChoiceWidget
Due to the nature of how checkbox values are processed, the
readonly
attribute has no effect. To achieve a read-only behavior, pass inattributes={"onclick": "return false;"}
. This will render as an inline JavaScriptonclick="return false;"
for each checkbox item.MoneyInputWidget
The provided value will be displayed in the input and be not editable.
RadioChoiceWidget
For the selected value it will render an attribute in the HTML as
readonly="readonly"
, and for all other values asdisabled="disabled"
.SelectWidget
For selection of single options only, the selected value will render an attribute in the HTML as
readonly="readonly"
, and for all other values asdisabled="disabled"
. Multiple selections, set by themultiple=True
option, do not support thereadonly
attribute on the<select>
element. For multiple selections, use theSelectizeWidget
.SelectizeWidget
For both single and multiple selections, the selected value or values will be rendered as selected, and the others will not be selectable. Selectize uses JavaScript to "lock" the form control.
TextAreaWidget
The provided value will be displayed in the input and be not editable.
TextInputWidget
The provided value will be displayed in the input and be not editable.
Warning
Regardless of using readonly
, never trust user input or client-side only validation, and validate submitted data on the server side to ensure that values are not altered.
New in version 2.0.15: Enhanced readonly
form control attribute.
Using Selectize Widget¶
The Selectize widget is based on the jQuery plugin selectize.js.
Configuration options of the Selectize widget can be passed in as a dict to the keyword argument selectize_options
.
These options are rendered as inline JavaScript in the HTML widget.
See the available configuration options at the selectize.js project.
By default, Deform treats any options with a ""
value as normal by virtue of setting allowEmptyOption
to True
.
This will render in HTML as <option value="">- Select -</option>
.
You can override this default value as follows.
widget=deform.widget.SelectizeWidget(
values=choices,
selectize_options={
"allowEmptyOption": False,
},
)
Using the above pattern, you can configure the Selectize widget for all of its configuration options.
Try a basic demonstration of the Selectize widget. Additional options are also demonstrated.
New in version 2.0.15.
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
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.