我正在构建一个基本的时间记录应用程序,我有一个使用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然而它看起来并没有得到明确的答案。我希望有人可以帮助我。谢谢!
答案 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() }