Escribiendo tu primera aplicación Django, parte 2

Este tutorial comienza donde Tutorial 1 dejó. Estableceremos la base de datos, crearemos tu primer modelo y obtendremos una introducción rápida al sitio administrativo generado automáticamente por Django.

¿Dónde obtener ayuda:

Si tienes problemas al seguir este tutorial, por favor dirígete a la sección Ayuda del FAQ.

Configuración de la base de datos

Ahora, abre el archivo mysite/settings.py. Es un módulo de Python normal con variables de nivel de módulo que representan configuraciones de Django.

Por defecto, la configuración de bases de datos DATABASES utiliza SQLite. Si eres nuevo en bases de datos o simplemente estás interesado en probar Django, esta es la elección más fácil. SQLite está incluido en Python, por lo que no necesitarás instalar nada más para apoyar tu base de datos. Al iniciar tu primer proyecto real, sin embargo, podrías querer utilizar una base de datos más escalable como PostgreSQL, para evitar dolores de cabeza con la selección de bases de datos en el futuro.

Si deseas utilizar otra base de datos, consulta detalles para personalizar y hacer funcionar tu base de datos.

Mientras editas mysite/settings.py, establece TIME_ZONE en tu zona horaria.

También ten en cuenta la configuración INSTALLED_APPS en la parte superior del archivo. Esa contiene los nombres de todas las aplicaciones Django que están activadas en esta instancia de Django. Las aplicaciones pueden usarse en múltiples proyectos y puedes empaquetarlas y distribuirlas para su uso por otros en sus proyectos.

Por defecto, INSTALLED_APPS contiene las siguientes aplicaciones, todas ellas que vienen con Django:

Estas aplicaciones están incluidas por defecto como una comodidad para el caso más común.

Algunas de estas aplicaciones utilizan al menos una tabla de la base de datos, por lo que necesitamos crear las tablas en la base de datos antes de poder utilizarlas. Para hacer eso, ejecuta el siguiente comando:

$ python manage.py migrate

El comando migrar analiza el parámetro INSTALLED_APPS y crea cualquier tabla de la base de datos necesaria según los ajustes de la base de datos en tu archivo mysite/configuración.py y las migraciones de la base de datos incluidas con la aplicación (más adelante cubriremos eso). Verás un mensaje para cada migración que aplique. Si estás interesado, ejecuta el cliente de línea de comandos de tu base de datos y escribe \dt (PostgreSQL), SHOW TABLAS; (MariaDB, MySQL), .tablas (SQLite) o SELECT NOMBRE_DE_TABLA FROM USER_TABLES; (Oracle) para mostrar las tablas que Django creó.

Para los minimalistas

Como mencionamos arriba, las aplicaciones por defecto están incluidas para el caso más común, pero no todos necesitan ellas. Si no necesitas ninguna o todas ellas, siente libre a comentar o eliminar la línea(s) correspondientes de INSTALLED_APPS antes de ejecutar migrar. El comando migrar solo aplicará migraciones para las apps en INSTALLED_APPS.

Creación de modelos

Ahora definiremos tus modelos – esencialmente, tu diseño de la base de datos, con metadatos adicionales.

Filosofía

Los textos traducidos manteniendo todas las etiquetas intactas son:

Esto incluye las migraciones - a diferencia de Ruby On Rails, por ejemplo, las migraciones están completamente derivadas del archivo de modelos y son básicamente una historia que Django puede recorrer para actualizar el esquema de base de datos para que coincida con tus modelos actuales.

En nuestra aplicación de encuesta, crearemos dos modelos: Pregunta y Opción. Una Pregunta tiene una pregunta y una fecha de publicación. Una Opción tiene dos campos: el texto de la opción y un recuento de votos. Cada Opción está asociada con una Pregunta.

Estos conceptos se representan mediante clases de Python. Edita el archivo polls/models.py para que tenga este aspecto:

polls/models.py
from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField("date published")


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

Aquí, cada modelo está representado por una clase que hereda de django.db.models.Model. Cada modelo tiene un número de variables de clase, cada una de las cuales representa un campo de base de datos en el modelo.

Cada campo se representa mediante una instancia de la clase Field – por ejemplo, CharField para campos de caracteres y DateTimeField para fechas y tiempos. Esto le dice a Django qué tipo de datos cada campo almacena.

El nombre de cada instancia de la clase Field (por ejemplo, question_text o pub_date) es el nombre del campo, en formato amigable para máquinas. Lo utilizarás en tu código Python y tu base de datos lo utilizará como nombre de columna.

Puedes usar un argumento posicional opcional al principio de una clase Field para designar un nombre legible por humanos. Eso se utiliza en algunas partes introspectivas de Django y sirve también como documentación. Si no se proporciona este campo, Django utilizará el nombre amigable para máquinas. En este ejemplo, hemos definido solo un nombre legible por humanos para Pregunta.pub_date. Para todos los demás campos en este modelo, el nombre amigable para máquinas será suficiente como su nombre legible.

Algunas clases Field tienen argumentos requeridos. Por ejemplo, CharField, requiere que le proporciones un max_length. Eso se utiliza no solo en el esquema de base de datos, sino también en la validación, como veremos pronto.

A Field también puede tener varios argumentos opcionales; en este caso, hemos establecido el valor por defecto (default) de votes a 0.

Finalmente, se define una relación utilizando ForeignKey. Esto le dice a Django que cada Choice está relacionado con un solo Question. Django admite todas las relaciones comunes de bases de datos: muchos-a-uno, muchos-a-muchos y uno-a-uno.

Activar modelos

Ese pequeño trozo de código del modelo proporciona a Django mucha información. Con él, Django puede:

  • Crear un esquema de base de datos (sentencias CREATE TABLE) para esta aplicación.

  • Crear una API de acceso a la base de datos en Python para acceder a objetos Question y Choice.

Pero antes debemos decirle a nuestro proyecto que la aplicación polls está instalada.

Filosofía

Las aplicaciones Django son «pluggables»: Puedes utilizar una aplicación en múltiples proyectos, y puedes distribuirlas porque no tienen que estar atadas a una instalación de Django específica.

Para incluir la aplicación en nuestro proyecto, debemos agregar una referencia a su clase de configuración en el parámetro INSTALLED_APPS del archivo de configuración. La clase PollsConfig está en el archivo polls/apps.py, por lo que su ruta punteada es 'polls.apps.PollsConfig'. Edita el archivo mysite/settings.py y agrega esa ruta punteada al parámetro INSTALLED_APPS. Se verá así:

mysite/settings.py`
INSTALLED_APPS = [
    "polls.apps.PollsConfig",
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
]

Ahora Django sabe incluir la aplicación polls. Vamos a ejecutar otro comando:

$ python manage.py makemigrations polls

Deberías ver algo similar al siguiente:

Migrations for 'polls':
  polls/migrations/0001_initial.py
    + Create model Question
    + Create model Choice

Al ejecutar makemigrations, estás diciendo a Django que has realizado algunas modificaciones en tus modelos (en este caso, has creado nuevos) y que te gustaría almacenar las modificaciones como una migración.

Las migraciones son cómo Django almacena cambios en tus modelos (y por lo tanto tu esquema de base de datos) - son archivos en disco. Puedes leer la migración para tu nuevo modelo si lo deseas; es el archivo polls/migrations/0001_initial.py. No te preocupes, no se espera que leas cada una de ellas cada vez que Django las genere, pero están diseñadas para ser editables por humanos en caso de que desees manualmente ajustar cómo Django cambia cosas.

Hay un comando que ejecutará las migraciones por ti y gestionará tu esquema de base de datos automáticamente - se llama migrate, y lo veremos más adelante - pero antes, vamos a ver qué SQL ejecutaría esa migración. El comando sqlmigrate toma nombres de migración y devuelve su SQL:

$ python manage.py sqlmigrate polls 0001

Deberías ver algo similar al siguiente (lo hemos reformateado para mejorar la legibilidad):

BEGIN;
--
-- Create model Question
--
CREATE TABLE "polls_question" (
    "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
    "question_text" varchar(200) NOT NULL,
    "pub_date" timestamp with time zone NOT NULL
);
--
-- Create model Choice
--
CREATE TABLE "polls_choice" (
    "id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
    "choice_text" varchar(200) NOT NULL,
    "votes" integer NOT NULL,
    "question_id" bigint NOT NULL
);
ALTER TABLE "polls_choice"
  ADD CONSTRAINT "polls_choice_question_id_c5b4b260_fk_polls_question_id"
    FOREIGN KEY ("question_id")
    REFERENCES "polls_question" ("id")
    DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");

COMMIT;

Ten en cuenta lo siguiente:

  • El resultado exacto variará dependiendo del motor de base de datos que estés utilizando. El ejemplo anterior se generó para PostgreSQL.

  • Los nombres de las tablas se generan automáticamente combinando el nombre de la aplicación (polls) y el nombre en minúsculas del modelo – question y choice. (Puedes sobrescribir este comportamiento.)

  • Las claves primarias (IDs) se agregan automáticamente. (También puedes sobrescribirlas.)

  • Por convención, Django agrega "_id" al nombre del campo de clave extranjera. (Sí, puedes sobrescribir esto también.)

  • La relación de clave extranjera se hace explícita mediante la restricción FOREIGN KEY. No te preocupes por los partes DEFERRABLE; está diciendo a PostgreSQL que no aplique la clave extranjera hasta el final de la transacción.

  • Está diseñado para la base de datos que estás utilizando, así que tipos de campo específicos de la base de datos como auto_increment (MySQL), bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (PostgreSQL) o integer primary key autoincrement (SQLite) se manejan automáticamente. Lo mismo ocurre con el citado de nombres de campos – por ejemplo, utilizando comillas dobles o simples.

  • El comando sqlmigrate no ejecuta realmente la migración en tu base de datos - en su lugar, lo imprime en la pantalla para que puedas ver qué SQL cree Django como requerido. Es útil para verificar qué hace Django o si tienes administradores de bases de datos que requieren scripts SQL para cambios.

Si estás interesado, también puedes ejecutar python manage.py check; esto verifica cualquier problema en tu proyecto sin aplicar migraciones ni tocar la base de datos.

Ahora, ejecuta nuevamente migrate para crear esas tablas de modelo en tu base de datos:

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Rendering model states... DONE
  Applying polls.0001_initial... OK

El comando migrate aplica todas las migraciones que no se han aplicado (Django sigue cuáles están aplicadas utilizando una tabla especial en tu base de datos llamada django_migrations) y las ejecuta contra tu base de datos - esencialmente, sincroniza los cambios que hiciste a tus modelos con el esquema en la base de datos.

Las migraciones son muy poderosas y te permiten cambiar tus modelos con el tiempo, mientras desarrollas tu proyecto, sin necesidad de eliminar tu base de datos o tablas y hacer nuevas - se especializa en actualizar tu base de datos en vivo, sin perder datos. Los cubriremos en más profundidad en una parte posterior del tutorial, pero por ahora recuerda la guía de tres pasos para hacer cambios en modelos:

La razón por la que hay comandos separados para crear y aplicar migraciones es porque vas a comitar las migraciones en tu sistema de control de versiones y enviarlas con tu aplicación; no solo facilitan el desarrollo, sino que también son utilizables por otros desarrolladores y en producción.

Lee la documentación django-admin para obtener información completa sobre lo que puede hacer la utilidad manage.py.

Jugar con la API

Ahora, vamos a entrar en el entorno de shell interactivo de Python y jugar un poco con la API gratuita que te da Django. Para invocar el entorno de shell, utiliza este comando:

$ python manage.py shell

Estamos utilizando esto en lugar de simplemente escribir python, porque manage.py establece la variable de entorno DJANGO_SETTINGS_MODULE, lo cual le da a Django la ruta de importación Python para tu archivo mysite/settings.py. Por defecto, el comando shell importa automáticamente los modelos desde tus INSTALLED_APPS.

Una vez que estés en el entorno de shell, explora la API de base de datos :doc:` </topics/db/queries>`:

# No questions are in the system yet.
>>> Question.objects.all()
<QuerySet []>

# Create a new Question.
# Support for time zones is enabled in the default settings file, so
# Django expects a datetime with tzinfo for pub_date. Use timezone.now()
# instead of datetime.datetime.now() and it will do the right thing.
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

# Save the object into the database. You have to call save() explicitly.
>>> q.save()

# Now it has an ID.
>>> q.id
1

# Access model field values via Python attributes.
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=datetime.timezone.utc)

# Change values by changing the attributes, then calling save().
>>> q.question_text = "What's up?"
>>> q.save()

# objects.all() displays all the questions in the database.
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>

Espera un momento. <Question: Question object (1)> no es una representación útil de este objeto. Vamos a arreglar eso editando el modelo Question (en el archivo polls/models.py) y agregando un método __str__() a ambos Question y Choice:

polls/models.py
from django.db import models


class Question(models.Model):
    # ...
    def __str__(self):
        return self.question_text


class Choice(models.Model):
    # ...
    def __str__(self):
        return self.choice_text

Es importante agregar métodos __str__() a tus modelos, no solo para tu conveniencia cuando estés trabajando con la consola interactiva, sino también porque las representaciones de los objetos se utilizan en toda la administración generada automáticamente por Django.

Vamos a agregar un método personalizado a este modelo:

polls/models.py
import datetime

from django.db import models
from django.utils import timezone


class Question(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

Nota la adición de import datetime y from django.utils import timezone, para referenciar el módulo estándar de Python datetime y las utilidades relacionadas con la zona horaria de Django en django.utils.timezone, respectivamente. Si no estás familiarizado con el manejo de zonas horarias en Python, puedes aprender más en los documentos de soporte para la zona horaria: zonas horarias.

Guarda estos cambios y arranca una nueva consola interactiva de Python. (Si el prompt con tres caras (>>>>) indica que todavía estás en la consola, necesitas salir primero usando exit()). Vuelve a ejecutar python manage.py shell para recargar los modelos.

# Make sure our __str__() addition worked.
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>

# Django provides a rich database lookup API that's entirely driven by
# keyword arguments.
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith="What")
<QuerySet [<Question: What's up?>]>

# Get the question that was published this year.
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>

# Request an ID that doesn't exist, this will raise an exception.
>>> Question.objects.get(id=2)
Traceback (most recent call last):
    ...
DoesNotExist: Question matching query does not exist.

# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to Question.objects.get(id=1).
>>> Question.objects.get(pk=1)
<Question: What's up?>

# Make sure our custom method worked.
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True

# Give the Question a couple of Choices. The create call constructs a new
# Choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set (defined as "choice_set") to hold the "other side" of a ForeignKey
# relation (e.g. a question's choice) which can be accessed via the API.
>>> q = Question.objects.get(pk=1)

# Display any choices from the related object set -- none so far.
>>> q.choice_set.all()
<QuerySet []>

# Create three choices.
>>> q.choice_set.create(choice_text="Not much", votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text="The sky", votes=0)
<Choice: The sky>
>>> c = q.choice_set.create(choice_text="Just hacking again", votes=0)

# Choice objects have API access to their related Question objects.
>>> c.question
<Question: What's up?>

# And vice versa: Question objects get access to Choice objects.
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3

# The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships.
# This works as many levels deep as you want; there's no limit.
# Find all Choices for any question whose pub_date is in this year
# (reusing the 'current_year' variable we created above).
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

# Let's delete one of the choices. Use delete() for that.
>>> c = q.choice_set.filter(choice_text__startswith="Just hacking")
>>> c.delete()

Para obtener más información sobre las relaciones de modelos, consulte Acceder a objetos relacionados. Para más detalles sobre cómo utilizar dobles guiones para realizar consultas de campos mediante la API, consulte Consultas de campo. Para obtener información completa sobre la API de base de datos, consulte nuestra Referencia de la API de base de datos.

Introduciendo la interfaz administrativa de Django

Filosofía

La creación de sitios administrativos para que tus empleados o clientes puedan agregar, cambiar y eliminar contenido es un trabajo tedioso que no requiere mucha creatividad. Por esa razón, Django automatiza completamente la creación de interfaces administrativas para modelos.

Django se escribió en un entorno de redacción de noticias, con una separación muy clara entre «editores de contenido» y el sitio público. Los administradores del sitio utilizan el sistema para agregar historias de noticias, eventos, resultados deportivos, etc., y ese contenido se muestra en el sitio público. Django resuelve el problema de crear una interfaz unificada para que los administradores del sitio editen el contenido.

El administrador no está destinado a ser utilizado por visitantes del sitio. Está pensado para los responsables de la gestión del sitio.

Crear un usuario administrador

Primero debemos crear un usuario que pueda acceder al sitio de administración. Ejecuta el siguiente comando:

$ python manage.py createsuperuser

Introduce tu nombre de usuario deseado y presiona Enter.

Username: admin

You will then be prompted for your desired email address:

Email address: admin@example.com

La última etapa es introducir tu contraseña. Te pedirán que la introduzcas dos veces, la segunda vez como confirmación de la primera.

Password: **********
Password (again): *********
Superuser created successfully.

Arranca el servidor de desarrollo

El sitio administrativo de Django está activado por defecto. Vamos a arrancar el servidor de desarrollo y explorarlo.

Si el servidor no está en marcha, arráncalo como se indica a continuación:

$ python manage.py runserver

Ahora, abre un navegador web y accede a «/admin/» en tu dominio local – por ejemplo, http://127.0.0.1:8000/admin/. Deberías ver la pantalla de inicio del administrador:

Pantalla de inicio del administrador Django

Dado que traducción está activada por defecto, si estableces LANGUAGE_CODE, la pantalla de inicio se mostrará en el idioma dado (si Django tiene traducciones adecuadas).

Accede al sitio administrativo

Ahora, intenta iniciar sesión con la cuenta de superusuario que creaste en el paso anterior. Deberías ver la página de inicio del administrador de Django:

Django admin index page

Deberías ver algunos tipos de contenido editable: grupos y usuarios. Están proporcionados por django.contrib.auth, el marco de autenticación embarcado en Django.

Haz que la aplicación de encuestas sea modificable en el admin

Pero ¿dónde está nuestra aplicación de encuestas? No se muestra en la página de inicio del admin.

Solo queda una cosa más por hacer: necesitamos decirle al admin que los objetos Question tienen una interfaz administrativa. Para ello, abre el archivo polls/admin.py y edita para que tenga este aspecto:

polls/admin.py
from django.contrib import admin

from .models import Question

admin.site.register(Question)

Explora la funcionalidad gratuita del admin

Ahora que hemos registrado Question, Django sabe que debería mostrarse en la página de inicio del admin:

Página de inicio del admin de Django, ahora con encuestas mostradas

Haz clic en «Encuestas». Ahora estás en la página de lista de cambio para las encuestas. Esta página muestra todas las encuestas en la base de datos y te permite elegir una para cambiarla. Allí está la pregunta «¿Qué pasa?» que creamos anteriormente:

La página de lista de encuestas cambia

Haz clic en la pregunta «¿Qué hay de nuevo?» para editarla:

Formulario de edición para el objeto pregunta

Aquí tienes algunas cosas que debes tener en cuenta:

  • El formulario se genera automáticamente a partir del modelo Question.

  • Los diferentes tipos de campos del modelo (DateTimeField, CharField) corresponden al widget de entrada HTML adecuado. Cada tipo de campo sabe cómo mostrarse en la administración Django.

  • Cada DateTimeField obtiene atajos JavaScript gratuitos. Las fechas tienen un atajo «Hoy» y una ventana emergente del calendario, y las horas tienen un atajo «Ahora» y una ventana emergente conveniente que enumera los tiempos comúnmente ingresados.

La parte inferior de la página te da algunas opciones:

  • Guardar – Guarda los cambios y regresa a la página de lista de cambio para este tipo de objeto.

  • Guardar y seguir editando – Guarda los cambios y recarga la página administrativa para este objeto.

  • Save y agregar otro – Guarda los cambios y carga una nueva forma en blanco para este tipo de objeto.

  • Eliminar – Muestra una página de confirmación de eliminación.

Si el valor de «Fecha publicada» no coincide con la hora en que creaste la pregunta en Tutorial 1, probablemente hayas olvidado establecer el valor correcto para la configuración TIME_ZONE. Cambiala, recarga la página y verifica que aparezca el valor correcto.

Cambia la «Fecha publicada» haciendo clic en los atajos «Hoy» y «Ahora». Luego haz clic en «Guardar y seguir editando.» Luego haz clic en «Historial» en la parte superior derecha. Verás una página que enumera todos los cambios realizados a este objeto a través del panel de administración de Django, con el timestamp y el nombre de usuario de la persona que hizo el cambio:

Página de historial para objeto pregunta

Cuando te sientas cómodo con la API de modelos y hayas familiarizado al sitio de administración, lee parte 3 de este tutorial para aprender a agregar más vistas a nuestra aplicación de encuestas.