Django-taggit prefetch_related

bb8*_*b89 7 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
Run Code Online (Sandbox Code Playgroud)

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

unique_tags = { tag.name.lower() for todo in project.todo_set.all() for tag in todo.tags.all() }
Run Code Online (Sandbox Code Playgroud)

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

unique_tags = { tag.name.lower() for todo in project.todo_set.all().prefetch_related('tags') for tag in todo.tags.all() }
Run Code Online (Sandbox Code Playgroud)

运行前面的代码给我错误:

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

我确实看到有人在这里问了一个非常相似的问题:优化django查询以获取外键和django-taggit关系,但它看起来并不像是得到了明确的答案.我希望有人可以帮助我.谢谢!

Mec*_*ail 6

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

拉取请求中引入了此功能.在测试用例中,请注意在使用预取标记后.prefetch_related('tags'),还有0个用于列出标记的其他查询.


Aks*_*aaj 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])
Run Code Online (Sandbox Code Playgroud)

解释

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

Taggit 不为您的特定用例提供任何方法。原因是因为它是通用的。taggit 的目的是可以标记任何模型的任何实例。因此,它利用 ContentType 和 GenericForeignKey 来实现这一点。

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

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

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