Personalización de ModelAdmin en Django

Intermedio
Django
Django
Actualizado: 18/04/2026

list_display

list_display controla las columnas que aparecen en el listado del modelo:

from django.contrib import admin
from django.utils.html import format_html
from .models import Producto

@admin.register(Producto)
class ProductoAdmin(admin.ModelAdmin):
    list_display = [
        'nombre',
        'precio_formateado',   # Método del ModelAdmin
        'categoria',
        'activo',
        'fecha_creacion',
        'imagen_preview'        # Método con HTML seguro
    ]

    def precio_formateado(self, obj):
        return f'{obj.precio} €'
    precio_formateado.short_description = 'Precio'
    precio_formateado.admin_order_field = 'precio'  # Permite ordenar por este campo

    def imagen_preview(self, obj):
        if obj.imagen:
            return format_html('<img src="{}" width="50" height="50">', obj.imagen.url)
        return '—'
    imagen_preview.short_description = 'Imagen'

Diagrama conceptual de Personalización de ModelAdmin en Django

list_filter

list_filter añade una barra lateral de filtros para facilitar la navegación:

list_filter = [
    'activo',
    'categoria',
    'fecha_creacion',
    ('precio', admin.filters.SimpleListFilter),  # Filtro personalizado
]

# Filtro personalizado
class RangoPrecioFilter(admin.SimpleListFilter):
    title = 'rango de precio'
    parameter_name = 'rango_precio'

    def lookups(self, request, model_admin):
        return [
            ('barato', 'Menos de 20 €'),
            ('medio', 'Entre 20 € y 100 €'),
            ('caro', 'Más de 100 €'),
        ]

    def queryset(self, request, queryset):
        if self.value() == 'barato':
            return queryset.filter(precio__lt=20)
        elif self.value() == 'medio':
            return queryset.filter(precio__gte=20, precio__lte=100)
        elif self.value() == 'caro':
            return queryset.filter(precio__gt=100)
        return queryset

search_fields

search_fields habilita el buscador en el listado. Soporta lookups (__icontains por defecto):

search_fields = [
    'nombre',
    'descripcion',
    'categoria__nombre',   # Búsqueda en campo de modelo relacionado
    'proveedor__email',
]

Resto de opciones clave

@admin.register(Producto)
class ProductoAdmin(admin.ModelAdmin):
    list_display = ['nombre', 'precio', 'activo', 'categoria', 'fecha_creacion']
    list_filter = [RangoPrecioFilter, 'activo', 'categoria']
    search_fields = ['nombre', 'descripcion']
    ordering = ['-fecha_creacion', 'nombre']  # Orden por defecto
    list_per_page = 25                         # Objetos por página
    list_max_show_all = 200                    # Máximo al usar "Mostrar todos"
    list_editable = ['activo', 'precio']       # Editable directamente en el listado
    date_hierarchy = 'fecha_creacion'          # Navegación jerárquica por fecha
    show_full_result_count = False             # Desactiva el conteo total (mejora rendimiento)

    # Columnas que se pueden ordenar por clic en cabecera
    sortable_by = ['nombre', 'precio', 'fecha_creacion']

Personalización del formulario de edición

@admin.register(Producto)
class ProductoAdmin(admin.ModelAdmin):
    # Organizar campos en secciones
    fieldsets = [
        ('Información básica', {
            'fields': ['nombre', 'descripcion', 'categoria']
        }),
        ('Precio y disponibilidad', {
            'fields': ['precio', 'precio_oferta', 'activo', 'stock'],
            'classes': ['collapse'],  # Sección colapsable
        }),
        ('Multimedia', {
            'fields': ['imagen', 'video_url'],
            'description': 'Archivos multimedia del producto',
        }),
        ('Metadatos', {
            'fields': ['fecha_creacion', 'fecha_modificacion', 'creado_por'],
            'classes': ['collapse'],
        }),
    ]

    readonly_fields = ['fecha_creacion', 'fecha_modificacion', 'creado_por']
    prepopulated_fields = {'slug': ['nombre']}  # Genera slug automáticamente

    # Autocompletar campos relacionados (require search_fields en el modelo relacionado)
    autocomplete_fields = ['categoria', 'etiquetas']

    def save_model(self, request, obj, form, change):
        if not change:  # Solo al crear
            obj.creado_por = request.user
        super().save_model(request, obj, form, change)
Alan Sastre - Autor del tutorial

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 list_display para controlar las columnas del listado de modelos. Usar list_filter para añadir filtros laterales por campo. Implementar search_fields para búsqueda de texto en el listado. Definir ordering y list_per_page para ordenación y paginación. Usar list_editable para editar campos directamente desde el listado.