Pregunta

class Tag(models.Model):
  name = models.CharField(maxlength=100)

class Blog(models.Model):
  name = models.CharField(maxlength=100)
  tags =  models.ManyToManyField(Tag)

Modelos simples solo para hacer mi pregunta.

Me pregunto cómo puedo consultar blogs usando etiquetas de dos maneras diferentes.

  • Entradas de blog etiquetadas con "etiqueta1" o "etiqueta2":Blog.objects.filter(tags_in=[1,2]).distinct()
  • Objetos de blog etiquetados con "etiqueta1" y "etiqueta2": ?
  • Objetos de blog que están etiquetados exactamente con "etiqueta1" y "etiqueta2" y nada más: ??

Etiqueta y Blog solo se usan como ejemplo.

¿Fue útil?

Solución

Podrías usar objetos Q para el n.° 1:

# Blogs who have either hockey or django tags.
from django.db.models import Q
Blog.objects.filter(
    Q(tags__name__iexact='hockey') | Q(tags__name__iexact='django')
)

Creo que las uniones y las intersecciones están un poco fuera del alcance de Django ORM, pero es posible hacerlo.Los siguientes ejemplos son de una aplicación Django llamada etiquetado con django que proporciona la funcionalidad. Línea 346 de models.py:

Para la segunda parte, buscas una unión de dos consultas, básicamente

def get_union_by_model(self, queryset_or_model, tags):
    """
    Create a ``QuerySet`` containing instances of the specified
    model associated with *any* of the given list of tags.
    """
    tags = get_tag_list(tags)
    tag_count = len(tags)
    queryset, model = get_queryset_and_model(queryset_or_model)

    if not tag_count:
        return model._default_manager.none()

    model_table = qn(model._meta.db_table)
    # This query selects the ids of all objects which have any of
    # the given tags.
    query = """
    SELECT %(model_pk)s
    FROM %(model)s, %(tagged_item)s
    WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
      AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
      AND %(model_pk)s = %(tagged_item)s.object_id
    GROUP BY %(model_pk)s""" % {
        'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
        'model': model_table,
        'tagged_item': qn(self.model._meta.db_table),
        'content_type_id': ContentType.objects.get_for_model(model).pk,
        'tag_id_placeholders': ','.join(['%s'] * tag_count),
    }

    cursor = connection.cursor()
    cursor.execute(query, [tag.pk for tag in tags])
    object_ids = [row[0] for row in cursor.fetchall()]
    if len(object_ids) > 0:
        return queryset.filter(pk__in=object_ids)
    else:
        return model._default_manager.none()

Para la parte 3, creo que estás buscando una intersección.Ver línea 307 de modelos.py

def get_intersection_by_model(self, queryset_or_model, tags):
    """
    Create a ``QuerySet`` containing instances of the specified
    model associated with *all* of the given list of tags.
    """
    tags = get_tag_list(tags)
    tag_count = len(tags)
    queryset, model = get_queryset_and_model(queryset_or_model)

    if not tag_count:
        return model._default_manager.none()

    model_table = qn(model._meta.db_table)
    # This query selects the ids of all objects which have all the
    # given tags.
    query = """
    SELECT %(model_pk)s
    FROM %(model)s, %(tagged_item)s
    WHERE %(tagged_item)s.content_type_id = %(content_type_id)s
      AND %(tagged_item)s.tag_id IN (%(tag_id_placeholders)s)
      AND %(model_pk)s = %(tagged_item)s.object_id
    GROUP BY %(model_pk)s
    HAVING COUNT(%(model_pk)s) = %(tag_count)s""" % {
        'model_pk': '%s.%s' % (model_table, qn(model._meta.pk.column)),
        'model': model_table,
        'tagged_item': qn(self.model._meta.db_table),
        'content_type_id': ContentType.objects.get_for_model(model).pk,
        'tag_id_placeholders': ','.join(['%s'] * tag_count),
        'tag_count': tag_count,
    }

    cursor = connection.cursor()
    cursor.execute(query, [tag.pk for tag in tags])
    object_ids = [row[0] for row in cursor.fetchall()]
    if len(object_ids) > 0:
        return queryset.filter(pk__in=object_ids)
    else:
        return model._default_manager.none()

Otros consejos

Los probé con Django 1.0:

Las consultas "o":

Blog.objects.filter(tags__name__in=['tag1', 'tag2']).distinct()

o podrías usar la clase Q:

Blog.objects.filter(Q(tags__name='tag1') | Q(tags__name='tag2')).distinct()

La consulta "y":

Blog.objects.filter(tags__name='tag1').filter(tags__name='tag2')

No estoy seguro acerca del tercero, probablemente necesitarás acceder a SQL para hacerlo.

Por favor, no reinventes la rueda y utilices aplicación de etiquetado django que fue creado exactamente para su caso de uso.Puede realizar todas las consultas que describa y mucho más.

Si necesita agregar campos personalizados a su modelo de etiqueta, también puede consultar mi rama de django-tagging.

Esto te servirá

Blog.objects.filter(tags__name__in=['tag1', 'tag2']).annotate(tag_matches=models.Count(tags)).filter(tag_matches=2)
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top