Widgets en Django
Un widget en Django controla cómo se renderiza un campo del formulario como HTML. Cada tipo de campo tiene un widget por defecto (TextInput para CharField, CheckboxInput para BooleanField, etc.), pero se puede cambiar o configurar según las necesidades del diseño.

Configurar widgets con attrs
La forma más sencilla de personalizar un widget es pasarle el diccionario attrs con atributos HTML adicionales:
from django import forms
class ProductoForm(forms.ModelForm):
class Meta:
model = Producto
fields = ['nombre', 'precio', 'categoria', 'descripcion', 'fecha_lanzamiento']
widgets = {
'nombre': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Nombre del producto',
'autofocus': True
}),
'precio': forms.NumberInput(attrs={
'class': 'form-control',
'min': '0',
'step': '0.01'
}),
'descripcion': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'maxlength': 500
}),
'fecha_lanzamiento': forms.DateInput(attrs={
'type': 'date',
'class': 'form-control'
}),
'categoria': forms.Select(attrs={
'class': 'form-select'
}),
}
Widgets para selección múltiple
class FiltroForm(forms.Form):
categorias = forms.ModelMultipleChoiceField(
queryset=Categoria.objects.all(),
widget=forms.CheckboxSelectMultiple(attrs={'class': 'checkbox-list'}),
required=False
)
etiquetas = forms.MultipleChoiceField(
choices=ETIQUETAS_CHOICES,
widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '5'}),
required=False
)
estado = forms.ChoiceField(
choices=[('', 'Todos'), ('activo', 'Activo'), ('inactivo', 'Inactivo')],
widget=forms.RadioSelect(attrs={'class': 'radio-group'}),
required=False
)
DateInput y TimeInput nativos
Para inputs de fecha y hora HTML5, específica el atributo type en el widget:
class EventoForm(forms.ModelForm):
class Meta:
model = Evento
fields = ['titulo', 'fecha_inicio', 'hora_inicio', 'fecha_fin']
widgets = {
'fecha_inicio': forms.DateInput(
attrs={'type': 'date', 'class': 'form-control'},
format='%Y-%m-%d'
),
'hora_inicio': forms.TimeInput(
attrs={'type': 'time', 'class': 'form-control'},
format='%H:%M'
),
'fecha_fin': forms.DateTimeInput(
attrs={'type': 'datetime-local', 'class': 'form-control'},
format='%Y-%m-%dT%H:%M'
),
}
Widget personalizado simple
Para casos más complejos, extiende la clase Widget:
from django.forms import Widget
from django.utils.html import format_html
class EstrellaRatingWidget(Widget):
"""Widget para seleccionar una puntuación de 1 a 5 estrellas."""
template_name = 'widgets/estrellas.html'
def __init__(self, maximo=5, attrs=None):
self.maximo = maximo
super().__init__(attrs)
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
context['widget']['maximo'] = range(1, self.maximo + 1)
context['widget']['valor_actual'] = int(value) if value else 0
return context
def value_from_datadict(self, data, files, name):
return data.get(name)
<!-- templates/widgets/estrellas.html -->
<div class="estrellas-widget">
{% for i in widget.maximo %}
<input type="radio"
name="{{ widget.name }}"
value="{{ i }}"
id="{{ widget.attrs.id }}_{{ i }}"
{% if widget.valor_actual == i %}checked{% endif %}>
<label for="{{ widget.attrs.id }}_{{ i }}" title="{{ i }} estrellas">★</label>
{% endfor %}
</div>
MultiWidget: campo con múltiples inputs
MultiWidget combina varios widgets en un único campo del formulario:
from django.forms import MultiWidget, TextInput, Select
class TelefonoWidget(MultiWidget):
"""Campo de teléfono con prefijo de país separado."""
def __init__(self, attrs=None):
widgets = [
Select(
choices=[('34', '+34 España'), ('1', '+1 EEUU'), ('44', '+44 UK')],
attrs={'class': 'form-select telefono-prefijo'}
),
TextInput(attrs={'class': 'form-control', 'placeholder': '123 456 789'}),
]
super().__init__(widgets, attrs)
def decompress(self, value):
"""Divide el valor almacenado en partes para cada widget."""
if value:
partes = value.split('-', 1)
return partes if len(partes) == 2 else [None, value]
return [None, None]
class TelefonoField(forms.MultiValueField):
widget = TelefonoWidget
def __init__(self, **kwargs):
fields = [
forms.ChoiceField(choices=[('34', '+34'), ('1', '+1'), ('44', '+44')]),
forms.CharField(max_length=20),
]
super().__init__(fields=fields, **kwargs)
def compress(self, data_list):
"""Combina las partes en un único valor para guardar."""
if data_list:
return f'{data_list[0]}-{data_list[1]}'
return ''
HiddenInput para datos ocultos
class PedidoForm(forms.ModelForm):
class Meta:
model = Pedido
fields = ['producto', 'cantidad', 'sesion_id']
widgets = {
'sesion_id': forms.HiddenInput(),
}
def __init__(self, *args, sesion_id=None, **kwargs):
super().__init__(*args, **kwargs)
if sesion_id:
self.initial['sesion_id'] = sesion_id
Los widgets son la capa de presentación del sistema de formularios de Django. Controlarlos correctamente permite integrar formularios con cualquier framework CSS y crear componentes de UI reutilizables sin modificar la lógica de validación.
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Django
Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Configurar widgets integrados de Django como DateInput, Select y CheckboxSelectMultiple. Añadir clases CSS y atributos HTML a los widgets con el parámetro attrs. Crear widgets personalizados extendiendo la clase Widget o MultiWidget. Usar HiddenInput, SplitDateTimeWidget y otros widgets avanzados. Controlar el renderizado de formularios con Form.renderer y plantillas personalizadas.