Django form field types: Widgets, options and validations
Because of the uncontrolled nature of the Internet -- with its many types of devices, browsers and user experience levels -- it creates all kinds of demands on web forms regarding the types of data they must handle, as well as endless variations of how data must be sanitized to comply with the original purpose of a web form.
When you create web forms for Django applications you rely on Django form classes, which are composed of form fields. Each Django form field is important because it dictates a small piece of functionality that eventually composes the bulk of a web form's overall behavior.
Django form fields define two
types of functionality, a form field's HTML markup and its
server-side validation facilities. For example, a Django form field
translates into the actual HTML form markup (e.g. an
<input>
, <select>
or
<textarea>
tag), the HTML markup attributes
(e.g. length, whether a field can be left blank or if a field must
be disabled), as well as the necessary hooks to easily perform
server-side validation on a form field's data.
Django form fields define these two types of web form functionality out of necessity. Although browsers have progressed tremendously with technologies like HTML5 to provide out of the box form field validation through the HTML markup itself -- without JavaScript -- a browser is still in full control of an end user that with sufficient knowledge can bypass and feed whatever data he wants to a form. Therefore, it's standard practice to further inspect form field data once it's submitted by users to see if it complies with a form's rules, a process that's made very easy thanks to the use of Django form fields.
Table 6-2 illustrates the various Django forms fields, including: their type, the HTML they produce, their default widget and their validation behavior.
Table 6-2. Django form field types, generated HTML, default widget and validation behavior
Field type | Django form field type | HTML output | Default Django widget | Validation behavior |
---|---|---|---|---|
Boolean |
forms.BooleanField() |
<input type='checkbox' ...> |
forms.widgets.CheckboxInput() |
Generates HTML checkbox input markup to obtain a boolean True or False value; returns False when the checkbox is unchecked, True when the checkbox is checked. |
Boolean |
forms.NullBooleanField() |
<select> <option value="1" selected="selected"> Unknown </option> <option value="2"> Yes </option> <option value="3"> No </option> </select> |
forms.widgets.NullBooleanSelect() |
Works just like BooleanField but also allows "Unknown" value; returns None when the Unknown(1) value is selected, True when the Yes(2) value is selected and False when the No(3) value is selected. |
Text |
forms.CharField() |
<input type="text" ...> |
forms.widgets.TextInput() |
Generates HTML text input markup. |
Text (Specialized) |
forms.EmailField() |
<input type="email" ...> |
forms.widgets.EmailInput() |
Generates HTML email input markup. Note this HTML5 markup is for client-side email validation and only works if a browser supports HTML5. If a browser doesn't support HTML5, then it treats this markup as a regular text input. Django server-side form validation is done for email irrespective of HTML5 support. |
Text (Specialized) |
forms.GenericIPAddressField() |
<input type="text" ...> |
forms.widgets.TextInput() |
Works just like CharField, but server-side Django validates the (text) value can be converted to an IPv4 or IPv6 address (e.g.192.46.3.2, 2001:0db8:85a3:0000:0000:8a2e:0370:7334). |
Text (Specialized) |
forms.RegexField( regex='regular_expression') |
<input type="text" ...> |
forms.widgets.TextInput() |
Works just like CharField, but server-side Django validates the (text) value complies with the regular expression defined in regex. Note regex can be either a string that represents a regular expression (e.g. \.com$ for a string that ends in .com) or a compiled Python regular expression from Python's re package (e.g. re.compile('\.com$') ) |
Text (Specialized) |
forms.SlugField() |
<input type="text" ...> |
forms.widgets.TextInput() |
Works just like CharField, but server-side Django validates the (text) value can be converted to slug. In Django a 'slug' is a value that contains only lower case letters, numbers, underscores and hyphens, which is typically used to sanitize URLs and file names (e.g. the slug representation of 'What is a Slug ?! A sanitized-string' is what-is-a-slug-a-sanitized-string. |
Text (Specialized) |
forms.URLField() |
<input type="url" ...> |
forms.widgets.URLInput() |
Generates HTML url input markup. Note this HTML5 markup is for client-side url validation and only works if the browser supports HTML5. If a browser doesn't support HTML5 then it treats this markup as regular text input. Django server-side form validation is done for a url irrespective of HTML5 support. |
Text (Specialized) |
forms.UUIDField() |
<input type="text" ...> |
forms.widgets.TextInput() |
Works just like CharField, but server-side Django validates the (text) value is convertable to a UUID (Universally unique identifier). |
Text (Specialized) |
forms.ComboField(fields=[field_type1,field_type1]) |
<input type="text" ...> |
forms.widgets.TextInput() |
Works just like CharField, but server-side Django enforces the data pass rules for a list of Django form fields (e.g.ComboField(fields=[CharField(max_length=50), SlugField()]) enforces the data be a slug field with a maximum length of 50 characters) |
Text (Specialized) |
forms.MultiValueField(fields=[field_type1,field_type1]) |
Varies depending on field
list (e.g. for three CharField : |
forms.widgets.TextInput() |
Designed to create custom form fields made up of multiple pre-exisiting form fields (e.g. Social Security form field made up of three CharField()). It requires a subclass implementation (e.g. class SocialSecurityField(MultiValueField):) to include the base form fields and validation logic of the new field. |
Text (Specialized) / Files |
forms.FilePathField( path='directory') |
<select > <option value="directory/file_1"> file_1 </option> <option value="directory/file_2"> file_2 </option> <option value="directory/file_3"> file_3 </option> </select> |
forms.widgets.Select() |
Generates an HTML select list from files located on a server-side path directory. Note value is composed of path+filename and just displays filename |
Files |
forms.FileField() |
<input type="file" ...> |
forms.widgets.ClearableFileInput() |
Generates HTML file input markup so an end user is able to select a file through his web browser. In addition, it provides various utilities to handle post-processing of files |
Files (Specialized) |
forms.ImageField() |
<input type="file" ...> |
forms.widgets.ClearableFileInput() |
Generates HTML file input markup so an end user is able to select an image file through his web browser. Works just like FileField but provides additional utilities to handle post-processing of images using the Pillow package. Note this field forces you to install Pillow (e.g. pip install Pillow). |
Date/time |
forms.DateField() |
<input type="text" ...> |
forms.widgets.DateInput() |
Works just like CharField, but server-side Django validates the (text) value can be converted to a datetime.date, datetime.datetime or string formatted in a particular date format (e.g. 2017-12-25, 11/25/17). |
Date/time |
forms.TimeField() |
<input type="text" ...> |
forms.widgets.TextInput() |
Works just like CharField, but server-side Django validates the (text) value can be converted to a datetime.time or string formatted in a particular time format (e.g. 15:40:33, 17:44). |
Date/time |
forms.DateTimeField() |
<input type="text" ...> |
forms.widgets.DateTimeInput() |
Works just like CharField, but server-side Django validates the (text) value can be converted to a datetime.datetime, datetime.date or string formatted in a particular datetime format (e.g. 2017-12-25 14:30:59, 11/25/17 14:30). |
Date/time |
forms.DurationField() |
<input type="text" ...> |
forms.widgets.TextInput() |
Works just like CharField, but server-side Django validates the (text) value can be converted to a timedelta. Note Django uses the django.utils.dateparse.parse_duration() method as a helper, which means the string must match the format DD HH:MM:SS.uuuuuu (e.g. 2 1:10:20 for a timedelta of 2 days, 1 hour, 10 minutes, 20 seconds). |
Date/time |
forms.SplitDateTimeField() |
<input type="text" name="_0" ...> <input type="text" name="_1" ...> |
forms.widgets.SplitDateTimeWidget |
Works similar to DateTimeField but generates two separate text inputs for date & time, unlike DateTimeField which expects a single string with date & time. Validation wise Django enforces the date input can be converted to a datetime.date and the time input can be converted to a datetime.time. |
Number |
forms.IntegerField() |
<input type="number" ...> |
forms.widgets.NumberInput() |
Generates an HTML number input markup. Note this HTML5 markup is for client-side number validation and only works if the browser supports HTML5. If a browser doesn't support HTML5 then it treats this markup as a regular text input. Django server-side form validation is done for an integer number irrespective of HTML5 support. |
Number |
forms.DecimalField() |
<input type="number" ...> |
forms.widgets.NumberInput() |
Generates an HTML number input markup. Note this HTML5 markup is for client-side number validation and only works if a browser supports HTML5. If a browser doesn't support HTML5 then it treats this markup as a regular text input. Django server-side form validation is done for a decimal number irrespective of HTML5 support. |
Number |
forms.FloatField() |
<input type="number" ...> |
forms.widgets.NumberInput() |
Generates an HTML number input markup. Note this HTML5 markup is for client-side number validation and only works if a browser supports HTML5. If a browser doesn't support HTML5 then it treats this markup as a regular text input. Django server-side form validation is done for a float number irrespective of HTML5 support. |
Predefined values |
forms.ChoiceField( choices=tuple_of_tuples) |
<select> <option value="tuple1_1" selected="selected"> tuple1_2 </option> <option value="tuple_2_1"> tuple_2_2 </option> <option value="tuple_3_1"> tuple_3_2 </option> </select> |
forms.widgets.Select() |
Generates an HTML select list from a tuple of tuples defined through choices (e.g.((1,'United States'),(2,'Canada'),(3,'Mexico'))). Note with ChoiceField if no value is selected an empty string '' is passed for post-processing and if a value like '2' is selected a literal string is passed for post-processing, irrespective of the original data representation. See TypeChoiceField or clean form methods to override these last behaviors for empty values and string handling. |
Predefined values |
forms.TypeChoiceField( choices=tuple_of_tuples, coerce=coerce_function, empty_value=None) |
<select> <option value="tuple1_1" selected="selected"> tuple1_2 </option> <option value="tuple_2_1"> tuple_2_2 </option> <option value="tuple_3_1"> tuple_3_2 </option> </select> |
forms.widgets.Select() |
Works just like ChoiceField but provides extra post-processing functionality with the coerce and empty_value arguments. For example, with TypeChoiceField you can define a different default value with the empty_value arguement (e.g.empty_value=None) and you can define a coercion method with the coerce argument so the selected value is converted from its string representation (e.g. with coerce=int a value like '2' gets converted to 2 (integer) through the built-in int function). |
Predefined values |
forms.MultipleChoiceField( choices=tuple_of_tuples) |
<select multiple='multiple'> <option value="tuple1_1" selected="selected"> tuple1_2 </option> <option value="tuple_2_1"> tuple_2_2 </option> <option value="tuple_3_1"> tuple_3_2 </option> </select> |
forms.widgets.SelectMultiple() |
Generates an HTML select list for multiple values from tuple of tuples defined through choices (e.g.((1,'United States'),(2,'Canada'),(3,'Mexico'))). It works just like ChoiceField but allows multiple values to be selected, which become available as a list in post-processing. |
Predefined values |
forms.TypedMultipleChoiceField( choices=tuple_of_tuples, coerce=coerce_function, empty_value=None) |
<select multiple='multiple'> <option value="tuple1_1" selected="selected"> tuple1_2 </option> <option value="tuple_2_1"> tuple_2_2 </option> <option value="tuple_3_1"> tuple_3_2 </option> </select> |
forms.widgets.Select() |
Works just like MultipleChoiceField but provides extra post-processing functionality with the coerce and empty_value arguments. For example, with TypedMultipleChoiceField you can define a different default value with the empty_value argument (e.g.empty_value=None) and you can define a coercion method with the coerce argument so the selected value is converted from its string representation (e.g. with coerce=int a value like '2' gets converted to 2 (integer) through the built-in int function). |
As you can see in table 6-2, the
Django form fields provided out-of-the-box support the generation
of practically every HTML form input in existence, as well as
provide the necessary server-side validation for a wide array of
data types. For example, you can use the CharField()
form field type to capture standard text or the more specialized
EmailField()
form field type to ensure the captured
value is a valid email. Just as you can use
ChoiceField()
to generate a form list with predefined
values or DateField()
to enforce a form value is a
valid date.
The relationship between widgets and form fields
In table 6-2 you can see that
besides the actual Django form field syntax (e.g.
forms.CharField()
, forms.ImageField()
)
each form field is associated with a default widget.
Django widgets for the most part go unnoticed and are often mixed
together with the functionality of a form field itself (e.g. if you
want an HTML text input <input type="text"..>
you use forms.CharField()
). However, when you require
changes to the HTML produced by a form field or the way a form
field's data is initially processed, you'll need to work with
widgets.
To make matters a little more
confusing, there are many options you specify on form fields that
end up being used as part of a widget. For example, the form field
forms.CharField(max_length=25)
tells Django to limit a
value to a maximum of 25 characters upon processing, but this same
max_length
option is passed to the
forms.widgets.TextInput()
widget to generate the HTML
<input type="text" maxlength="25"...>
to enforce
the same rule on the browser via the HTML
maxlength="25"
attribute. So in this case, you can
actually change the HTML output through a form field option,
without even knowing about widgets!
So do you really need to work with widgets to change the HTML produced by a form field ? The answer is it depends. A lot of form fields options are automatically passed to a widget behind the scenes in effect altering the generated HTML, but make no mistake about it, it's a widget that's tasked with generating the HTML and not a form field. For cases when a form field's options can't achieve a desired HTML output, then it becomes necessary to change a form field's widget to achieve a custom HTML output.
In upcoming sections in this chapter, I'll expand on the topic of Django widgets and describe how to override and customize a Django form field's default widget. Now that you know about the existence of Django widgets and their relationship with Django form fields, I'll continue on the topic of Django form fields and explain the various Django form field options and their validation behavior.
Table 6-2 shows all the built-in form fields in Django, which can mislead you to believe the same table also shows all Django built-in form widgets. This is not the case. The widget column in table 6-2 only shows the default widgets assigned to all built-in form fields. There are a few more Django built-in widgets you can use that are also included in the forms.widgets. package:
- PasswordInput.- Widget for password field (e.g. displays **** as a user types text). Also supports re-display of a field value after validation error.
- HiddenInput.- Widget for hidden field (e.g. <input type='hidden'...>
- MultipleHiddenInput.- Like HiddenInput but for multiple values (i.e. a list).
- Textarea.- Widget for text area field (e.g.<textarea></textarea>).
- RadioSelect.- Like the Select widget, but generates a list of radio buttons (e.g. <ul><li><input type="radio"></li>.. </ul>)
- CheckboxSelectMultiple.- Like the SelectMultiple widget, but generates a list of checkboxes (e.g. <ul><li><input type="checkbox"></li>.. </ul>)
- TimeInput.- Like the DateTimeInput widget, but for time input only (e.g. 13:54, 13:54:59)
- SelectDateWidget.- Widget to generate three Select widgets for date (e.g. select widget for day, select widget for month, select widget for year).
- SplitHiddenDateTimeWidget.- Like the SplitDateTimeWidget widget, but uses Hidden input for date and time.
- FileInput.- Like the ClearableFileInput widget, but without a checkbox input to clear the field's value.
Future sections in this chapter which describe a form field's widget argument and how to customize Django widgets, provide more context on how and when to use these additional built-in widgets.
Empty, default and predetermined values: required, initial and choices.
By default, all Django form
fields are marked as required which means every field must contain
a value to pass validation. The required
argument is
valid on all Django form fields described in table 6-2 and in
addition to enforcing a value is not empty on the server-side, the
HTML 5 required
attribute is also assigned to a form
field so a user's browser also enforce validation.
Tip You can initialize a form with use_required_field=False to forgo the use of the HTML 5 required attribute. See the previous sub-section entitled 'Initialize forms'.
If you want to allow a field
value to be left empty -- None
or empty string
''
-- then a field must be assigned the
required=False
argument.
You can also assign a default
value to a field through the initial
argument. The
initial argument is equally valid across all Django form fields
described in table 6-2 and is described in detail in the previous
sub-section 'Initialize forms'.
For cases in which you don't want
to allow a user the ability to introduce open-ended values for a
field you can restrict a field's values to a predetermined set of
values through the choices
argument. If you want to
use the choices
attribute you must use a form field
data type designed to generate an HTML <select>
list such as forms.ChoiceField()
,
forms.MultipleChoiceField
or
forms.FielPathField()
. The choices
argument cannot be used on data types like
forms.CharField()
designed for open-ended input.
Limiting text values: max_length, min_length, strip and validators
Form field data types that accept
text such as CharField()
, EmailField()
and others described in table 6-2, can accept both the
max_length
and min_length
arguments to
restrict a field's value to a maximum and minimum character length,
respectively.
The strip
argument
is used to apply Python's strip()
method -- which
strips all trailing and leading whitespace -- to a field's value.
The strip
argument can only be used on two Django
field data types, CharField()
which defaults to
strip=True
and RegexField()
which
defaults to strip=False
.
To apply more advanced limitation
rules on fields that accept text values, see the previous
sub-section on 'Validating form values' which describes
validators
and other techniques to limit field
values.
Limiting number values: max_value, min_value, max_digits, decimal_places and validators.
Form field data types that accept
numbers such as IntegerField()
,
DecimalField()
and FloatField()
can
accept both the max_value
and min_value
arguments to restrict the upper and lower bounds of field's number
value, respectively.
In addition, the
DecimalField()
data type which accepts more elaborate
number types can use the max_digits
argument to
restrict the maximum number of digits in a value or the
decimal_places
argument to specify the maximum number
of decimal places in a value.
To apply more advanced limitation
rules on fields that accept number values, see the previous
sub-section on 'Validating form values' which describes
validators
and other techniques to limit field
values.
Error messages: error_messages
Every Django field data type has
built-in error messages. For example, when a field data type is
required
and no value is added by a user, Django
assigns the error message This field is required
to
the field, as part of a form's errors
dictionary.
Similarly, if a field data type uses the max_length
argument and the value provided by a user exceeds this threshold,
Django creates the error message Ensure this value has at
most X characters (it has X)
.
As described earlier in listing
6-18, Django error messages are typically given a message error
code in addition to the error message itself. And it's these
message error codes which are used to assign custom messages via
the error_messages
argument.
The error_messages
argument expects a dictionary where each key is the message error
code and its value a custom error message. For example, to provide
a custom message for the required
code you would use
the syntax
forms.CharField(error_messages={"required":"Please, pretty
please provide a comment"})
.Similarly, if you expect a form
field to violate its max_length
value, you would
assign a custom error message through the max_length
code (e.g. error_messages={"max_length":"This value exceeds
its max length value"}
).
Error codes generally map
directly to the rule they violate (e.g. if a
forms.IntegerField
violates its
max_value
, Django uses the max_value
code
to assign a default error message, which you can override by using
this code). However, there are over two dozen built-in error
message codes (e.g. 'missing'
,
'contradiction'
) some of which are not too obvious.
For example, a forms.ImageField
can generate the error
message 'Upload a valid image. The file you uploaded was
either not an image or a corrupted image.'
, which uses the
'invalid_image'
error code, meaning that to override
this default error message you would need to know the error code
beforehand to declare it as part of
error_messages
.
For the most part, customizing
error messages for some of the more esoteric error codes is rarely
needed. But if you're having trouble customizing error messages for
a given field because you can't determine its error code, a little
logging debugging on a form errors dictionary (e.g.
form.errors.as_json()
or some of the other methods in
table 6-1) can quickly net you a form's error codes to override the
messages with the error_messages
argument.
Field layout values: label, label_suffix, help_text
When you output a form field in a
template besides the essential HTML form field markup (e.g.
<input type="text">
) it's almost always
accompanied by a human-friendly descriptor to indicate what a field
is for (e.g. Email: <input type="text">
). This
human-friendly descriptor is called a label and by default in
Django it's assigned the same value as the field name.
To customize a form field's label
you can use the label
argument. For example, to
provide a more descriptive label for a field named email you can
use the syntax email = EmailField(label="Please provide your
email")
. By default, all labels on a form are accompanied by
the :
symbol which functions as a suffix. You can
further customize the output of field labels with the
label_suffix
argument on individual form fields or the
form instance itself.
For example, the syntax
email = EmailField(label_suffix='-->')
overrides
the default suffix label on the email field for the
-->
symbol.
Tip The label_suffix can also be used to
initialize a form (e.g. form =
ContactForm(label_suffix='-->')
) so all fields receive a
label suffix, instead of doing it field by field. See the
'Initialize forms' section earlier for more details.
In certain circumstances it can
be helpful to add more explicit instructions to a form field, for
such cases you can use the help_text
argument.
Depending on the template layout you use, the
help_text
value is added right next to the HTML form
field markup.
For example, the syntax
comment = CharField(help_text="Please be as specific as
possible to receive a quick response")
generates the given
html_text
value right next to the comment
input field (e.g. Please be as specific as possible to
receive a quick response <input type="text">
). The
next section 'Set up the layout for Django forms in templates' goes
into greater detail on the use of help_text
and other
form layout properties.