Django-taggit prefetch_related

时间:2012-10-17 02:00:34

标签: django django-taggit

我正在构建一个基本的时间记录应用程序,我有一个使用django-taggit的待办事项模型。我的Todo模型看起来像这样:

class Todo(models.Model):
    project = models.ForeignKey(Project)
    description = models.CharField(max_length=300)
    is_done = models.BooleanField(default=False)
    billable = models.BooleanField(default=True)
    date_completed = models.DateTimeField(blank=True, null=True)
    completed_by = models.ForeignKey(User, blank=True, null=True)
    tags = TaggableManager()

    def __unicode__(self):
        return self.description

我正在尝试获取项目中所有Todos的唯一标记列表,并且我已设法使用set comprehension使其工作,但是对于项目中的每个Todo,我必须查询数据库以获取标签。我的理解是:

unique_tags = { tag.name.lower() for todo in project.todo_set.all() for tag in todo.tags.all() }

这很好用,但是对于项目中的每个待办事项,它都会运行一个单独的查询来获取所有标记。我想知道是否有任何方法可以做类似prefetch_related的事情,以避免这些重复的查询:

unique_tags = { tag.name.lower() for todo in project.todo_set.all().prefetch_related('tags') for tag in todo.tags.all() }

运行上一代码会给我一个错误:

'tags' does not resolve to a item that supports prefetching - this is an invalid parameter to prefetch_related().

我确实看到有人在这里问了一个非常相似的问题:Optimize django query to pull foreign key and django-taggit relationship然而它看起来并没有得到明确的答案。我希望有人可以帮助我。谢谢!

4 个答案:

答案 0 :(得分:6)

Taggit现在直接在标记字段上支持prefetch_related(版本0.11.0及更高版本,2013-11-25发布)。

this pull request中引入了此功能。在the test case for it中,请注意在使用.prefetch_related('tags')预取标记后,还有0个其他查询可用于列出标记。

答案 1 :(得分:3)

稍微犹豫不决:

ct = ContentType.objects.get_for_model(Todo)
todo_pks = [each.pk for each in project.todo_set.all()]
tagged_items = TaggedItem.objects.filter(content_type=ct, object_id__in=todo_pks)   #only one db query
unique_tags = set([each.tag for each in tagged_items])

解释

我说这是hackish,因为我们必须使用taggit内部使用的TaggedItem和ContentType。

Taggit不为您的特定用例提供任何方法。原因是因为它是通用的。 taggit的意图是可以标记任何模型的任何实例。因此,它使用了ContentType和GenericForeignKey。

taggit内部使用的模型是Tag和TaggedItem。模型标记仅包含标记的字符串表示形式。 TaggedItem是用于将这些标记与任何对象相关联的模型。由于标记应与任何对象相关联,因此TaggedItem使用模型ContentType。

taggit提供的apis如 tags.all() tags.add()等内部使用TaggedItem和此模型上的过滤器为您提供标签对于特定的实例。

因为,您的要求是获取特定对象列表的所有标记,我们必须使用taggit使用的内部类。

答案 2 :(得分:1)

使用django-tagging和方法usage_for_model

 def usage_for_model(self, model, counts=False, min_count=None, filters=None):
    """
    Obtain a list of tags associated with instances of the given
    Model class.

    If ``counts`` is True, a ``count`` attribute will be added to
    each tag, indicating how many times it has been used against
    the Model class in question.

    If ``min_count`` is given, only tags which have a ``count``
    greater than or equal to ``min_count`` will be returned.
    Passing a value for ``min_count`` implies ``counts=True``.

    To limit the tags (and counts, if specified) returned to those
    used by a subset of the Model's instances, pass a dictionary
    of field lookups to be applied to the given Model as the
    ``filters`` argument.
    """

答案 3 :(得分:0)

比akshar的回答略逊一筹,但只是略微......

只要您使用prefetch_related('tagged_items__tag')子句遍历tagged_item关系,就可以使用prefetch_related。不幸的是,todo.tags.all()不会利用预取 - “标记”管理器仍会最终执行自己的查询 - 所以你必须在那里跨越tagged_items关系。这应该做的工作:

unique_tags = { tagged_item.tag.name.lower()
    for todo in project.todo_set.all().prefetch_related('tagged_items__tag')
    for tagged_item in todo.tagged_items.all() }