União e Intersect no Django

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

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

Modelos simples apenas para fazer a minha pergunta.

Eu me pergunto como posso consultar blogs usando tags de duas maneiras diferentes.

  • entradas de blog que o tag tag1 ou tag2: Blog.objects.filter(tags_in=[1,2]).distinct()
  • Objetos de blog que estão marcados com tag1 e tag2: ?
  • Objetos de blog que estão marcados com exatamente tag1 e tag2 e nada mais: ??

Tag e Blog é usado apenas para um exemplo.

Publicado 20/09/2008 em 14:46
fonte usuário
Em outras línguas...                            


4 respostas

votos
21

Você pode usar objetos Q para o # 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')
)

Uniões e interseções, creio eu, são um pouco fora do escopo do Django ORM, mas a sua possível para estes. Os exemplos que se seguem são de uma aplicação Django chamado chamado django codificação que fornece a funcionalidade. Linha 346 do models.py :

Para a parte dois, você está procurando uma união de duas consultas, basicamente,

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 a parte # 3 Eu acredito que você está procurando um cruzamento. Ver linha 307 do models.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()
Respondeu 20/09/2008 em 16:01
fonte usuário

votos
16

Eu testei estes para fora com Django 1.0:

Os "ou" consultas:

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

ou você pode usar a classe Q:

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

O "e" consulta:

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

Eu não tenho certeza sobre o terceiro, você provavelmente vai precisar cair para SQL para fazê-lo.

Respondeu 20/09/2008 em 16:33
fonte usuário

votos
9

Por favor, não reinventar a roda e usar aplicação de etiquetagem django que foi feito exatamente para o seu caso de uso. Ele pode fazer todas as consultas que você descreve, e muito mais.

Se você precisa adicionar campos personalizados para o seu modelo de Tag, você também pode dar uma olhada no meu ramo de django-tagging .

Respondeu 21/09/2008 em 08:03
fonte usuário

votos
5

Isto irá fazer o truque para você

Blog.objects.filter(tags__name__in=['tag1', 'tag2']).annotate(tag_matches=models.Count(tags)).filter(tag_matches=2)
Respondeu 05/01/2011 em 13:45
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more