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 : <input type="text" ...><input type="text" ...><input type="text" ...>; for two CharField: <input type="text" ...><input type="text" ...>

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.

DJANGO 'hidden' built-in widgets
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 set_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.