Personalización del Admin de Django en 2026: Acciones, Filtros y Preguntas de Entrevista
Guía completa sobre la personalización de la interfaz de administración de Django en 2026. Acciones personalizadas, filtros avanzados y preguntas técnicas de entrevista para desarrolladores Django.

La interfaz de administración de Django continúa siendo una de las características más valoradas del framework en 2026. Esta funcionalidad integrada permite a los desarrolladores crear rápidamente interfaces de gestión de datos sin necesidad de escribir código frontend. Sin embargo, para requisitos empresariales específicos, la personalización resulta indispensable. Esta guía explora las técnicas avanzadas de personalización del admin de Django, incluyendo acciones personalizadas, filtros sofisticados y las preguntas más frecuentes en entrevistas técnicas.
La personalización del admin de Django permite ahorrar cientos de horas de desarrollo mientras se ofrece una experiencia de usuario óptima a los gestores de contenido.
Configuración Base del Admin de Django
Antes de explorar las personalizaciones avanzadas, es necesario comprender la configuración fundamental. Django 5.x introduce varias mejoras notables en la interfaz de administración.
from django.contrib import admin
from django.contrib.admin import AdminSite
from .models import Article, Category, Author
class CustomAdminSite(AdminSite):
site_header = "Panel de Administración"
site_title = "Admin Portal"
index_title = "Gestión de Contenido"
admin_site = CustomAdminSite(name='custom_admin')
@admin.register(Article, site=admin_site)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'status', 'published_at', 'view_count']
list_filter = ['status', 'category', 'published_at']
search_fields = ['title', 'content', 'author__name']
date_hierarchy = 'published_at'
ordering = ['-published_at']Esta configuración establece las bases de una interfaz de administración personalizada con un encabezado distintivo y opciones de filtrado esenciales.
Acciones Personalizadas en el Admin de Django
Las acciones personalizadas permiten realizar operaciones masivas sobre los objetos seleccionados. Esta funcionalidad resulta particularmente útil para flujos de trabajo editoriales.
from django.contrib import admin, messages
from django.utils import timezone
from django.core.mail import send_mass_mail
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'status', 'published_at']
actions = ['publish_articles', 'archive_articles', 'notify_authors']
@admin.action(description="Publicar artículos seleccionados")
def publish_articles(self, request, queryset):
updated = queryset.filter(status='draft').update(
status='published',
published_at=timezone.now()
)
self.message_user(
request,
f"{updated} artículo(s) publicado(s) exitosamente.",
messages.SUCCESS
)
@admin.action(description="Archivar artículos seleccionados")
def archive_articles(self, request, queryset):
queryset.update(status='archived', archived_at=timezone.now())
self.message_user(request, "Artículos archivados.", messages.WARNING)
@admin.action(description="Notificar a los autores")
def notify_authors(self, request, queryset):
emails = []
for article in queryset.select_related('author'):
if article.author.email:
emails.append((
f"Actualización: {article.title}",
f"Su artículo '{article.title}' ha sido actualizado.",
'noreply@example.com',
[article.author.email]
))
send_mass_mail(emails, fail_silently=True)
self.message_user(request, f"{len(emails)} notificación(es) enviada(s).")Las acciones con confirmación proporcionan una capa adicional de seguridad para operaciones críticas.
from django.contrib.admin import helpers
from django.template.response import TemplateResponse
@admin.action(description="Eliminar permanentemente con confirmación")
def delete_permanently(self, request, queryset):
if request.POST.get('confirm'):
count = queryset.count()
queryset.delete()
self.message_user(request, f"{count} elemento(s) eliminado(s) permanentemente.")
return None
context = {
**self.admin_site.each_context(request),
'title': 'Confirmar eliminación',
'queryset': queryset,
'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
}
return TemplateResponse(request, 'admin/confirm_delete.html', context)Filtros Personalizados Avanzados
Los filtros predeterminados de Django cubren los casos de uso comunes, pero los proyectos complejos frecuentemente requieren filtros a medida.
from django.contrib.admin import SimpleListFilter
from django.utils import timezone
from datetime import timedelta
class PublicationStatusFilter(SimpleListFilter):
title = 'Estado de publicación'
parameter_name = 'pub_status'
def lookups(self, request, model_admin):
return [
('recent', 'Publicado recientemente (7 días)'),
('this_month', 'Este mes'),
('scheduled', 'Programado'),
('needs_review', 'Requiere revisión'),
]
def queryset(self, request, queryset):
now = timezone.now()
if self.value() == 'recent':
return queryset.filter(
status='published',
published_at__gte=now - timedelta(days=7)
)
elif self.value() == 'this_month':
return queryset.filter(
status='published',
published_at__month=now.month,
published_at__year=now.year
)
elif self.value() == 'scheduled':
return queryset.filter(
status='scheduled',
published_at__gt=now
)
elif self.value() == 'needs_review':
return queryset.filter(
status='draft',
updated_at__lt=now - timedelta(days=30)
)
return queryset
class AuthorActivityFilter(SimpleListFilter):
title = 'Actividad del autor'
parameter_name = 'author_activity'
def lookups(self, request, model_admin):
return [
('active', 'Autores activos'),
('inactive', 'Autores inactivos'),
('top', 'Principales contribuyentes'),
]
def queryset(self, request, queryset):
from django.db.models import Count
if self.value() == 'active':
active_authors = Author.objects.filter(
articles__published_at__gte=timezone.now() - timedelta(days=90)
).values_list('id', flat=True)
return queryset.filter(author_id__in=active_authors)
elif self.value() == 'top':
top_authors = Author.objects.annotate(
article_count=Count('articles')
).filter(article_count__gte=10).values_list('id', flat=True)
return queryset.filter(author_id__in=top_authors)
return querysetLa integración de estos filtros en el admin se realiza de manera sencilla.
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'status', 'published_at', 'view_count']
list_filter = [
PublicationStatusFilter,
AuthorActivityFilter,
'category',
('published_at', admin.DateFieldListFilter),
]Personalización de la Visualización y los Formularios
La mejora de la experiencia de usuario requiere la personalización de formularios y visualización.
from django import forms
from django.utils.html import format_html
from django.urls import reverse
class ArticleAdminForm(forms.ModelForm):
class Meta:
model = Article
fields = '__all__'
widgets = {
'content': forms.Textarea(attrs={
'rows': 20,
'class': 'vLargeTextField'
}),
'summary': forms.Textarea(attrs={'rows': 3}),
}
def clean_title(self):
title = self.cleaned_data['title']
if len(title) < 10:
raise forms.ValidationError(
"El título debe contener al menos 10 caracteres."
)
return title
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
form = ArticleAdminForm
list_display = ['title', 'author_link', 'status_badge', 'published_at', 'view_count']
readonly_fields = ['created_at', 'updated_at', 'preview_link']
fieldsets = [
('Información principal', {
'fields': ['title', 'slug', 'author', 'category']
}),
('Contenido', {
'fields': ['summary', 'content', 'featured_image'],
'classes': ['wide']
}),
('Publicación', {
'fields': ['status', 'published_at', 'is_featured'],
'classes': ['collapse']
}),
('Metadatos', {
'fields': ['created_at', 'updated_at', 'preview_link'],
'classes': ['collapse']
}),
]
@admin.display(description='Autor')
def author_link(self, obj):
url = reverse('admin:blog_author_change', args=[obj.author.pk])
return format_html('<a href="{}">{}</a>', url, obj.author.name)
@admin.display(description='Estado')
def status_badge(self, obj):
colors = {
'draft': '#ffc107',
'published': '#28a745',
'archived': '#6c757d'
}
color = colors.get(obj.status, '#17a2b8')
return format_html(
'<span style="background: {}; color: white; padding: 3px 10px; '
'border-radius: 3px;">{}</span>',
color, obj.get_status_display()
)
@admin.display(description='Vista previa')
def preview_link(self, obj):
if obj.pk:
url = obj.get_absolute_url()
return format_html(
'<a href="{}" target="_blank" class="button">Ver vista previa</a>',
url
)
return "-"Optimización del Rendimiento
Para proyectos con grandes volúmenes de datos, la optimización de consultas se vuelve crucial.
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'category', 'status']
list_select_related = ['author', 'category']
list_prefetch_related = ['tags']
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related('author', 'category').prefetch_related('tags')
def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super().get_search_results(
request, queryset, search_term
)
# Búsqueda optimizada con índices
if search_term:
queryset = queryset.filter(
title__icontains=search_term
) | queryset.filter(
content__icontains=search_term
)
return queryset, use_distinctPreguntas de Entrevista sobre el Admin de Django
Durante las entrevistas técnicas para puestos de desarrollo Django, el dominio de la interfaz de administración es frecuentemente evaluado. A continuación se presentan las preguntas más comunes.
Pregunta 1: ¿Cómo restringir el acceso a ciertos campos según el rol del usuario?
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
if not request.user.is_superuser:
return ['author', 'published_at', 'view_count']
return []
def get_fieldsets(self, request, obj=None):
fieldsets = super().get_fieldsets(request, obj)
if not request.user.has_perm('blog.can_publish'):
# Ocultar sección de publicación para no editores
fieldsets = [fs for fs in fieldsets if fs[0] != 'Publicación']
return fieldsets
def has_delete_permission(self, request, obj=None):
if obj and obj.status == 'published':
return request.user.is_superuser
return super().has_delete_permission(request, obj)Pregunta 2: ¿Cómo implementar un sistema de auditoría en el admin?
from django.contrib.admin.models import LogEntry, CHANGE
from django.contrib.contenttypes.models import ContentType
class AuditableAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if change:
obj.modified_by = request.user
else:
obj.created_by = request.user
super().save_model(request, obj, form, change)
def log_change(self, request, obj, message):
super().log_change(request, obj, message)
# Log personalizado adicional
AuditLog.objects.create(
user=request.user,
action='change',
model_name=obj._meta.model_name,
object_id=obj.pk,
changes=str(message)
)Pregunta 3: ¿Cómo crear un widget de autocompletado personalizado?
from django.contrib.admin.widgets import AutocompleteSelect
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
autocomplete_fields = ['author', 'category']
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'author':
kwargs['queryset'] = Author.objects.filter(is_active=True)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
search_fields = ['name', 'email']
ordering = ['name']¿Listo para aprobar tus entrevistas de Django?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
Buenas Prácticas y Recomendaciones
La personalización efectiva del admin de Django se fundamenta en varios principios clave. La separación de responsabilidades garantiza un mantenimiento sencillo del código. Los filtros y acciones deben mantenerse simples y eficientes, evitando consultas N+1 mediante select_related y prefetch_related.
El uso apropiado de los permisos de Django permite crear interfaces adaptadas a cada rol de usuario. Las pruebas unitarias para acciones personalizadas aseguran la confiabilidad de las operaciones masivas.
Finalmente, la documentación de las personalizaciones facilita la integración de nuevos desarrolladores y el mantenimiento a largo plazo del proyecto.
Conclusión
La interfaz de administración de Django ofrece una flexibilidad notable para satisfacer los requisitos empresariales más diversos. Las acciones personalizadas, filtros avanzados y técnicas de optimización presentadas en esta guía constituyen una base sólida para cualquier proyecto Django profesional. El dominio de estos conceptos representa una ventaja significativa durante las entrevistas técnicas y en la práctica diaria del desarrollo Django.
¿Sabrías detectar el bug en Django?
Un fragmento real, un bug oculto, un intento al día. Sin cuenta para probar.

Escrito por
Anthony Fillion-MailletFundador de SharpSkill
Desarrollador fullstack desde hace más de 10 años. Dirige SharpSkill y responde por todo lo que se publica aquí.
Actualizado el 13 de septiembre de 2026
Compartir
Artículos relacionados

Middleware Avanzado en Django 2026: Middleware Personalizado, Logging y Preguntas de Entrevista
Guía completa sobre middleware avanzado en Django 2026. Creación de middleware personalizado, logging estructurado, manejo de errores y preguntas de entrevista técnica para desarrolladores Python.

Django Signals vs Celery Tasks en 2026: Cuándo Usar Cada Uno y Preguntas de Entrevista
Aprende cuándo usar señales Django versus tareas Celery. Cubre el manejo de eventos síncrono vs asíncrono, implicaciones de rendimiento y preguntas de entrevista para desarrolladores Django.

Django Channels en 2026: WebSockets, Tiempo Real y Preguntas de Entrevista
Guía completa sobre Django Channels 4.x para WebSockets y comunicación en tiempo real. Configuración de Redis, consumers async y preguntas de entrevista técnica.