El libro de Django 1.0

5.13. Realizar cambios en el esquema de una base de datos

Cuando presentamos el comando syncdb previamente en este capítulo, hicimos notar que syncdb simplemente crea tablas que todavía no existen en tu base de datos — no sincroniza cambios en modelos ni borra modelos. Si agregas o modificas un campo de un modelo o si eliminas un modelo, será necesario que realices el cambio en tu base de datos en forma manual. Esta sección explica cómo hacerlo.

Cuando estás realizando cambios de esquema, es importante tener presente algunas características de la capa de base de datos de Django:

  • Django se quejará estrepitosamente si un modelo contiene un campo que todavía no ha sido creado en la tabla de la base de datos. Esto causará un error la primera vez que uses la API de base de datos de Django para consultar la tabla en cuestión (esto es, ocurrirá en tiempo de ejecución y no en tiempo de compilación).
  • A Django no le importa si una tabla de base de datos contiene columnas que no están definidas en el modelo.
  • A Django no le importa si una base de datos contiene una tabla que no está representada por un modelo.

El realizar cambios al esquema de una base de datos es cuestión de cambiar las distintas piezas — el código Python y la base de datos en sí misma — en el orden correcto.

5.13.1. Agregar campos

Cuando se agrega un campo a una tabla/modelo en un entorno de producción, el truco es sacar ventaja del hecho que a Django no le importa si una tabla contiene columnas que no están definidas en el modelo. La estrategia es agregar la columna en la base de datos y luego actualizar el modelo Django para que incluya el nuevo campo.

Sin embargo, tenemos aquí un pequeño problema del huevo y la gallina, porque para poder saber cómo debe expresarse la nueva columna en SQL, necesitas ver la salida producida por el comando manage.py sqlall de Django, el cual requiere que el campo exista en el modelo. (Notar que no es un requerimiento el que crees tu columna con exactamente el mismo SQL que usaría Django, pero es una buena idea el hacerlo para estar seguros de que todo está en sincronía).

La solución al problema del huevo y la gallina es usar un entorno de desarrollo en lugar de realizar los cambios en un servidor de producción. (Estás usando un entorno de pruebas/desarrollo, ¿no es cierto?). Este es el detalle de los pasos a seguir.

Primero, realiza los siguientes pasos en el entorno de desarrollo (o sea, no en el servidor de producción):

  1. Agrega el campo a tu modelo.
  2. Ejecuta manage.py sqlall [yourapp] para ver la nueva sentencia CREATE TABLE para el modelo. Toma nota de la definición de la columna para el nuevo campo.
  3. Arranca el shell interactivo de tu base de datos (por ej. psql o mysql, o puedes usar manage.py dbshell). Ejecuta una sentencia ALTER TABLE que agregue tu nueva columna.
  4. (Opcional) Arranca el shell interactivo de Python con manage.py shell y verifica que el nuevo campo haya sido agregado correctamente importando el modelo y seleccionando desde la tabla (por ej. MyModel.objects.all()[:5]).

Entonces en el servidor de producción realiza los siguientes pasos:

  1. Arranca el shell interactivo de tu base de datos.
  2. Ejecuta la sentencia ALTER TABLE que usaste en el paso 3 de arriba.
  3. Agrega el campo a tu modelo. Si estás usando un sistema de control de revisiones de código fuente y has realizado un check in de la modificación del paso 1 del trabajo en el entorno de desarrollo, entonces puedes actualizar el código (por ej. svn update si usas Subversion) en el servidor de producción.
  4. Reinicia el servidor Web para que los cambios en el código surtan efecto.

Por ejemplo, hagamos un repaso de los que haríamos si agregáramos un campo num_pages al modelo Book descrito previamente en este capítulo. Primero, alteraríamos el modelo en nuestro entorno de desarrollo para que se viera así:

class Book(models.Model):
    title = models.CharField(maxlength=100)
    authors = models.ManyToManyField(Author)
    publisher = models.ForeignKey(Publisher)
    publication_date = models.DateField()
    num_pages = models.IntegerField(blank=True, null=True)

    def __str__(self):
        return self.title

(Nota: Revisa el apartado "Agregando columnas NOT NULL" para conocer detalles importantes acerca de por qué hemos incluido blank=True y null=True).

Luego ejecutaríamos el comando manage.py sqlall books para ver la sentencia CREATE TABLE. La misma se vería similar a esto:

CREATE TABLE "books_book" (
    "id" serial NOT NULL PRIMARY KEY,
    "title" varchar(100) NOT NULL,
    "publisher_id" integer NOT NULL REFERENCES "books_publisher" ("id"),
    "publication_date" date NOT NULL,
    "num_pages" integer NULL
);

La nueva columna está representada de la siguiente manera:

"num_pages" integer NULL

A continuación, arrancaríamos el shell interactivo de base de datos en nuestra base de datos de desarrollo escribiendo psql (para PostgreSQL), y ejecutaríamos la siguiente sentencia:

ALTER TABLE books_book ADD COLUMN num_pages integer;

Luego de la sentencia ALTER TABLE, verificaríamos que el cambio haya funcionado correctamente, para ello iniciaríamos el shell de Python y ejecutaríamos este código:

>>> from mysite.books.models import Book
>>> Book.objects.all()[:5]

Si dicho código no produjera errores, podríamos movernos a nuestro servidor de producción y ejecutaríamos la sentencia ALTER TABLE en la base de datos de producción. Entonces, actualizaríamos el modelo en el entorno de producción y reiniciaríamos el servidor Web.

5.13.2. Eliminar campos

Eliminar un campo de un modelo es mucho más fácil que agregar uno. Para borrar un campo, sólo sigue los siguientes pasos:

  1. Elimina el campo de tu modelo y reinicia el servidor Web.
  2. Elimina la columna de tu base de datos, usando un comando como este:
ALTER TABLE books_book DROP COLUMN num_pages;

5.13.3. Eliminar campos Many-to-Many

Debido a que los campos many-to-many son diferentes a los campos normales, el proceso de borrado es diferente:

  1. Elimina el campo ManyToManyField de tu modelo y reinicia el servidor Web.
  2. Elimina la tabla many-to-many de tu base de datos, usando un comando como este:
DROP TABLE books_books_publishers;

5.13.4. Eliminar modelos

Eliminar completamente un modelo es tan fácil como el eliminar un campo. Para borrar un modelo, sigue los siguientes pasos:

  1. Elimina el modelo de tu archivo models.py y reinicia el servidor Web.
  2. Elimina la tabla de tu base de datos, usando un comando como este:
DROP TABLE books_book;