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 Sábado 10 de Enero de 2009
Compártelo:
| Categorías:
etiquetas,
trucos
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 ...
En múltiples ocasiones nos gustaría extender el modelo User para que incluyera otros campos y funciones. La manera "oficial" de hacer esto (la mostrada ...
Muchas veces deseamos acceder a los settings de nuestro proyecto desde alguna de nuestras plantillas. Lo ideal es crear un context processor que nos ...
En ocasiones nos interesa trabajar con subdominios en nuestros proyectos Django. Para ello podemos utilizar un sencillo middleware para subdominios que podemos encontrar en ...
En Django 1.1 ya es posible utilizar funciones de agregación (SUM, AVG, MIN, ...) sin necesidad de utilizar SQL puro.
http://docs.djangoproject.com/en/dev/topics/db/aggregation/#topics-db-aggregation
¡Cierto Fran! Mucho más fácil de hacer con la versión de desarrollo que con la estable 1.0.2. Cuando tenga tiempo escribo otro post sobre cómo hacerlo con las funciones de agregación de la versión de desarrollo.
Lo mismo utilizando las funciones de agregación: http://django.es/blog/descubriendo-objetos-similares-por-etiquetas-2/
Suscríbete a nuestro feed RSS y al feed de la comunidad para estar al tanto de todo lo que ocurre entorno a Django.
Tú también puedes escribir en éste blog. Para hacerlo basta con que nos digas sobre qué quieres escribir un artículo relacionado con Django.
Utilizar un formulario para modificar 2 modelos
Descubriendo objetos similares por sus etiquetas