Django es el entorno de desarrollo web para perfeccionistas con límites de tiempo

Entradas sobre "tutorial":

Crear gráficas estadísticas con Django y Google Visualization API

Vamos a ver cómo combinar django-qsstats para generar estadísticas y Google Visualization API para representarlas gráficamente.

django-qsstats permite obtener estadísticas agregadas sobre querysets de forma sencilla. Vamos a crear una vista que nos dé el número de usuarios registrados cada día para los últimos 14 días. Para ello crearemos una QuerySetStats a partir del campo date_joined del modelo User de django.contrib.auth. Usaremos time_series pasándole la fecha inicial y final, para obtener un listado de tuplas tipo (fecha, valor) diario con la fecha y el número de usuarios registrados cada día. Necesitaremos pasar a nuestra plantilla nuestra clave de la API de javascript de Google. Si no tienes una clave puedes conseguirla aquí.

Crearemos la siguiente vista en views.py:

from django.template import RequestContext
from django.shortcuts import render_to_response
from django.contrib.auth import User
import datetime
import qsstats

def estadisticas(request):

    GOOGLE_API_KEY = 'clave'

    qs = User.objects.all()
    qss = qsstats.QuerySetStats(qs, 'date_joined')

    hoy = datetime.date.today()
    hace_2_semanas = hoy - datetime.timedelta(weeks=2)

    users_stats = qss.time_series(hace_2_semanas, hoy)

    return render_to_response('estadisticas.html', locals(), context_instance=RequestContext(request))

En user_stats tendremos una lista de tuplas con un elemento por cada día de las dos semanas.

En nuestra plantilla estadisticas.html cargaremos la API de javascript de Google. Crearemos una tabla de datos con DataTable y utilizaremos un gráfico tipo LineChart para su representación. La plantilla quedará de la siguiente manera:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
</head>
<body>
    <h1>Nuevos usuarios en las últimos 2 semanas</h1>
    <div id="usuarios_14_dias"></div>
</body>
</html>

<script type="text/javascript" src="http://www.google.com/jsapi?key={{ GOOGLE_API_KEY }}"></script>
<script type="text/javascript"><!--
    google.load("visualization", "1", {packages:["corechart"]});
    google.setOnLoadCallback(function() {
        var data = new google.visualization.DataTable();
        data.addColumn('string', 'Día');
        data.addColumn('number', 'Usuarios nuevos');
        data.addRows({{ users_stats|length }});

        {% for s in users_stats %}  
            data.setValue({{ forloop.counter0 }}, 0, '{{ s.0|date:"d M" }}');
            data.setValue({{ forloop.counter0 }}, 1, {{ s.1 }});
        {% endfor %}

        var chart = new google.visualization.LineChart(document.getElementById('usuarios_14_dias'));
        chart.draw(data, {width: 620, height: 200, legend:'none'});
    });
//--></script>

Obtendremos un gráfico como el siguiente:

Google Visualization API graph

Publicado por Antonio Melé el Domingo 15 de Agosto de 2010 | 1 comentario | Categorías: plantillas, tutorial

Aplicar filtros a annotate() al usar Count()

A veces queremos utilizar la función de aggregación annotate() y aplicar filtros al modelo que se encuentra dentro de la misma, pero no es posible hacerlo con el ORM de Django actualmente. En este caso la solución pasa por usar la función extra() del ORM de Django para incluir SQL propio.

Vamos a ver un ejemplo práctico. Supongamos una aplicación llamada ocio y el siguiente models.py con hobbies y personas que los practican:

from django.db import models

class Hobby(models.Model):
    nombre = models.CharField(max_length=250)

SEXO_CHOICES = (
    ('m','Masculino'),
    ('f','Femenino'),
)

class Persona(models.Model):
    nombre = models.CharField(max_length=250)
    sexo = models.CharField(max_length=1, choices=SEXO_CHOICES)
    hobbies = models.ManyToManyField(Persona, related_name='personas')

Gracias a las funciones de agregación de Django podemos obtener fácilmente todos los hobbies y el número de personas que lo practican utilizando annotate(). En el siguiente ejemplo vemos que para cada hobby se contará el número de personas relacionadas y será accesible mediante el atributo personas_count:

from django.db.models import Count

hobbies = Hobby.objects.annotate(personas_count=Count('personas_count'))

for hobby in hobbies:
    print '%s personas practican %s' % (hobby.personas_count, hobby.nombre)

Con esto contamos todas las personas relacionadas con cada hobby, pero a veces nos interesa filtrar las personas que se contabilizan. ¿Y si sólo quisiéramos obtener el número de mujeres que practican cada hobby? En este caso queremos filtrar por un atributo del modelo que se está utilizando en annotate() y de momento no se puede hacer con el ORM de Django.

Para obtener el número de mujeres que practican cada hobby lo primero que haremos será obtener mediante values_list() un listado de los id de las personas que cumplan la condición sexo='f':

# obtenemos lista en python, p. ej: [13, 21, 60]
mujeres_ids = Persona.objects.filter(sexo='f').values_list('id', flat=True)

A continuación convertiremos la lista a un string con id's separados por comas para poder utilizarlos en nuestra sentencia SQL:

# id's separados por comas en string, p. ej: "13,21,60"
lista_ids = ','.join(str(id) for id in mujeres_ids)

Después prepararemos la sentencia SQL que utilizaremos en nuestro queryset(). Recordemos que por defecto Django crea las tablas de la base de datos con la nomenclatura aplicacion_modelo. Por lo que si nuestra aplicación se llama ocio, la tabla para el modelo Persona será ocio_persona y para el modelo Hobby será ocio_hobby. Para la relación M2M Django genera una tabla intermedia que relaciona los id's de personas con los id's de hobbies. Esta tabla en nuestro caso será ocio_persona_hobbies ya que el campo de la relación ManyToMany se llama hobbies y se encuentra en el modelo Persona.

Teniendo en cuenta que la query va a hacerse sobre el modelo Hobby utilizaremos una consulta SELECT que realice un COUNT sobre la tabla que relaciona personas con hobbies de tal forma que por cada hobby (ocio_hobby.id) contemos el número de filas en los que el id de la persona relacionada se encuentre en la lista de id's de mujeres.

'SELECT COUNT(*) FROM ocio_persona_hobbies WHERE ocio_persona_hobbies.hobby_id = ocio_hobby.id AND ocio_persona_hobbies.persona_id IN (%s)' % lista_ids

Por último prepararemos nuestra queryset() utilizando extra() para incluir nuestro código SQL. Así quedará el código completo:

mujeres_ids = Persona.objects.filter(sexo='f').values_list('id', flat=True)
lista_ids = ','.join(str(id) for id in mujeres_ids)

hobbies = Hobby.objects.extra(
    select = {
        'personas_count': 'SELECT COUNT(*) FROM ocio_persona_hobbies WHERE ocio_persona_hobbies.hobby_id = ocio_hobby.id AND ocio_persona_hobbies.persona_id IN (%s)' % lista_ids
    },
)

Si queremos hacer nuestro código independiente de los nombres de las tablas de la base de datos podemos usar Persona._meta.db_table para obtener el nombre de la tabla usada para el modelo Persona y del mismo modo para el modelo Hobby. Para obtener el nombre de la tabla que relaciona personas y hobbies usaremos Persona._meta.get_field('hobbies').m2m_db_table().

El código independiente de las tablas quedaría de la siguiente manera:

mujeres_ids = Persona.objects.filter(sexo='f').values_list('id', flat=True)
lista_ids = ','.join(str(id) for id in mujeres_ids)

personas_hobbies = Persona._meta.get_field('hobbies').m2m_db_table()

hobbies = Hobby.objects.extra(
    select = {
        'personas_count': 'SELECT COUNT(*) FROM %s WHERE %s.hobby_id = %s.id AND %s.persona_id IN (%s)' % (personas_hobbies, personas_hobbies, Hobby._meta.db_table, personas_hobbies, lista_ids)
    },
)

Publicado por Antonio Melé el Viernes 13 de Agosto de 2010 | 0 comentarios | Categorías: modelos, querysets, trucos, tutorial

Generar archivos PDF con Django y Pisa

Pisa es un conversor de HTML/XHTML a PDF escrito en Python. Vamos a ver cómo utilizar Pisa en nuestras vistas para convertir nuestras plantillas HTML a PDF.

Lo primero que tenemos que hacer es descargar e instalar Pisa. Pisa requiere ReportLab Toolkit, HTML5lib, pyPdf y PIL (útil: Cómo instalar PIL en Mac OSX). La forma más sencilla de instalar Pisa es mediante easy_install:

easy_install pisa

También podemos bajar el código fuente e instalarlo mediante el siguiente comando (en Linux y Mac Osx puedes necesitar incluir sudo delante):

python setup.py install

Una vez instalado Pisa añadiremos a views.py una función que nos permita convertir nuestras plantillas HTML a PDF. Utilizaremos una vista y una plantilla HTML que la función se encargará de convertir a formato PDF y devolverlo en la petición HTTP. La función recibirá la plantilla HTML renderizada previamente en nuestra vista mediante render_to_string.

Vamos a ver cómo quedaría nuestro views.py para una vista que reciba el id de un hipotético modelo Libro y devuelva un PDF con la información del mismo:

# -*- coding: utf-8 -*-

import ho.pisa as pisa
import cStringIO as StringIO
import cgi
from django.template import RequestContext
from django.template.loader import render_to_string
from django.http import HttpResponse

def generar_pdf(html):
    # Función para generar el archivo PDF y devolverlo mediante HttpResponse
    result = StringIO.StringIO()
    pdf = pisa.pisaDocument(StringIO.StringIO(html.encode("UTF-8")), result)
    if not pdf.err:
        return HttpResponse(result.getvalue(), mimetype='application/pdf')
    return HttpResponse('Error al generar el PDF: %s' % cgi.escape(html))

def libro_pdf(request, id):
    # vista de ejemplo con un hipotético modelo Libro
    libro=get_object_or_404(Libro, id=id)
    html = render_to_string('libro_pdf.html', {'pagesize':'A4', 'libro':libro}, context_instance=RequestContext(request))
    return generar_pdf(html)

Ahora sólo nos falta crear la plantilla libro_pdf.html para mostrar la información del libro:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
</head>    
<body>
    <h1>{{ libro.titulo }}</h1>
    <p>{{ libro.descripcion }}</p>
</body>
</html>

Incluir imágenes en los PDFs generados con Pisa

Si queremos incluir imágenes en nuestro PDF deberemos utilizar su ruta local en la plantilla en vez de su URL. Imaginando que nuestro modelo Libro tuviera un campo imagen de tipo ImageField podríamos hacerlo de la siguiente forma:

<img src="{{ libro.imagen.path }}" />

Las imágenes suelen quedar más grandes de lo normal debido al mapeo de píxeles a puntos que hace Pisa. Para que las imágenes salgan con un tamaño adecuado conviene incluirles el estilo CSS zoom:

<img src="{{ libro.imagen.path }}" style="zoom: 80%;" />

Etiquetas HTML especiales de Pisa

Hay varias etiquetas especiales que aporta Pisa y que podemos utilizar en nuestras plantillas. Las más útiles son: pdf:pagenumber y pdf:nextpage. La primera nos permite incluir número de página en cada una de las páginas del documento PDF y la segunda nos permite definir dónde comienza otra página. Su uso en las plantillas HTML es muy sencillo:

<pdf:pagenumber>
<pdf:nextpage>

Como véis, las posibilidades son múltiples a la hora de generar PDFs dinámicamente con Python. Aquí tenéis un ejemplo de PDF que se genera dinámicamente mediante este método: Receta de dátiles con bacon

Publicado por Antonio Melé el Miércoles 4 de Agosto de 2010 | 2 comentarios | Categorías: plantillas, snippets, tutorial

Crear una imagen de nuestros modelos con django-command-extensions

Algo interesante que nos aporta django-command-extensions es poder crear una representación gráfica de nuestros modelos (o por decirlo de otro modo nuestro esquema de base de datos) con tan sólo un comando. Esto es posible gracias a GraphViz y el resultado es algo parecido a un diseño UML. Para poder utilizar este comando debemos tener instalado pygraphviz y por supuesto la aplicación django_extensions debe estar incluída en el setting INSTALLED_APPS de nuestro proyecto.

Para instalar pygraphviz en Linux nos bastará con utilizar el comando:

apt-get install python-pygraphviz

Una vez hemos instalado django-command-extensions y pygraphviz podremos crear un archivo dot, formato utilizado por GraphViz ó una imagen. En nuestro caso vamos a crear un archivo PNG que incluya los modelos de todas las aplicaciones de nuestro proyecto. Para ello usamos el comando:

./manage.py graph_models -a -g -o mis_modelos.png

Con el parámetro -o especificamos el archivo de imagen en el que queremos que se almacene el resultado. Si sólo queremos incluir los modelos de algunas aplicaciones podemos hacerlo con el siguiente comando:

./manage.py graph_models app1 app2 app3 -o mis_modelos.png

Este ejemplo es de los modelos de la PyCon-Tech, un framework de gestión de conferencias basado en Django:

Representación gráfica de modelos con django-command-extensions y graphviz

Publicado por Antonio Melé el Domingo 12 de Julio de 2009 | 0 comentarios | Categorías: aplicaciones, imágenes, modelos, pluggables, trucos, tutorial