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

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

简单的模型只是为了问我的问题。

我想知道如何以两种不同的方式使用标签查询博客。

  • 带有“tag1”或“tag2”标签的博客条目: Blog.objects.filter(tags_in=[1,2]).distinct()
  • 用“tag1”和“tag2”标记的博客对象:
  • 完全用“tag1”和“tag2”标记的博客对象,没有别的:??

标记和博客仅用于示例。

4

4 回答 4

22

您可以将 Q 对象用于#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')
)

我相信联合和交集有点超出了 Django ORM 的范围,但它可以做到这些。以下示例来自一个名为django-tagging的 Django 应用程序,该应用程序提供了该功能。models.py 的第 346 行

对于第二部分,您正在寻找两个查询的联合,基本上

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()

对于第 3 部分,我相信您正在寻找一个十字路口。见models.py 的第 307 行

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()
于 2008-09-20T15:01:12.527 回答
16

我已经用 Django 1.0 测试了这些:

“或”查询:

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

或者你可以使用 Q 类:

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

“和”查询:

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

我不确定第三个,你可能需要使用 SQL 来完成它。

于 2008-09-20T15:33:55.333 回答
9

请不要重新发明轮子并使用专门为您的用例制作的django-tagging 应用程序。它可以执行您描述的所有查询,等等。

如果您需要在 Tag 模型中添加自定义字段,还可以查看我的 django-tagging 分支

于 2008-09-21T07:03:07.160 回答
5

这将为您解决问题

Blog.objects.filter(tags__name__in=['tag1', 'tag2']).annotate(tag_matches=models.Count(tags)).filter(tag_matches=2)
于 2011-01-05T12:45:41.700 回答