Customizing HTML Output with Widgets
To control how form fields are rendered in HTML, you use Widgets. Widgets handle the translation between Python data and HTML output, as well as the extraction of data from submission dictionaries.
Adding Custom HTML Attributes
The most common way to customize a widget is by passing the attrs argument to its constructor. This allows you to add CSS classes, placeholders, or any other HTML attribute to the rendered element.
from django.forms.widgets import TextInput, Textarea
# Adding a CSS class and a placeholder to a text input
name_widget = TextInput(attrs={
'class': 'form-control',
'placeholder': 'Enter your full name'
})
# Customizing rows and columns for a textarea
comment_widget = Textarea(attrs={
'rows': 5,
'cols': 50,
'class': 'custom-textarea'
})
When render() is called, these attributes are merged with the widget's defaults using build_attrs().
Customizing Choice Widgets
Choice-based widgets like Select, RadioSelect, and CheckboxSelectMultiple allow you to control how options are presented.
Using Radio Buttons or Checkboxes
If you want to change a standard dropdown to radio buttons or multiple checkboxes, swap the widget class:
from django.forms.widgets import RadioSelect, CheckboxSelectMultiple
# Render choices as radio buttons
gender_widget = RadioSelect(choices=[('M', 'Male'), ('F', 'Female')])
# Render choices as a list of checkboxes
hobby_widget = CheckboxSelectMultiple(choices=[
('reading', 'Reading'),
('coding', 'Coding'),
('gaming', 'Gaming')
])
Grouping Choices (Optgroups)
You can group choices by passing a nested structure to the choices argument. The ChoiceWidget.optgroups() method handles the rendering of these groups.
from django.forms.widgets import Select
media_choices = [
('Audio', [
('vinyl', 'Vinyl'),
('cd', 'CD'),
]),
('Video', [
('vhs', 'VHS'),
('dvd', 'DVD'),
]),
('unknown', 'Unknown'),
]
widget = Select(choices=media_choices)
Customizing File Uploads
The ClearableFileInput provides a way to clear an existing file while uploading a new one. You can customize the labels used in the rendered HTML.
from django.forms.widgets import ClearableFileInput
class CustomFileWidget(ClearableFileInput):
clear_checkbox_label = "Remove this file"
initial_text = "Current file"
input_text = "Select new file"
widget = CustomFileWidget()
Splitting Inputs with MultiWidget
MultiWidget allows you to combine multiple HTML inputs into a single logical field. A common example is SplitDateTimeWidget, which uses two separate inputs for date and time.
Customizing SplitDateTimeWidget
You can pass specific attributes to the sub-widgets (DateInput and TimeInput) during initialization:
from django.forms.widgets import SplitDateTimeWidget
widget = SplitDateTimeWidget(
date_attrs={'class': 'date-picker', 'type': 'date'},
time_attrs={'class': 'time-picker', 'type': 'time'},
date_format='%Y-%m-%d',
time_format='%H:%M'
)
Creating a Custom MultiWidget
To create your own multi-input widget, subclass MultiWidget and implement the decompress() method. This method takes a single value and returns a list of values for the sub-widgets.
from django.forms.widgets import MultiWidget, TextInput
class NameSplitWidget(MultiWidget):
def __init__(self, attrs=None):
widgets = [
TextInput(attrs={'placeholder': 'First Name'}),
TextInput(attrs={'placeholder': 'Last Name'}),
]
super().__init__(widgets, attrs)
def decompress(self, value):
if value:
return value.split(' ', 1)
return [None, None]
# Usage
widget = NameSplitWidget()
html = widget.render('full_name', 'John Doe')
Creating a Custom Widget Template
For complete control over the HTML, you can subclass Widget (or Input) and provide a custom template_name. You can also override get_context to pass extra data to your template.
from django.forms.widgets import Widget
class ColorPickerWidget(Widget):
template_name = 'vforms/widgets/color_picker.html'
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
# Add custom data for the template
context['widget']['swatches'] = ['#ff0000', '#00ff00', '#0000ff']
return context
The Widget.render() method will use the configured renderer to render your template with the context provided.
Troubleshooting and Gotchas
Checkbox Missing Data
CheckboxInput.value_from_datadict() returns False if the field name is missing from the submitted data. This is because browsers do not send any value for unchecked checkboxes.
File Input Contradictions
If using ClearableFileInput, be aware that if a user both uploads a new file AND checks the "clear" checkbox, value_from_datadict() returns a special FILE_INPUT_CONTRADICTION object. Your field validation should handle this case.
Required Attribute on Select
The Select widget only renders the required HTML attribute if the first choice has an empty value. This prevents browsers from blocking submission when a default non-empty value is selected in a required field.
MultiWidget Decompression
If you forget to implement decompress() in a MultiWidget subclass, it will raise a NotImplementedError when the widget tries to render a non-list value.