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

Entradas sobre "etiquetas":

Descubriendo objetos similares por sus etiquetas 2

Ya hablamos anteriormente sobre cómo descubrir objetos similares por sus etiquetas con la versión 1.0 de Django. Sin embargo la versión de desarrollo de Django incluye funciones de agregación que nos permiten realizar este tipo de tareas de una manera más cómoda sin tener que utilizar SQL puro como hacíamos antes.

Partiremos exactamente del mismo código que usamos la vez anterior: Dos modelos definidos en el archivo models.py de nuestra aplicación:

class Etiqueta(models.Model):
    nombre = models.CharField(max_length=40)
    slug = models.SlugField(max_length=40)

class Entrada(models.Model):
    titulo = models.CharField(max_length=150)
    slug = models.SlugField(max_length=150, unique=True)
    cuerpo = models.TextField()
    etiquetas = models.ManyToManyField(Etiqueta, related_name="entradas")

Queremos construir una vista que reciba el slug perteneciente a una entrada y obtenga dicha entrada y sus entradas similares en función de las etiquetas que tienen las distintas entradas.

Para ello utilizamos la función de agregación Count que se encuentra en django.db.models y que nos va a permitir realizar una consulta SQL con COUNT de una forma sencilla. Primero realizamos el import correspondiente:

from django.db.models import Count

A continuación creamos la estructura básica de nuestra vista que recibe un slug y selecciona la entrada correspondiente al mismo:

from django.db.models import Count
from django.shortcuts import get_object_or_404

def ver_entrada(request,slug):
    entrada = get_object_or_404(Entrada, slug=slug)

Necesitamos la lista de entradas que comparten etiquetas con esta entrada. Para ello obtenemos en una lista cada id de las etiquetas correspondientes a la entrada utilizando el método values_list. Esta lista nos servirá para encontrar otras entradas que compartan dichas etiquetas:

entrada_etiquetas_ids = entrada.etiquetas.values_list('id', flat=True)

Tras esto sólo queda seleccionar entradas públicas cuyas etiquetas figuren en dicha lista, contabilizar el número de etiquetas en común con nuestra entrada original y ordernar las entradas resultantes por número de entradas en común, de mayor a menor similitud con la entrada original.

similares = Entrada.objects.filter(publico=True, etiquetas__in=entrada_etiquetas_ids).exclude(id=entrada.id).annotate(mismas_etiquetas=Count('etiquetas')).order_by('-mismas_etiquetas')

Con todo esto el código de nuestra vista quedaría de la siguiente manera:

from django.db.models import Count
from django.shortcuts import get_object_or_404

def ver_entrada(request,slug):
    entrada = get_object_or_404(Entrada, slug=slug)
    entrada_etiquetas_ids = entrada.etiquetas.values_list('id', flat=True)
    similares = Entrada.objects.filter(publico=True, etiquetas__in=entrada_etiquetas_ids).exclude(id=entrada.id).annotate(mismas_etiquetas=Count('etiquetas')).order_by('-mismas_etiquetas')
    # ...

Si comparamos este código con el que teníamos que utilizar antes de la introducción de las funciones de agregación vemos cómo éstas nos han simplificado enormemente el trabajo. ¡Gracias otra vez, Django!

Publicado por Antonio Melé el Tuesday 24 de February de 2009 | 0 comentarios | Categorías: etiquetas, trucos

Descubriendo objetos similares por sus etiquetas

Si estás utilizando la versión de desarrollo de Django es mejor que utilices las funciones de agregación.

Muchas veces utilizamos etiquetas para nuestros modelos de datos para poder distinguirlos por categorías. Cuando empecé a utilizar etiquetas para mis modelos con Django una de las primeras dudas que me surgió fue cómo poder encontrar objetos similares a otro objeto a partir de sus etiquetas. Sin ir más lejos es una característica que hemos añadido a este blog recientemente: En la página de un post se muestran otros similares por si son de interés para el visitante. Esta funcionalidad la hemos implementado exactamente con el mismo código que os vamos a mostrar hoy.

Nota: Si utilizas django-tagging, encontrarás una funcionalidad similar en el método get_related, pero siempre viene bien aprender cómo funcionan las cosas :)

Partiremos de dos sencillos modelos definidos en el archivo models.py de nuestra aplicación:

class Etiqueta(models.Model):
    nombre = models.CharField(max_length=40)
    slug = models.SlugField(max_length=40)

class Entrada(models.Model):
    titulo = models.CharField(max_length=150)
    slug = models.SlugField(max_length=150, unique=True)
    cuerpo = models.TextField()
    etiquetas = models.ManyToManyField(Etiqueta, related_name="entradas")

Cada entrada tendrá unas etiquetas determinadas. Ahora vamos a ver cómo construir una vista que reciba un slug y obtenga la entrada con dicho slug y sus entradas similares.

Partimos de una vista incial en la que seleccionamos una Entrada a partir del slug dado y en caso de no encontrarse alguna devolvemos un error 404:

from django.shortcuts import get_object_or_404

def ver_entrada(request,slug):
    entrada = get_object_or_404(Entrada, slug=slug)
    # ...

A continuación obtenemos en una lista cada id de las etiquetas correspondientes a esa entrada utilizando el método values_list. Esta lista nos servirá para hacer la siguiente query y encontrar otras entradas que también tengan alguna de esas etiquetas:

entrada_etiquetas_ids = entrada.etiquetas.values_list('id', flat=True)

Continuamos seleccionando las entradas de cuyas etiquetas al menos una tiene su id presente en la lista que hemos elaborado mediante Entrada.objects.filter(etiquetas__in=entrada_etiquetas_ids). Obviamente evitamos seleccionar de nuevo la entrada original utilizando exclude(id=entrada.id). También extendemos la query mediante SQL puro (ya que la actual API de Django todavía no facilita realizar este tipo de cosas) contando el número de etiquetas que tiene en común cada una de las entradas con la entrada original, para poder ordenarlas de mayor a menor similitud con la entrada original mediante order_by. Este número lo almacenamos en mismas_etiquetas_count para cada una de las entradas obtenidas:

similares = Entrada.objects.filter(etiquetas__in=entrada_etiquetas_ids).exclude(id=entrada.id).extra(
        select={
            'mismas_etiquetas_count': 'COUNT(%s.etiqueta_id)' % Entrada._meta.get_field('etiquetas').m2m_db_table(),
        },
        order_by=['-mismas_etiquetas_count',]
        )

Al estar realizando un COUNT tenemos que hacer GROUP BY para que el gestor de bases de datos sepa por qué columna(s) agrupar los resultados de la operación COUNT. Por ese motivo seleccionamos la tabla correspondiente al modelo Entrada mediante Entrada._meta.db_table y extendemos el group_by de nuestra query con el atributo id de la tabla de entradas:

entradas_table = Entrada._meta.db_table
similares.query.group_by.extend(['%s.id' % entradas_table])

Ya sólo queda añadir a similares los atributos de la query que queremos obtener para incluir mismas_etiquetas_count y poder saber cuántas etiquetas tiene en común cada entrada similar con la entrada original. Añadimos también todos los atributos que queramos obtener de cada entrada retornada (en nuestro caso son id, slug y titulo):

similares.values_list('id','slug','titulo','mismas_etiquetas_count')

Y ya está. Puede parecer algo complicado pero después de verlo un par de veces te parecerá la mar de normal. A continuación tienes todo el código listo para ser usado:

from django.shortcuts import render_to_response, get_object_or_404

def ver_entrada(request,slug):
    entrada = get_object_or_404(Entrada, slug=slug)

    entrada_etiquetas_ids = entrada.etiquetas.values_list('id', flat=True)

    similares = Entrada.objects.filter(etiquetas__in=entrada_etiquetas_ids).exclude(id=entrada.id).extra(
            select={
                'mismas_etiquetas_count': 'COUNT(%s.etiqueta_id)' % Entrada._meta.get_field('etiquetas').m2m_db_table(),
            },
            order_by=['-mismas_etiquetas_count',]
            )

    entradas_table = Entrada._meta.db_table
    similares.query.group_by.extend(['%s.id' % entradas_table])
    similares.values_list('id','slug','titulo','mismas_etiquetas_count')

Publicado por Antonio Melé el Saturday 10 de January de 2009 | 3 comentarios | Categorías: etiquetas, trucos