Novedades en Django 1.5

Hace aproximadamente un año desde la última versión de Django, y desde entonces muchas cosas han cambiado. La versión 1.5 fue lanzada hace apenas un par de días y viene cargada de novedades (también podéis ver los artículos sobre las versiones versión 1.3 y versión 1.4).

Soporte para Python 3

Por fin Django es compatible con Python 3... aunque de forma no estable aún. Tras un esfuerzo muy grande por parte de todos los desarrolladores se ha conseguido portar todo el código de Django de tal manera que el mismo código base sea compatible al mismo tiempo con Python 2 y Python 3.

Para ello han hecho uso de la librería de python six, la cual se encarga de proporcionar una API que salva las diferencias que pueda haber entre ambas versiones.

Django ha creado una guía muy útil en su wiki sobre como portar a Python 3 aplicaciones para Django. A quien haga webs para cliente final usualmente no le va a interesar (una vez empiezas una web decides para que versión de Python hacerla; normalmente no interesa hacerla compatible para las dos), pero en el caso de desarrolladores de módulos de Django aconsejo la lectura del enlace anterior, ya que pasará mucho tiempo hasta que Django deje de soportar Python 2, y hasta que llegue ese momento, las aplicaciones deberán ser compatibles con ambas versiones.

La situación actual queda entonces de la siguiente manera:

  • En la rama de Python 2 se soporta como mínimo la versión 2.6.5, aunque se aconseja fervientemente usar Python 2.7.3 o superiores. La compatibilidad con estas versiones quedará ya así posiblemente hasta que Django decida dejar de soportar Python 2 oficialmente (para lo que queda aún mucho tiempo).
  • Sobre Python 3 se soporta como mínimo la versión 3.2.0, aunque es de esperar que esta versión mínima vaya aumentando, como lo ha hecho en los últimos años con la de Python 2.

Por último, para los que no sean valientes, Django ofrecerá una versión estable y plenamente compatible con Python 3 en su próxima versión: Django 1.6

Modelos de usuario configurables

Este es el gran cambio de Django 1.5, uno de esos que se llevaban esperando años. Tras mucha discusión en la lista de desarrollo, para esta versión han llegado a un acuerdo sobre como ofrecer modelos de usuario configurables.

Hasta ahora, en django.contrib.auth.models teníamos el modelo User, que es el que se usaba para la gestión de usuarios. El problema de este modelo es que es muy poco configurable. Ofrece campos que no son siempre útiles (hay muchas webs que no necesitan el campo username, usando el email para identificar a la persona) y es muy difícil de extender, ya que aunque pudieses heredar de ese modelo, era una tarea complicada el hacer que el resto del portal, con el admin de Django incluído, se integrase correctamente con ese nuevo modelo.

En Django 1.5 se añade una nueva directiva de configuración llamada AUTH_USER_MODEL que especifica que modelo de usuario se quiere usar.

Para que las aplicaciones sean compatibles con ese posible cambio, a partir de este momento, en todos los modelos donde se quiera crear una ForeignKey al usuario, no se deberá usar el User de django normal, si no que se tendrá que crear la relación sobre la directiva antes mencionada.

# settings.py
AUTH_USER_MODEL = 'usuarios.Usuario'

# blog/models.py
from django.conf import settings

class Articulo(models.Model):
    autor = models.ForeignKey(settings.AUTH_USER_MODEL)

Como veis en el settings.py definimos cual será nuestro modelo de usuario (el formato es app.modelo), y a partir de ahí, en las relaciones que tengan nuestros modelos con los usuarios, usamos ese nuevo setting.

Por último quedaría la parte más importante: definir el nuevo modelo de usuario. Para ello Django nos facilita la tarea ofreciéndonos varios modelos base de los que poder hederar (entre los que tenemos AbstractBaseUser). La base obligatoria que Django necesita para que el nuevo modelo de usuario sea válido son:

  • Tiene que tener una clave primaria
  • También debe tener un campo que sea único con el que poder identificar al usuario (un nombre de usuario, email, ...)
  • Por último debes poder referirte al usuario de una manera corta y de otra larga (nombre y apellidos, y nombre de usuario por ejemplo).
from django.contrib.auth.models import AbstractBaseUser

# usuarios/models.py
class Usuario(AbstractBaseUser):
    email = models.EmailField(max_length=254, unique=True)
    cif = models.CharField(max_length=20)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['cif']

    def get_full_name(self):
        return self.email

    def get_short_name(self):
        return self.email

Como podemos ver en el ejemplo anterior, creamos un nuevo modelo de usuario heredando desde la clase AbstractBaseUser. En este modelo definimos un campo de email y un CIF. A continuación usamos la variable USERNAME_FIELD para indicarle a Django cual es el campo que será usado como campo único (para cumplir el segundo requisito) y cuales son los campos que son obligatorios de introducir para crear un usuario (el campo definido en USERNAME_FIELD pasa a ser requerido automáticamente).

Por último, definimos los métodos get_full_name y get_short_name para cumplir el tercer requisito, indicándole como obtener el nombre corto y largo del usuario (que en nuestro caso no es muy imaginativo: devolvemos siempre el email).

Y con esto quedarían satisfechas las tres condiciones, ya que el campo que define una clave primaria (primer requisito) se crea automáticamente. Ya solo nos quedaría crearle un Manager al modelo, para que Django sepa como crear usuarios y super-usuarios.

Recomiendo la lectura de la wiki de Django sobre como crear modelos de usuario ya que explican perfectamente como crear modelos y managers que sean válidos. Espero escribir un artículo más detallado sobre el tema en algún momento, ya que da para un artículo entero :)

Guardando subsets de un modelo

Hasta ahora si hacíamos una operación save sobre una instancia de modelo, la sentencia SQL que se generaba contra la base de datos incluía todos los campos del propio modelo, y no solo los que hubiesen cambiado.

Esto, que puede parecer una tontería, es un problema en caso de que el modelo tenga muchos campos, o que algunos de ellos tengan mucho contenido. Aparte, podía provocar problemas de concurrencia, en los que varios usuarios se pisan sus cambios sobre el mismo objeto aunque ni siquiera hubiesen cambiado los mismos campos.

Para resolver esta circunstancia Django ha añadido un nuevo parámetro a la llamada save en el que se puede especificar que campos en concreto se quieren guardar, tal que la consulta SQL de UPDATE incluirá únicamente esos.

def mi_vista(request):
    user = request.user
    user.username = 'Paco'
    user.save(update_fields=['username'])

Como podéis ver en el ejemplo anterior, tras cambiarle el nombre al usuario que ejecuta la vista, se realiza un save en el que se especifica mediante el parámetro update_fields que solo queremos guardar ese campo.

Como nota adicional, los métodos defered y only (los cuales fueron explicados en las novedades de Django 1.4), pasan a usar este nuevo sistema, por lo que solo guardarán los campos que realmente se hayan bajado de la BD.

Mejora en el cacheo de modelos

El ORM de Django pasa a ser un poco más listo en esta nueva versión.

Hasta ahora, a la hora de atravesar relaciones (ForeignKey o ManyToMany), Django no era capaz de detectar cuando un modelo había sido ya cogido de la base de datos, por lo que podía llegar a hacer más de una consulta para traerse exactamente el mismo objeto.

def mi_vista(request, pk):
    noticia = Noticia.objects.get(pk=pk)
    comentario = noticia.comentario_set.all()[0]

    assert comentario.noticia == noticia

Hasta ahora el código anterior generaba una consulta en la linea número 3 para coger el comentario, y otra en la línea número 5 para volver a obtener la noticia, a pesar de que la noticia ya la tenemos ya que estamos obteniendo el comentario a través de ella. Pues Django 1.5 corrige este comportamiento, generando una única consulta entre la línea en la que se coge el comentario, y la línea del assert).

Se añade un nuevo response para streaming

En una página web normalmente las respuestas que da el servidor son de contenido finito y tamaño conocido, tal que por ejemplo, en el caso más básico, se renderiza un template con unos datos y se manda al navegador web.

Este caso no tiene por qué ser siempre así, siendo posible también querer devolver una respuesta de tipo streaming, en la que el contenido de la respuesta no tiene un tamaño delimitado, si no que se va enviando poco a poco (por ejemplo, para hacer streaming de audio o vídeo).

Hasta Django 1.5, la única forma de devolver este tipo de response era devolviendo un iterador a través de un HttpResponse normal y corriente (el cual iría consumiendo el iterador hasta que se quedase sin contenido). Este sistema tenía muchos inconvenientes, como que por ejemplo un Middleware podía consumir el iterador antes de que este llegase a salir hacia el cliente.

Para resolverlo, a partir de ahora se añade una nueva clase llamada StreamingHttpResponse, especializada en este tipo de responses

Otros cambios menores

Algunos cambios menores que se han añadido que merecen la pena señalar son:

  • Se ha añadido un nuevo templatetag, llamado verbatim, que tiene como objetivo el desactivar el parser de Django en todo el código que haya en su interior. Esto es útil para escribir templates con ciertas librerías de JS que tenían como tag el mismo que Django (las dos llaves {{}}) y que por lo tanto provocaban conflictos con Django.
  • Se ha mejorado la documentación de Django, estando ahora mucho mejor organizada. Aparte, se ha actualizado el tutorial de introducción a Django, siendo ahora más completo.
  • Todos los mensajes de logging se imprimirán por la consola cuando la variable DEBUG esté habilitada.
  • Django pasa a proveer templates 404.html y 500.html por lo que deja de ser obligatorio proporcionarlos (evitando generar errores 500 en producción por no existir dichos templates por ejemplo).

Y estos son algunos de los cambios que traen esta nueva versión. La lista completa en inglés la podéis consultar en el siguiente enlace, en la web oficial de Django.

Comentarios