Aspectos destacados de Django: modelos, administración y aprovechamiento de la base de datos relacional (parte 3)

Publicado: 2022-03-10
Resumen rápido ↬ El panel de administración es una de las características más potentes y flexibles que ofrece el marco web de Django, que combina una funcionalidad instantánea lista para usar con una personalización infinita. Usando un proyecto de ejemplo basado en un sistema de inventario de biblioteca, usaremos el panel de administración para aprender a crear modelos e interactuar con bases de datos relacionales en Django.

Antes de comenzar, quiero señalar que las capacidades administrativas integradas de Django, incluso después de la personalización, no están destinadas a los usuarios finales. El panel de administración existe como una herramienta de desarrollador, operador y administrador para crear y mantener software. No está destinado a ser utilizado para brindar a los usuarios finales capacidades de moderación o cualquier otra capacidad de administrador sobre la plataforma que desarrolle.

Este artículo se basa en una hipótesis en dos partes:

  1. El panel de administración de Django es tan intuitivo que básicamente ya sabes cómo usarlo.
  2. El panel de administración de Django es tan poderoso que podemos usarlo como una herramienta para aprender a representar datos en una base de datos relacional usando un modelo de Django.

Ofrezco estas ideas con la advertencia de que aún necesitaremos escribir algún código de configuración para activar las capacidades más poderosas del panel de administración, y aún necesitaremos usar el ORM (mapeo relacional de objetos) basado en modelos de Django para especificar la representación de datos. en nuestro sistema.

Lectura recomendada

"Django Highlights" es una serie que presenta conceptos importantes del desarrollo web en Django. Es posible que desee leer sobre cómo proporcionar flujos de autenticación de usuario seguros y seguir junto con una demostración sobre el uso de plantillas de Django para escribir páginas complejas.

¡Más después del salto! Continúe leyendo a continuación ↓

Configuración

Vamos a trabajar con un proyecto de muestra en este artículo. El proyecto modela algunos datos que una biblioteca almacenaría sobre sus libros y usuarios. El ejemplo debería ser bastante aplicable a muchos tipos de sistemas que administran usuarios y/o inventario. Aquí hay un adelanto de cómo se ven los datos:

Modelo de datos. (Vista previa grande)

Complete los siguientes pasos para ejecutar el código de ejemplo en su máquina local.

1. Instalación de paquetes

Con Python 3.6 o superior instalado, cree un directorio y un entorno virtual. Luego, instale los siguientes paquetes:

 pip install django django-grappelli

Django es el framework web con el que estamos trabajando en este artículo. ( django-grappelli es un tema del panel de administración que cubriremos brevemente).

2. Obtener el proyecto

Con los paquetes anteriores instalados, descargue el código de ejemplo de GitHub. Correr:

 git clone https://github.com/philipkiely/library_records.git cd library_records/library

3. Creando un Superusuario

Con los siguientes comandos, configure su base de datos y cree un superusuario. La interfaz de línea de comandos lo guiará a través del proceso de creación de un superusuario. Su cuenta de superusuario será la forma en que accederá al panel de administración en un momento, así que asegúrese de recordar la contraseña que estableció. Utilizar:

 python manage.py migrate python manage.py createsuperuser

4. Cargando los datos

Para nuestra exploración, creé un conjunto de datos llamado accesorio que puede cargar en la base de datos (más información sobre cómo crear un accesorio al final del artículo). Use el accesorio para completar su base de datos antes de explorarlo en el panel de administración. Correr:

 python manage.py loaddata ../fixture.json

5. Ejecutar el proyecto de ejemplo

Finalmente, está listo para ejecutar el código de ejemplo. Para ejecutar el servidor, use el siguiente comando:

 python manage.py runserver

Abra su navegador en https://127.0.0.1:8000 para ver el proyecto. Tenga en cuenta que se le redirigirá automáticamente al panel de administración en /admin/ . Logré eso con la siguiente configuración en library/urls.py :

 from django.contrib import admin from django.urls import path from records import views urlpatterns = [ path('admin/', admin.site.urls), path('', views.index), ]

combinado con la siguiente redirección simple en records/views.py :

 from django.http import HttpResponseRedirect def index(request): return HttpResponseRedirect('/admin/')

Uso del panel de administración

¡Lo hemos logrado! Cuando cargues tu página, deberías ver algo como lo siguiente:

Página principal del panel de administración de Django. (Vista previa grande)

Esta vista se logra con el siguiente código repetitivo en records/admin.py :

 from django.contrib import admin from .models import Book, Patron, Copy admin.site.register(Book) admin.site.register(Copy) admin.site.register(Patron)

Esta vista debería brindarle una comprensión inicial de los datos que almacena el sistema. Quitaré parte del misterio: los Groups y los Users están definidos por Django y almacenan información y permisos para cuentas en el sistema. Puede leer más sobre el modelo de User en un artículo anterior de esta serie. Books , Copys y Patrons son tablas en la base de datos que creamos cuando ejecutamos migraciones y completamos al cargar el accesorio. Tenga en cuenta que Django pluraliza ingenuamente los nombres de los modelos agregando una "s", incluso en casos como "copias" donde la ortografía es incorrecta.

Modelo de datos. (Vista previa grande)

En nuestro proyecto, un Book es un registro con título, autor, fecha de publicación e ISBN (International Standard Book Number). La biblioteca mantiene una Copy de cada Book , o posiblemente varias. Cada Copy puede ser prestada por un Patron , o podría ser prestada actualmente. Un Patron es una extensión del User que registra su dirección y fecha de nacimiento.

Crear, Leer, Actualizar, Destruir

Una capacidad estándar del panel de administración es agregar instancias de cada modelo. Haga clic en "libros" para acceder a la página del modelo y haga clic en el botón "Agregar libro" en la esquina superior derecha. Al hacerlo, aparecerá un formulario, que puede completar y guardar para crear un libro.

Crear un libro (vista previa grande)

La creación de un Patron revela otra capacidad integrada del formulario de creación del administrador: puede crear el modelo conectado directamente desde el mismo formulario. La siguiente captura de pantalla muestra la ventana emergente que se activa con el signo más verde a la derecha del menú desplegable User . Por lo tanto, puede crear ambos modelos en la misma página de administración.

Crear un patrón. (Vista previa grande)

Puede crear una COPY a través del mismo mecanismo.

Para cada registro, puede hacer clic en la fila para editarlo usando el mismo formulario. También puede eliminar registros mediante una acción de administrador.

Acciones de administración

Si bien las capacidades integradas del panel de administración son muy útiles, puede crear sus propias herramientas mediante acciones de administración. Crearemos dos: uno para crear copias de libros y otro para registrar libros que han sido devueltos a la biblioteca.

Para crear una Copy de un Book , vaya a la URL /admin/records/book/ y use el menú desplegable "Acción" para seleccionar "Agregar una copia de los libros" y luego use las casillas de verificación en la columna de la izquierda de la tabla para seleccionar qué libro o libros agregar una copia al inventario.

Crear acción de copia. (Vista previa grande)

La creación de esto se basa en un método modelo que veremos más adelante. Podemos llamarlo como una acción de administración creando una clase ModelAdmin para el modelo de Profile de la siguiente manera en records/admin.py :

 from django.contrib import admin from .models import Book, Patron, Copy class BookAdmin(admin.ModelAdmin): list_display = ("title", "author", "published") actions = ["make_copys"] def make_copys(self, request, queryset): for q in queryset: q.make_copy() self.message_user(request, "copy(s) created") make_copys.short_description = "Add a copy of book(s)" admin.site.register(Book, BookAdmin)

La propiedad list_display indica qué campos se utilizan para representar el modelo en la página de descripción general del modelo. La propiedad de actions enumera las acciones de administración. Nuestra acción de administración se define como una función dentro de BookAdmin y toma tres argumentos: el objeto de administración en sí, la solicitud (la solicitud HTTP real enviada por el cliente) y el conjunto de consultas (la lista de objetos cuyas casillas fueron marcadas). Realizamos la misma acción en cada elemento del conjunto de consultas y luego notificamos al usuario que las acciones se han completado. Cada acción de administración requiere una breve descripción para que pueda identificarse correctamente en el menú desplegable. Finalmente, ahora agregamos BookAdmin al registrar el modelo.

Escribir acciones de administrador para configurar propiedades de forma masiva es bastante repetitivo. Aquí está el código para registrar una Copy , tenga en cuenta su equivalencia cercana a la acción anterior.

 from django.contrib import admin from .models import Book, Patron, Copy class CopyAdmin(admin.ModelAdmin): actions = ["check_in_copys"] def check_in_copys(self, request, queryset): for q in queryset: q.check_in() self.message_user(request, "copy(s) checked in") check_in_copys.short_description = "Check in copy(s)" admin.site.register(Copy, CopyAdmin)

Tema de administración

De forma predeterminada, Django proporciona estilos bastante simples para el panel de administración. Puede crear su propio tema o usar un tema de terceros para darle una nueva apariencia al panel de administración. Un tema popular de código abierto es grappelli, que instalamos anteriormente en este artículo. Puede consultar la documentación para conocer todas sus capacidades.

Instalar el tema es bastante sencillo, solo requiere dos líneas. Primero, agregue grappelli a INSTALLED_APPS de la siguiente manera en library/settings.py :

 INSTALLED_APPS = [ 'grappelli', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'records', ]

Luego, ajuste library/urls.py :

 from django.contrib import admin from django.urls import path, include from records import views urlpatterns = [ path('grappelli/', include('grappelli.urls')), path('admin/', admin.site.urls), path('', views.index), ]

Con esos cambios en su lugar, el panel de administración debería tener el siguiente aspecto:

Panel de administración con tema. (Vista previa grande)

Hay una serie de otros temas por ahí y, de nuevo, puede desarrollar el suyo propio. Me quedaré con el aspecto predeterminado para el resto de este artículo.

Comprender los modelos

Ahora que se siente cómodo con el panel de administración y lo usa para navegar por los datos, echemos un vistazo a los modelos que definen la estructura de nuestra base de datos. Cada modelo representa una tabla en una base de datos relacional.

Una base de datos relacional almacena datos en una o más tablas. Cada una de estas tablas tiene una estructura de columnas específica, que incluye una clave principal (un identificador único para cada elemento) y una o más columnas de valores, que son de varios tipos, como cadenas, números enteros y fechas. Cada objeto almacenado en la base de datos se representa como una sola fila. La parte "relacional" del nombre proviene de lo que podría decirse que es la característica más importante de la tecnología: crear relaciones entre tablas. Un objeto (fila) puede tener una asignación de uno a uno, de uno a muchos (clave externa) o de muchos a muchos a filas en otras tablas. Discutiremos esto más adelante en los ejemplos.

Django, por defecto, usa SQLite3 para el desarrollo. SQLite3 es un motor de base de datos relacional simple y su base de datos se crea automáticamente como db.sqlite3 la primera vez que ejecuta python manage.py migrate . Continuaremos con SQLite3 para este artículo, pero no es adecuado para uso en producción, principalmente porque es posible sobrescribir con usuarios simultáneos. En producción, o cuando escriba un sistema que algún día pretenda implementar, use PostgreSQL o MySQL.

Django usa modelos para interactuar con la base de datos. Usando parte del ORM de Django, el archivo records/models.py incluye múltiples modelos, lo que permite especificar campos, propiedades y métodos para cada objeto. Al crear modelos, nos esforzamos por una arquitectura de "Modelo gordo", dentro de lo razonable. Eso significa que la mayor parte posible de la validación de datos, el análisis, el procesamiento, la lógica comercial, el manejo de excepciones, la resolución de casos extremos y tareas similares deben manejarse en la especificación del modelo mismo. Bajo el capó, los modelos de Django son objetos muy complejos y llenos de características con un comportamiento predeterminado muy útil. Esto hace que la arquitectura del "Modelo pesado" sea fácil de lograr incluso sin escribir una cantidad sustancial de código.

Veamos los tres modelos en nuestra aplicación de muestra. No podemos cubrir todo, ya que se supone que este es un artículo introductorio, no la documentación completa del framework Django, pero resaltaré las elecciones más importantes que hice al construir estos modelos simples.

La clase Book es el más sencillo de los modelos. Aquí está de records/models.py :

 from django.db import models class Book(models.Model): title = models.CharField(max_length=300) author = models.CharField(max_length=150) published = models.DateField() isbn = models.IntegerField(unique=True) def __str__(self): return self.title + " by " + self.author def make_copy(self): Copy.objects.create(book=self)

Todos los campos CharField requieren un atributo max_length especificado. La longitud convencional es de 150 caracteres, que doblé por title en caso de títulos muy largos. Por supuesto, todavía hay un límite arbitrario, que podría excederse. Para una longitud de texto ilimitada, use un TextField . El campo published es un DateField . La hora en que se publicó el libro no importa, pero si lo hiciera, usaría un DateTimeField . Finalmente, el ISBN es un número entero (los ISBN tienen 10 o 13 dígitos y, por lo tanto, todos se ajustan al valor máximo del número entero) y usamos unique=True ya que dos libros no pueden tener el mismo ISBN, que luego se aplica a nivel de base de datos.

Todos los objetos tienen un método __str__(self) que define su representación de cadena. Anulamos la implementación predeterminada proporcionada por la clase models.Model y, en su lugar, representamos los libros como "título por autor" en todos los lugares donde el modelo se representaría como una cadena. Recuerde que anteriormente usamos list_display en el objeto de administración de Book para determinar qué campos se mostrarían en la lista del panel de administración. Si ese list_display no está presente, la lista de administración muestra la representación de cadena del modelo, como lo hace tanto para Patron como para Copy .

Finalmente, tenemos un método en Book que llamamos en su acción de administración que escribimos anteriormente. Esta función crea una Copy relacionada con una instancia dada de un Book en la base de datos.

Pasando a Patron , este modelo introduce el concepto de una relación uno a uno, en este caso con el modelo de User incorporado. Compruébalo en records/models.py :

 from django.db import models from django.contrib.auth.models import User class Patron(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) address = models.CharField(max_length=150) dob = models.DateField() def __str__(self): return self.user.username

El campo de user no es exactamente una función biyectiva. PUEDE haber una instancia User sin una instancia de Patron asociada. Sin embargo, un User NO PUEDE estar asociado con más de una Patron de Usuario, y un Patron no puede existir sin exactamente una relación con un usuario. Esto se aplica a nivel de la base de datos y está garantizado por la on_delete=models.CASCADE : si se elimina una instancia User , se eliminará un Profile asociado.

Los otros campos y __str__(self) que hemos visto antes. Vale la pena señalar que puede llegar a través de una relación uno a uno para obtener atributos, en este caso user.username , en las funciones de un modelo.

Para ampliar la utilidad de las relaciones de la base de datos, dirijamos nuestra atención a Copy de registros/modelos.py :

 from django.db import models class Copy(models.Model): book = models.ForeignKey(Book, on_delete=models.CASCADE) out_to = models.ForeignKey(Patron, blank=True, null=True, on_delete=models.SET_NULL) def __str__(self): has_copy = "checked in" if self.out_to: has_copy = self.out_to.user.username return self.book.title + " -> " + has_copy def check_out(self, p): self.out_to = p self.save() def check_in(self): self.out_to = None self.save()

Nuevamente, hemos visto la mayor parte de esto antes, así que concentrémonos en las cosas nuevas: models.ForeignKey . Una Copy debe ser de un solo Book , pero la biblioteca puede tener múltiples Copy de cada Book . Un Book puede existir en la base de datos sin que la biblioteca tenga una Copy en su catálogo, pero una Copy no puede existir sin un Book subyacente.

Esta compleja relación se expresa con la siguiente línea:

 book = models.ForeignKey(Book, on_delete=models.CASCADE)

El comportamiento de borrado es el mismo que el del Patron en referencia al User .

La relación entre una Copy y un Patron es ligeramente diferente. Una Copy se puede prestar hasta para un Patron , pero cada Patron puede prestar tantas Copy como la biblioteca le permita. Sin embargo, esta no es una relación permanente, la Copy a veces no se desprotege. Patron y las Copy existen de forma independiente en la base de datos; eliminar una instancia de uno no debería eliminar ninguna instancia del otro.

Esta relación sigue siendo un caso de uso para la clave externa, pero con diferentes argumentos:

 out_to = models.ForeignKey(Patron, blank=True, null=True, on_delete=models.SET_NULL)

Aquí, tener blank=True permite que los formularios acepten None como el valor de la relación y null=True permite que la columna para la relación del Patron en la tabla de Copy en la base de datos acepte null como valor. El comportamiento de eliminación, que se activaría en una Copy si se eliminara una Patron de usuario mientras tenían esa Copy desprotegida, es cortar la relación y dejar la Copy intacta configurando el campo Patron en nulo.

El mismo tipo de campo, models.ForeignKey , puede expresar relaciones muy diferentes entre objetos. La única relación que no pude encajar claramente en el ejemplo es un campo de muchos a muchos, que es como un campo de uno a uno, excepto que, como sugiere su nombre, cada instancia se puede relacionar con muchas otras instancias. y todos los demás y cada uno de ellos puede relacionarse con muchos otros, por ejemplo, cómo un libro puede tener varios autores, cada uno de los cuales ha escrito varios libros.

Migraciones

Quizás se pregunte cómo la base de datos sabe lo que se expresa en el modelo. En mi experiencia, las migraciones son una de esas cosas que son bastante sencillas hasta que dejan de serlo y luego te comen la cara. Aquí le mostramos cómo mantener su taza intacta, para principiantes: aprenda sobre las migraciones y cómo interactuar con ellas, pero trate de evitar realizar ediciones manuales en los archivos de migración. Si ya sabe lo que está haciendo, omita esta sección y continúe con lo que funciona para usted.

De cualquier manera, consulte la documentación oficial para un tratamiento completo del tema.

Las migraciones traducen los cambios en un modelo a cambios en el esquema de la base de datos. No tiene que escribirlos usted mismo, Django los crea con el comando python manage.py makemigrations . Debe ejecutar este comando cuando crea un nuevo modelo o edita los campos de un modelo existente, pero no es necesario hacerlo al crear o editar métodos de modelo. Es importante tener en cuenta que las migraciones existen como una cadena, cada una hace referencia a la anterior para que pueda realizar ediciones sin errores en el esquema de la base de datos. Por lo tanto, si está colaborando en un proyecto, es importante mantener un único historial de migración coherente en el control de versiones. Cuando haya migraciones no aplicadas, ejecute python manage.py migrate para aplicarlas antes de ejecutar el servidor.

El proyecto de ejemplo se distribuye con una sola migración, records/migrations/0001_initial.py . Nuevamente, este es un código generado automáticamente que no debería tener que editar, por lo que no lo copiaré aquí, pero si desea tener una idea de lo que sucede detrás de escena, continúe y échele un vistazo.

Accesorios

A diferencia de las migraciones, los accesorios no son un aspecto común del desarrollo de Django. Los uso para distribuir datos de muestra con artículos y nunca los he usado de otra manera. Sin embargo, debido a que usamos uno antes, me siento obligado a presentar el tema.

Por una vez, la documentación oficial es un poco escasa sobre el tema. En general, lo que debe saber es que los accesorios son una forma de importar y exportar datos de su base de datos en una variedad de formatos, incluido JSON, que es el que uso. Esta función existe principalmente para ayudar con cosas como las pruebas automatizadas, y no es un sistema de copia de seguridad ni una forma de editar datos en una base de datos activa. Además, los accesorios no se actualizan con las migraciones, y si intenta aplicar un accesorio a una base de datos con un esquema incompatible, fallará.

Para generar un accesorio para toda la base de datos, ejecute:

 python manage.py dumpdata --format json > fixture.json

Para cargar un dispositivo, ejecute:

 python manage.py loaddata fixture.json

Conclusión

Escribir modelos en Django es un gran tema, y ​​usar el panel de administración es otro. En 3000 palabras, solo he logrado presentar cada uno. Con suerte, el uso del panel de administración le ha brindado una mejor interfaz para explorar cómo funcionan los modelos y cómo se relacionan entre sí, brindándole la confianza para experimentar y desarrollar sus propias representaciones relacionales de datos.

Si está buscando un lugar fácil para comenzar, intente agregar un modelo de Librarian que herede del User como lo hace el Profile . Para un mayor desafío, intente implementar un historial de pago para cada Copy y/o Patron (hay varias formas de lograr esto).

Django Highlights es una serie que presenta conceptos importantes del desarrollo web en Django. Cada artículo está escrito como una guía independiente de una faceta del desarrollo de Django con la intención de ayudar a los desarrolladores y diseñadores front-end a alcanzar una comprensión más profunda de "la otra mitad" de la base de código. Estos artículos están construidos principalmente para ayudarlo a comprender la teoría y la convención, pero contienen algunos ejemplos de código que están escritos en Django 3.0.

Otras lecturas

Puede que le interesen los siguientes artículos y documentación.

  • Aspectos destacados de Django: Modelos de usuario y autenticación (Parte 1)
  • Aspectos destacados de Django: creación de plantillas de líneas de guardado (parte 2)
  • Aspectos destacados de Django: disputa de activos estáticos y archivos multimedia (Parte 4)
  • Documentación de administración de Django
  • Modelos Django
  • Referencia de campo del modelo de Django
  • Migraciones de Django