Interfaz administrativa¶
Django básico
Como se ha comentado previamente Django proporciona una interfaz administrativa para manejar los modelos de nuestro proyecto, prácticamente sin esfuerzo.
Pero existen una gran cantidad de opciones de personalización de la interfaz administrativa que iremos viendo en esta sección.
Registrar un modelo¶
Supongamos un ejemplo en el que tenemos un modelo Post y queremos gestionar dicho modelo desde la interfaz administrativa. Para ello debemos registrarlo en el siguiente fichero:
from django.contrib import admin#(1)!
from .models import Post#(2)!
@admin.register(Post)#(3)!
class PostAdmin(admin.ModelAdmin):#(4)!
pass#(5)!
- Necesitamos importar el módulo
adminpara utilizar sus funcionalidades. - Importamos el modelo que queremos activar. Es habitual que los modelos estén en la misma aplicación, por lo cual podemos importarlos desde
.models - Este decorador registra el modelo que le pasamos como argumento.
- Necesitamos crear una clase que hereda de
ModelAdmin. El nombre de la clase (por convención) suele ser el nombre del modeloAdmin. - No es necesario inicialmente que implementemos nada más.
Cuando accedamos ahora a http://localhost/admin/ veremos que estará disponible un enlace Posts para gestionar los objetos del modelo Post.
Singular/Plural
El nombre del modelo debería estar escrito en singular. Esto se hace aún más evidente cuando Django nos lo muestra en plural desde la interfaz administrativa.
Mostrar campos¶
Cuando registramos un modelo, sus objetos aparecen en la interfaz administrativa a través del método __str__(). Si no está implementado veremos algo como: <Post: Post object (1)>
Por lo tanto es siempre recomendable implementar el método __str__() de nuestros modelos.
Pero es muy habitual querer mostrar los campos del objeto (que interesen) en vez de su representación «string» en la interfaz administrativa. Para ello haremos uso del atributo de clase list_display.
Si seguimos con el ejemplo del «post» de un blog, tendríamos lo siguiente:
from django.contrib import admin
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'slug')#(1)!
-
- Se puede usar tanto una tupla como una lista.
titleyslugson campos del modeloPost.
Este sería el resultado en la interfaz administrativa:

Campos de búsqueda¶
Django nos permite habilitar la búsqueda de objetos en la interfaz administrativa. Para ello simplemente tenemos que hacer uso del atributo search_fields.
Por ejemplo vamos a añadir búsqueda de «posts» a través de su título y/o contenido:
from django.contrib import admin
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'slug')
search_fields = ('title', 'content')#(1)!
-
- Se puede usar tanto una tupla como una lista.
titleycontentson campos del modeloPost.
Con este pequeño cambio veremos que aparece un cuadro de búsqueda en la interfaz administrativa:

Filtros de lista¶
Otra de las funcionalidades que ofrece Django en la interfaz administrativa es añadir filtros de lista sobre determinados campos del modelo. Para ello tenemos que utilizar el atributo list_display.
Pensemos en un ejemplo de «post» al que hemos añadido un campo published que indica si el «post» está o no publicado:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=256)
slug = models.SlugField(max_length=256)
content = models.TextField()
published = models.BooleanField(default=False)
def __str__(self):
return self.title
Podemos añadir un filtro sobre dicho campo para consultar más fácilmente aquellos «posts» públicos y/o privados:
from django.contrib import admin
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'slug')
search_fields = ('title', 'content')
list_filter = ('published',)#(1)!
-
- Se puede usar tanto una tupla como una lista.
publishedes un campo del modeloPost.
Ahora ya disponemos de un «widget» donde poder filtrar:

Campos autocompletados¶
Otra de las funcionalidades existentes en la interfaz administrativa de Django es rellenar campos de manera automática a partir del valor de otros campos. Para conseguir este resultado tendremos que utilizar el atributo prepopulated_fields.
Por ejemplo podríamos querer que el «slug» de un «post» se autocomplete mediante su título:
from django.contrib import admin
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'slug')
search_fields = ('title', 'content')
list_filter = ('published',)
prepopulated_fields = {'slug': ('title',)}#(1)!
-
La estructura esperada es un diccionario donde:
- Cada clave es un campo a autocompletar.
- Cada valor es un iterable (lista o tupla) de campos desde los que se toma el contenido para autocompletar.
Con esto conseguimos que a medida que escribimos el título de un «post» se vaya autocompletando su «slug» correspondiente:

Acciones de administración¶
Django intermedio
En la interfaz administrativa de Django podemos realizar acciones sobre objetos de modelo. Estas acciones están predefinidas (añadir, borrar, editar, etc.), pero tenemos la posibilidad de incorporar nuevas acciones de administración personalizadas.
Supongamos un ejemplo en el que pretendemos regenerar todos los «slugs» de los «posts» existentes en nuestra base de datos, a partir de cada uno de sus títulos:
from django.contrib import admin
from django.utils.text import slugify
from .models import Post
@admin.action(description='Regenerate slug (from title) for selected posts')#(1)!
def regenerate_slug(modeladmin, request, queryset):#(2)!
for post in queryset:#(3)!
post.slug = slugify(post.title)#(4)!
post.save()
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'slug')
actions = [regenerate_slug]#(5)!
- Hay que registrar la acción indicando una descripción coherente.
- La función recibe:
- El modelo de administración (
PostAdminen este caso). - La petición HTTP.
- El «queryset» con los objetos seleccionados desde la interfaz administrativa.
- El modelo de administración (
- Recorremos todos los «posts» de la consulta.
- Actualizamos su «slug».
- El atributo
actionsdefine una lista de acciones.
De esta manera nos aparecerá una nueva acción a la hora de gestionar los «posts» en la interfaz administrativa:

Claves ajenas¶
Django intermedio
Relaciones uno a muchos¶
Es habitual manejar claves ajenas en nuestros modelos. Por defecto, Django las muestra en la interfaz administrativa como desplegables donde seleccionar la instancia correspondiente.
Como ejemplo vamos a partir de este escenario en el que un «post» puede tener varios comentarios.
El aspecto del formulario para añadir un comentario sería el siguiente:

Veamos otras maneras de presentar la clave ajena a «post» para que sea más accesible:
Mediante el atributo raw_id_fields se pueden definir un conjunto de campos para los que se habilita una búsqueda en ventana.
from django.contrib import admin
from .models import Comment
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
raw_id_fields = ('post',)
El formulario para añadir un comentario quedaría de la siguiente manera:

Mediante el atributo autocomplete_fields se pueden definir un conjunto de campos para los que se habilita una búsqueda con autocompletado (a medida que se escribe).
from django.contrib import admin
from .models import Comment
@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
autocomplete_fields = ('post',)
Error admin.E040
Es probable que el código anterior lance el siguiente error: <class 'comments.admin.CommentAdmin'>: (admin.E040) PostAdmin must define "search_fields", because it's referenced by CommentAdmin.autocomplete_fields.
Si es así, lo que debemos hacer para solucionarlo es habilitar campos de búsqueda para los «posts»:
El formulario para añadir un comentario quedaría de la siguiente manera:

Relaciones muchos a muchos¶
Django avanzado
Cuando disponemos de campos ManyToMany en nuestros modelos, Django presenta un control de selección múltiple que, en muchas ocasiones, es suficiente para manipular los datos.
Pero podemos mejorarlo muy fácilmente. Partiendo del ejemplo en el que un «post» puede tener varias etiquetas, haríamos lo siguiente:
from django.contrib import admin
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
filter_horizontal = ('labels',)
Esta simple línea añade a la interfaz administrativa dos paneles (horizontales) con las etiquetas disponibles y las etiquetas elegidas a la hora de editar/crear un nuevo «post»:

Relaciones muchos a muchos con modelo intermedio¶
Para poder visualizar (y gestionar) de mejor manera las relaciones muchos a muchos con modelo intermedio dentro de la interfaz administrativa, Django proporciona unos artefactos denominados «inlines».
La idea detrás de esto es poder «presentar» en la misma página del objeto aquellos otros objetos relacionados con el primero que conformen esta relación muchos a muchos.
Para ilustrar el modo de uso con un ejemplo, vamos a retomar este escenario en el que un «post» puede tener varias etiquetas y se añade el detalle del etiquetado (al asignar etiquetas a «posts»):
from django.contrib import admin
from .models import Post, PostLabelingDetail
class PostLabelingDetailInline(admin.TabularInline):#(1)!
model = PostLabelingDetail#(2)!
extra = 1#(3)!
@admin.register(Post)#(4)!
class PostAdmin(admin.ModelAdmin):
inlines = [PostLabelingDetailInline]#(5)!
-
- El modelo
PostLabelingDetail(modelo intermedio) se registra medianteadmin.TabularInline. -
También se puede usar aquí la clase
admin.StackedInlineque cambia la disposición de los elementos.

- El modelo
-
Es obligatorio especificar el modelo mediante el atributo de clase
model. - El atributo
extradefine el número adicional de entradas del modelo. - Registramos el modelo
Post. - El atributo
inlinesnos permite definir los «slots» de objetosPostLabelingDetailque aparecerán en la interfaz administrativa dePost.
Esta simple línea añade a la interfaz administrativa un panel con los detalles del etiquetado (tabulados) a la hora de editar/crear un nuevo «post»:

Obviamente también podemos hacer uso de raw_id_fields o autocomplete_fields (relaciones 1:N) en la clase PostLabelingDetailInline con el objetivo de mejorar la búsqueda de etiquetas.
Comandos de gestión¶
Django avanzado
Django ofrece la posibilidad de añadir comandos personalizados para ser ejecutados mediante ./manage.py <command>. Hay escenarios en los que esto es realmente útil.
Vamos a partir del ejemplo de un «blog» donde queremos implementar un comando de gestión personalizado para borrar todos los comentarios de uno o varios «posts».
Para ello definimos el comando de la siguiente manera:
from django.core.management.base import BaseCommand, CommandError#(1)!
from .models import Post#(2)!
class Command(BaseCommand):#(3)!
help = 'Delete all comments for the given post.'#(4)!
def add_arguments(self, parser):#(5)!
parser.add_argument('post_pks', nargs='+', type=int)#(6)!
def handle(self, *args, **options):#(7)!
for post_pk in options['post_pks']:#(8)!
try:
post = Post.objects.get(pk=post_pk)#(9)!
except Post.DoesNotExist:
raise CommandError(f'Post #{post.pk} does not exist')#(10)!
post.comments.delete()#(11)!
self.stdout.write(#(12)!
self.style.SUCCESS(f'Successfully deleted all comments for post #{post.pk}')
)
- Importamos las clases necesarias para definir el comando.
- Importamos el modelo sobre el que vamos a trabajar.
- Definimos la clase que contendrá el comando y que hereda de
BaseCommand. - El atributo
helppermite definir el texto de ayuda explicativo al comando que saldrá en terminal. -
- En este caso el comando requiere de ciertos argumentos.
- Este método se debe llamar exactamente así
add_arguments(). - El parámetro
parseres un objeto de tipoargparse.ArgumentParser.
-
- El método
add_argument()es muy versátil. - Añadimos el argumento
post_pksque representa a las claves primarias de los «posts» que vamos a tratar.
- El método
-
- El método
handle()contiene el código a ejecutar por comando. - Debe tener esta signatura.
- El método
-
- Los argumentos que se reciben por línea de comandos se almacenan en el diccionario
self.options. - Recorremos las claves primarias de los «posts» indicados.
- Los argumentos que se reciben por línea de comandos se almacenan en el diccionario
- Buscamos cada «post» en la base de datos.
-
- Si no se encuentra el «post» indicado lanzamos un error.
- Django proporciona una clase
CommandErrorpara estas situaciones.
- Borramos todos los comentarios del «post» en cuestión.
-
- Mostramos por pantalla un mensaje de confirmación.
- Es posible usar
self.stdoutpara enviar a la salida estándar yself.stderrpara enviar al error estándar. - En ambos casos se pueden utilizar estilos que permiten colorear sintaxis en terminal.
Ubicación del comando
Es obligatorio que el comando se ubique dentro de la carpeta management/commands de la aplicación correspondiente. El nombre del fichero .py será el nombre del comando.
Para ejecutar el comando creado basta con ir a la terminal y escribir lo siguiente:
Con esto estaríamos borrando todos los comentarios de los «posts» con clave primaria #45, #23 y #17.