Escribiendo tu primera Django app, parte 1

Vamos a aprender mediante un ejemplo.

A lo largo de este tutorial vamos a recorrer la creación de una aplicación de encuestas básica.

Consistirá de dos partes:

  • Un sitio público que permite a la gente ver y votar encuestas.

  • Un sitio de administración que nos permite agregar, cambiar y borrar encuestas.

Vamos a asumir que tenés Django ya instalado. Podés chequear esto, así como también la versión, corriendo el siguiente comando:

$ python -c "import django; print(django.get_version())"

Si Django está instalado, deberías ver la versión de tu instalación. Si no, obtendrás un error diciendo “No module named django”.

Este tutorial está escrito para Django 1.8 y Python 3.2 o mayor. Si la versión de Django no coincide, te podés remitir a la versión del tutorial que corresponda, o actualizar Django a la versión más reciente. Si todavía usás Python 2.7, vas a necesitar ajustar los ejemplos ligeramente como se describe en los comentarios.s

Ver Cómo instalar Django para leer sobre cómo borrar versiones anteriores de Django e instalar una más reciente.

Dónde encontrar ayuda:

Si tenés problemas siguiendo este tutorial, por favor posteá un mensaje a lista de correo django-users o date una vuelta por #django en irc.freenode.net para chatear con otros usuarios de Django que quizás te puedan ayudar.

Creando un proyecto

Si esta es tu primera vez usando Django, tenés que hacer un setup inicial. En particular, necesitás auto-generar algo de código que define un Django project – una colección de settings para una instancia de Django, que incluye la configuración de la base de datos, opciones específicas de Django y settings específicos de las aplicaciones.

Desde la línea de comandos, cd al directorio donde quisieras guardar tu código, y corré el siguiente comando:

$ django-admin startproject mysite

Esto creará el directorio mysite en tu directorio actual. Si no funcionó, podés ver Problemas corriendo django-admin.py.

Nota

Hay que evitar nombrar los proyectos que coincidan con componentes built-in de Python o Django. En particular, significa que uno no debería usar nombres tales como django (en conflicto con Django mismo) o test (en conflicto con el paquete built-in test de Python).

Dónde debería estar este código?

Si tu background es en PHP (sin usar un framework moderno), probablemente estés acostumbrado a poner el código en la raíz del servidor web (un lugar como /var/www). Con Django no se hace así. No es una buena idea poner código Python en dicho lugar, porque existe el riesgo de que la gente pueda ver tu código en la web. Eso no es bueno en relación a la seguridad.

Uno pone el código en algún directorio fuera de la raíz del servidor web, como /home/mycode.

Veamos lo que creó startproject:

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py

Estos archivos son:

  • El directorio mysite/ de más afuera es sólo un contenedor para tu proyecto. El nombre no afecta a Django; lo podés renombrar libremente como quieras.

  • manage.py: Una utilidad de línea de comandos que te permite interactuar con este proyecto Django de varias maneras. Podés leer todos los detalles sobre manage.py en /ref/django-admin.

  • El directorio mysite/ interno es el paquete Python para tu proyecto. Su nombre es el nombre de paquete Python que necesitarás usar para importar cualquier cosa adentro del mismo (e.g. mysite.urls).

  • mysite/__init__.py: Un archivo vacío que le dice a Python que este directorio debe considerarse un paquete Python (si sos nuevo con Python, podés leer más sobre paquetes en la documentación oficial de Python).

  • mysite/settings.py: Settings/configuración de este proyecto Django. /topics/settings describe cómo funcionan estos settings.

  • mysite/urls.py: Declaración de las URL de este proyecto Django; una “tabla de contenidos” de tu sitio Django. Podés leer más sobre URLs en /topics/http/urls.

  • mysite/wsgi.py: Punto de entrada para servir tu proyecto mediante servidores web compatibles con WSGI. Podés ver /howto/deployment/wsgi/index para más detalles.

Configurar la base de datos

Ahora editemos mysite/settings.py. Es un módulo Python normal, que define variables a nivel módulo que representan los settings de Django.

Por defecto, la configuración usa SQLite. Si sos nuevo en lo que a base de datos se refiere, o solamente te interesa probar Django, está es la opción más simple. SQLite está incluido en Python, entonces no es necesario instalar nada extra. Sin embargo, cuando empieces un proyecto más serio quizás quieras considerar una base de datos más robusta como PostgreSQL, para evitar dolores de cabeza cambiando el motor de base de datos durante el camino.

Si querés usar otro motor de base de datos, instalá los bindings apropiados y cambiá las siguientes claves en DATABASES 'default' para que coincidan con la configuración de tu conexión a la base de datos:

  • ENGINE – Puede ser 'django.db.backends.postgresql_psycopg2', 'django.db.backends.mysql', 'django.db.backends.sqlite3' o 'django.db.backends.oracle'. También hay :setting:`otros backends disponibles <https://docs.djangoproject.com/en/1.8/ref/databases/#third-party-notes>`_.

  • NAME – El nombre de la base de datos. Si estás usando SQLite, tu base de datos será un archivo en tu computadora; en ese caso, NAME debería ser un path absoluto, incluyendo el nombre del archivo de base de datos. Si no existiera, se creará automáticamente cuando se sincronice la base de datos por primera vez.

Si no estás usando SQLite, tenés que agregar parámetros adicionales como USER, PASSWORD, HOST. Para más detalles, ver la documentación de referencia para DATABASES.

Nota

Si usás PostgreSQL o MySQL, fijate de crear una base de datos antes de seguir. Para ello bastará con hacer “CREATE DATABASE database_name;” en el intérprete del motor correspondiente.

Si usás SQLite, no es necesario crear nada de antemano - el archivo de la base de datos se creará automáticamente cuando haga falta.

Mientras editás settings.py, podés setear TIME_ZONE a tu zona horaria.

También podés mirar el setting INSTALLED_APPS hacia el final del archivo. Éste registra los nombres de todas las aplicaciones Django que están activadas en esta instancia Django. Las apps se pueden usar en múltiples proyectos, y podés empaquetarlas y distribuirlas para su uso por otros en sus respectivos proyectos.

Por defecto, INSTALLED_APPS contiene las siguientes apps, todas provistas por Django:

  • django.contrib.admin – El sitio de administración. Lo vamos a usar en la parte 2 de este tutorial.

  • django.contrib.auth – Sistema de autenticación.

  • django.contrib.contenttypes – Un framework para tipos de contenido.

  • django.contrib.sessions – Un framework para manejo de sesiones.

  • django.contrib.messages – Un framework de mensajes.

  • django.contrib.staticfiles – Un framework para manejar los archivos estáticos.

Estas aplicaciones están incluidas por defecto como conveniencia para el caso común.

Algunas de estas aplicaciones hace uso de al menos una tabla de la base de datos, entonces necesitaremos crear las respectivas tablas antes de poder usarlas. Para ello corremos el siguiente comando:

$ python manage.py migrate

El comando migrate se fija en el setting INSTALLED_APPS y crea las tablas necesarias en la base de datos determinada por los parámetros establecidos en el archivo mysite/settings.py. Verás un mensaje por cada migración que se aplica. Si estás interesado, podés correr el cliente de línea de comandos de tu base de datos y tipear \dt (PostgreSQL), SHOW TABLES; (MySQL), o .schema (SQLite) para ver las tablas que Django creó.

Para los minimalistas

Como dijimos arriba, las aplicaciones incluidas por defecto son para el caso común, pero no todos las necesitan. Si no necesitás alguna o ninguna de las mismas, sos libre de comentar o borrar las líneas apropiadas de INSTALLED_APPS antes de correr migrate. El comando migrate sólo creará las tablas para las apps en INSTALLED_APPS.

El servidor de desarrollo

Verifiquemos que el proyecto Django funciona. Cambiamos al directorio mysite de más afuera, si no lo habías hecho, y corremos los siguientes comandos:

$ python manage.py runserver

Veremos la siguiente salida en la línea de comandos:

Performing system checks...

0 errors found
September 27, 2017 - 15:50:53
Django version 1.8, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Hemos levantado el servidor de desarrollo de Django, un servidor web liviano escrito puramente en Python. Viene incluido con Django para permitir desarrollar rápidamente, sin necesidad de configurar un servidor de producción – como Apache – hasta el momento en que todo esté listo para producción.

Este es un buen momento para notar: NO hay que usar este servidor para nada que se parezca a un entorno de producción. Está pensado solamente para desarrollo (Django es un framework web, no un servidor).

Ahora que el servidor está corriendo, podemos visitar http://127.0.0.1:8000/ en nuestro browser. Deberíamos ver una página con el mensaje “Welcome to Django”. Funcionó!

Cambiando el puerto

Por defecto, el comando runserver levanta el servidor de desarrollo en una IP interna en el puerto 8000.

Si uno quisiera cambiar el puerto, se puede pasar como argumento en la línea de comandos. Por ejemplo, para levantar el servidor escuchando en el puerto 8080:

$ python manage.py runserver 8080

Para cambiar la dirección IP del servidor, se pasa junto con el puerto. Entonces, para escuchar en todas las IP públicas (útil para mostrarle nuestro trabajo en otras computadoras), podemos usar:

$ python manage.py runserver 0.0.0.0:8000

La documentación completa sobre el servidor de desarrollo se puede encontrar en runserver.

Recarga automática de runserver

El servidor de desarrollo recarga automáticamente el código Python en cada request según sea necesario. No es necesario reiniciar el servidor para que los cambios al código tengan efecto. Sin embargo, algunas acciones como agregar archivos no producen un reinicio automático y entonces será necesario reiniciar el servidor a mano en esos casos.

Creando modelos

Ahora que hemos levantado nuestro entorno – un “proyecto” –, estamos listos para empezar a trabajar.

Cada aplicación que uno escribe en Django consiste de un paquete Python que sigue una ciera convención. Django trae una utilidad que automáticamente genera la estructura de directorios básica de una app, de tal manera que uno pueda concentrarse en escribir código en lugar de directorios.

Proyectos vs. apps

Cuál es la diferencia entre un proyecto y una app? Una app es una aplicación web que hace algo – e.g., un sistema de blog, una base de datos de registros públicos o una aplicación simple de encuestas. Un proyecto es una colección de configuración y apps para un sitio web particular. Un proyecto puede contener múltiples app. Una app puede estar en múltiples proyectos.

Las apps viven en cualquier lugar del Python path. En este tutorial, vamos a crear nuestra app en el directorio donde se encuentra el archivo manage.py, para que pueda ser importada como módulo de primer nivel, en lugar de ser un submódulo de mysite.

Para crear una app, nos aseguramos de estar en el mismo directorio que manage.py y corremos el comando:

$ python manage.py startapp polls

Esto creará el directorio polls, con la siguiente estructura:

polls/
    __init__.py
    admin.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

Esta estructura de directorio va a almacenar la aplicación poll.

El primer paso al escribir una app web en Django es definir los modelos – esencialmente, el esquema de base de datos, con metadata adicional.

Filosofía

Un modelo es la única y definitiva fuente de datos de nuestra información. Contiene los campos y comportamientos esenciales de los datos que vamos a guardar. Django sigue el :ref:`principio DRY <https://docs.djangoproject.com/en/1.8/misc/design-philosophies/#dry>`_. El objetivo es definir el modelo de datos en un lugar y automáticamente derivar lo demás a partir de éste.

Esto incluye las migraciones - a diferencia de Ruby On Rails, por ejemplo, las migraciones son completamente derivadas del archivo de modelos, y son esencialmente una historia que Django puede seguir para actualizar la base de datos y mantenerla en sincronía con tus modelos.

En nuestra simple app poll, vamos a crear dos modelos: Question and Choice. Una Question tiene una pregunta y una fecha de publicación. Una Choice tiene dos campos: el texto de la opción y un contador de votos. Cada Choice está asociada a una Question.

Estos conceptos se representan mediante clases Python. Editamos el archivo polls/models.py para que se vea así:

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)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

El código es directo. Cada modelo se representa por una clase que hereda de django.db.models.Model. Cada modelo tiene ciertas variables de clase, cada una de las cuales representa un campo de la base de datos en el modelo.

Cada campo se representa como una instancia de una clase Field – e.g., CharField para campos de caracteres y DateTimeField para fecha y hora. Esto le dice a Django qué tipo de datos almacena cada campo.

El nombre de cada instancia de Field (e.g. question_text o pub_date) es el nombre del campo, en formato amigable (a nivel código). Vamos a usar este valor en nuestro código, y la base de datos lo va a usar como nombre de columna.

Se puede usar un primer argumento, opcional, de Field para designar un nombre legible (a nivel ser humano). Se usa en algunas partes en que Django hace introspección, y funciona como documentación. Si no se provee este argumento, Django usa el nombre del campo. En el ejemplo, solamente definimos un nombre descriptivo para Question.pub_date. Para todos los demás campos en el modelo, el nombre del campo será suficiente.

Algunas clases de Field tienen argumentos requeridos. Por ejemplo, CharField requiere que se pase max_length. Esto se usa no sólo en el esquema de la base de datos sino también en la validación de los datos, como veremos más adelante.

Un Field puede tener también varios argumentos opcionales; en este caso, seteamos el valor default de votes a 0.

Finalmente, notemos que se define una relación, usando ForeignKey. Esto le dice a Django que cada Choice está relacionada a una única Question. Django soporta todos los tipos de relación comunes en una base de datos: muchos-a-uno, muchos-a-muchos y uno-a-uno.

Activando modelos

Ese poquito código le da a Django un montón de información. A partir de él, Django puede:

  • Crear el esquema de base de datos (las sentencias CREATE TABLE) para la app.

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

Pero primero debemos informarle a nuestro proyecto que la app polls está instalada.

Filosofía

Las apps Django son “pluggable”: podés usar una app en múltiples proyectos, y distribuirlas, porque no necesitan estar ligadas a una instancia de Django particular.

Editamos de nuevo el archivo mysite/settings.py, y cambiamos el setting INSTALLED_APPS para incluir 'polls'. Se verá algó así:

mysite/settings.py
INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'polls',
)

Ahora Django sabe sobre nuestra app polls. Corramos otro comando:

$ python manage.py makemigrations polls

“Deberíamos ver algo similar a lo siguiente:

Migrations for 'polls':
  0001_initial.py:
    - Create model Question
    - Create model Choice
    - Add field question to choice

Corriendo makemigrations, le estamos diciendo a Django que hemos hecho algunos cambios a nuestros modelos (en este caso, nuevos modelos) y que quisiéramos registrar esos cambios en una migración.

Las migraciones es como DJango guarda los cambios a nuestros modelos (y por lo tanto al esquema de base de datos) - son solamente archivos en disco. Podríamos leer la migración de nuestro nuevo modelo si quisiéramos; es el archivo polls/migrations/0001_initial.py. No te preocupes, no se espera que uno las lea cada vez que Django crea una nueva, pero están diseñadas para ser editables a mano en caso de que se quiera hacer alguna modificación en la forma que Django aplica los cambios.

Existe un comando que corre las migraciones y administra el esquema de base de datos automáticamente - se llama migrate, y llegaremos a él en un momento - pero primero, veamos cuál es el SQL que la migración correría. El comando sqlmigrate toma nombres de migraciones y devuelve el SQL respectivo:

$ python manage.py sqlmigrate polls 0001

Deberías ver algo similar a lo siguiente (reformateado aquí por legibilidad):

BEGIN;
CREATE TABLE "polls_choice" (
    "id" serial NOT NULL PRIMARY KEY,
    "choice_text" varchar(200) NOT NULL,
    "votes" integer NOT NULL
);
CREATE TABLE "polls_question" (
    "id" serial NOT NULL PRIMARY KEY,
    "question_text" varchar(200) NOT NULL,
    "pub_date" timestamp with time zone NOT NULL
);
ALTER TABLE "polls_choice" ADD COLUMN "question_id" integer NOT NULL;
ALTER TABLE "polls_choice" ALTER COLUMN "question_id" DROP DEFAULT;
CREATE INDEX "polls_choice_7aa0f6ee" ON "polls_choice" ("question_id");
ALTER TABLE "polls_choice"
  ADD CONSTRAINT "polls_choice_question_id_246c99a640fbbd72_fk_polls_question_id"
    FOREIGN KEY ("question_id")
    REFERENCES "polls_question" ("id")
    DEFERRABLE INITIALLY DEFERRED;

COMMIT;

Notemos lo siguiente:

  • La salida exacta varía de acuerdo a la base de datos que se esté usando. El ejemplo anterior está generado para PostgreSQL.

  • Los nombres de las tablas se generan automáticamente combinando el nombre de la app (polls) con el nombre, en minúsculas del modelo – question y choice (se puede modificar este comportamientos).

  • Las claves primarias (IDs) se agregan automáticamente (esto también se puede modificar).

  • Por convención, Django añade "_id" al nombre del campo de clave foránea (sí, se puede modificar esto también).

  • La relación de clave foránea se hace explícita mediante un constraint FOREIGN KEY. No te preocupes por la parte del DEFERRABLE; indica a PostgreSQL no forzar la clave foránea hasta el final de la transacción.

  • Se ajusta a la base de datos que se esté usando, y entonces los tipos de campos específicos de la base de datos como auto_increment (MySQL), serial (PostgreSQL), o integer primary key (SQLite) se manejan por uno automáticamente. Lo mismo aplica para los nombres de los campos – e.g., el uso de comillas dobles o simples.

  • El comando sqlmigrate no corre la migración en la base de datos - solamente imprime por pantalla para mostrar cuál es el SQL que Django piensa es requerido. Es útil para chequear lo que Django va a hacer o si uno tiene administradores de base de datos que requieren el SQL para aplicar los cambios.

Si te interesa, también podés correr python manage.py check; este comando chequea por cualqueir problema en tu proyecto sin aplicar las migraciones ni tocar la base de datos.

Ahora corramos migrate de nuevo para crear las tablas correspondientes a nuestros modelos en la base de datos:

$ python manage.py migrate
Operations to perform:
  Synchronize unmigrated apps: staticfiles, messages
  Apply all migrations: admin, contenttypes, polls, auth, sessions
Synchronizing apps without migrations:
  Creating tables...
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying <migration name>... OK

El comando migrate toma todas las migraciones que no se aplicaron (Django lleva registro de cuáles se aplicaron usando una tabla especial en la base de datos llamada django_migrations) y las corre contra la base de datos - esencialmente, sincroniza el esquema de la base de datos con los cambios hechos a nuestros modelos.

Las migraciones son muy poderosas y nos permiten cambiar nuestros modelos a lo largo del tiempo, mientras se avanza con el proyecto, sin necesidad de borrar la base de datos o las tablas, y crear nuevas - se especializa en actualizar la base de datos sin perder información. Las veremos en más detalle en una parte más adelante del tutorial, pero por ahora, recordermos los 3 pasos para hacer cambios a nuestros modelos:

  • Cambiar nuestros modelos (en models.py).

  • Correr python manage.py makemigrations para crear las migraciones correspondientes a esos cambios

  • Correr python manage.py migrate para aplicar esos cambios a la base de datos.

La razón por la que hay comandos separados para crear y aplicar migraciones es porque vas a necesitar hacer commit de las migraciones en tu sistema de control de versiones y distribuirlas con tu app; no solamente hacen tu desarrollo más simple, también son reusables por otros desarrolladores y en producción.

Para tener la información completa de qué puede hacer la utilidad manage.py, podés leer la documentación de django-admin.py.

Jugando con la API

Ahora pasemos al intérprete interactivo de Python y juguemos con la API que Django nos provee. Para invocar el shell de Python, usamos este comando:

$ python manage.py shell

Usamos esto en lugar de simplemente tipear “python” porque manage.py setea la variable de entorno DJANGO_SETTINGS_MODULE, que le da a Django el import path al archivo mysite/settings.py.

Evitando manage.py

Si preferís no usar manage.py, no hay problema. Basta setear la variable de entorno DJANGO_SETTINGS_MODULE a mysite.settings, levantar un shell de Python, y configurar Django:

>>> import django
>>> django.setup()

Si esto levanta una excepción AttributeError, probablemente estás usando una versión de Django que no coincide con la de este tutorial. Deberías cambiar a la versión del tutorial (o conseguir la versión de Django) correspondiente.

Tenés que correr python en el mismo directorio que está manage.py, o asegurarte de que ese directorio está en el Python path, para que import mysite funcione.

Para más información sobre todo esto, ver la documentación de django-admin.

Una vez en el shell, exploramos la API de base de datos:

>>> from polls.models import Question, Choice   # Import the model classes we just wrote.

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

# 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. Note that this might say "1L" instead of "1", depending
# on which database you're using. That's no biggie; it just means your
# database backend prefers to return integers as Python long integer
# objects.
>>> 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=<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()
[<Question: Question object>]

Un minuto. <Question: Question object> es, definitivamente, una representación poco útil de este objeto. Arreglemos esto editando los modelos (en el archivo polls/models.py) y agregando el método __str__() a Question y Choice:

polls/models.py
from django.db import models

class Question(models.Model):
    # ...
    def __str__(self):              # __unicode__ on Python 2
        return self.question_text

class Choice(models.Model):
    # ...
    def __str__(self):              # __unicode__ on Python 2
        return self.choice_text

Es importante agregar el método __str__() a nuestros modelos, no sólo por nuestra salud al tratar con el intérprete, sino también porque es la representación usada por Django en la interfaz de administración autogenerada.

__str__ o __unicode__?

En Python 3, es fácil, usamos __str__().

En Python 2, deberíamos en vez definir el método __unicode__() que devuelva valores unicode. Los modelos de Django tienen una implementación por defecto de __str__() que llama a __unicode__() y convierte el resultado a un bytestring UTF-8. Esto quiere decir que unicode(p) devuelve un string Unicode, y str(p) devuelve un bytestring, encodeado en UTF-8. Python hace lo contrario: object tiene un método __unicode__ que llama a __str__ e interpreta el resultado como un bytestring ASCII. Esta diferencia puede generar confusión.

Si todo esto es mucho ruido, usá Python 3.

Notar que estos son métodos Python normales. Agreguemos uno más, como demostración:

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)

Notar que agregamos import datetime y from django.utils import timezone, para referenciar el módulo datetime de la librería estándar de Python y las utilidades de Django relacionadas a zonas horarias en django.utils.timezone, respectivamente. Si no estás familiarizado con el manejo de zonas horarias en Python, podés aprender más en la documentación de time zone.

Guardamos los cambios y empezamos una nueva sesión en el shell corriendo python manage.py shell nuevamente:

>>> from polls.models import Question, Choice

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

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

# 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()
[<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)
[<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 más información sobre relaciones en modelos, ver Acceder objetos relacionados. Para más detalles sobre cómo usar los doble guión bajo para efectuar búsquedas usando la API, ver Field lookups. Para el detalle completo de la API de base de datos, ver Database API reference.

Cuando te sientas confortable con la API, podés pasar a parte 2 del tutorial para tener funcionando la interfaz automática de administración de Django.