Fieldsets: organizar el formulario
Los fieldsets organizan los campos del formulario de edición en secciones con nombres y opciones:
@admin.register(Articulo)
class ArticuloAdmin(admin.ModelAdmin):
fieldsets = [
(None, {
'fields': ['titulo', 'slug', 'autor', 'categoria']
}),
('Contenido', {
'fields': ['resumen', 'contenido', 'imagen_destacada'],
}),
('Publicación', {
'fields': ['publicado', 'fecha_publicacion', 'destacado'],
'description': 'Controla la visibilidad y programación del artículo',
}),
('SEO', {
'fields': ['seo_titulo', 'seo_descripcion', 'seo_imagen'],
'classes': ['collapse'], # La sección empieza colapsada
}),
('Metadatos', {
'fields': ['fecha_creacion', 'fecha_modificacion', 'visitas'],
'classes': ['collapse'],
}),
]
prepopulated_fields = {'slug': ['titulo']}
readonly_fields = ['fecha_creacion', 'fecha_modificacion', 'visitas']
def get_readonly_fields(self, request, obj=None):
"""Campos de solo lectura que dependen del estado del objeto."""
readonly = list(self.readonly_fields)
if obj and obj.publicado:
readonly.append('autor') # No permitir cambiar el autor de artículos publicados
if not request.user.is_superuser:
readonly.append('destacado')
return readonly

Sobreescribir save_model y delete_model
@admin.register(Pedido)
class PedidoAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if not change: # Solo en creación
obj.creado_por = request.user
obj.ultima_modificacion_por = request.user
super().save_model(request, obj, form, change)
# Lógica posterior al guardado
if obj.estado == 'confirmado':
enviar_email_confirmacion(obj)
def delete_model(self, request, obj):
"""Lógica antes de eliminar."""
import logging
logger = logging.getLogger(__name__)
logger.info(f'Pedido {obj.id} eliminado por {request.user}')
super().delete_model(request, obj)
def get_queryset(self, request):
"""Filtrar el queryset según el usuario."""
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(creado_por=request.user)
Personalizar plantillas del admin
Django permite sobreescribir cualquier plantilla del admin creando el archivo en templates/admin/:
templates/
admin/
base_site.html # Cabecera global del admin
index.html # Página de inicio del admin
catalogo/
producto/
change_list.html # Listado de productos
change_form.html # Formulario de edición de producto
<!-- templates/admin/base_site.html -->
{% extends "admin/base.html" %}
{% block branding %}
<div id="site-name">
<a href="{% url 'admin:index' %}">
<img src="/static/img/logo_admin.png" alt="Logo" height="30">
Panel de administración
</a>
</div>
{% endblock %}
{% block extrastyle %}
{{ block.super }}
<style>
#header { background: #1a1a2e; }
.module h2 { background: #16213e; }
</style>
{% endblock %}
Campos calculados con anotaciones
from django.db.models import Count, Sum
@admin.register(Categoria)
class CategoriaAdmin(admin.ModelAdmin):
list_display = ['nombre', 'num_productos', 'valor_total_stock']
def get_queryset(self, request):
return super().get_queryset(request).annotate(
_num_productos=Count('producto'),
_valor_total=Sum('producto__precio'),
)
def num_productos(self, obj):
return obj._num_productos
num_productos.short_description = 'Productos'
num_productos.admin_order_field = '_num_productos'
def valor_total_stock(self, obj):
if obj._valor_total:
return f'{obj._valor_total:.2f} €'
return '0,00 €'
valor_total_stock.short_description = 'Valor total'
valor_total_stock.admin_order_field = '_valor_total'
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
Organizar los campos del formulario de edición en secciones con fieldsets. Definir readonly_fields para campos no editables en el formulario. Usar prepopulated_fields para generar slugs automáticamente. Personalizar plantillas del admin sobreescribiendo las plantillas base. Añadir lógica personalizada sobreescribiendo save_model y get_queryset.