Django ORM:选择相关集
假设我有两个模型:
class Poll(models.Model):
category = models.CharField(u"Category", max_length = 64)
[...]
class Choice(models.Model):
poll = models.ForeignKey(Poll)
[...]
给定一个投票对象,我可以用以下方式查询它的选项:
poll.choice_set.all()
但是,有没有什么工具函数可以从一组投票中查询所有选项呢?
其实,我想要的功能类似于下面这个(不过这个功能并不支持,我也不想知道怎么实现):
polls = Poll.objects.filter(category = 'foo').select_related('choice_set')
for poll in polls:
print poll.choice_set.all() # this shouldn't perform a SQL query at each iteration
我写了一个(很丑的)函数来帮我实现这个功能:
def qbind(objects, target_name, model, field_name):
objects = list(objects)
objects_dict = dict([(object.id, object) for object in objects])
for foreign in model.objects.filter(**{field_name + '__in': objects_dict.keys()}):
id = getattr(foreign, field_name + '_id')
if id in objects_dict:
object = objects_dict[id]
if hasattr(object, target_name):
getattr(object, target_name).append(foreign)
else:
setattr(object, target_name, [foreign])
return objects
这个函数的使用方法如下:
polls = Poll.objects.filter(category = 'foo')
polls = qbind(polls, 'choices', Choice, 'poll')
# Now, each object in polls have a 'choices' member with the list of choices.
# This was achieved with 2 SQL queries only.
有没有什么更简单的方法是Django已经提供的?或者至少,有没有一个代码片段可以更好地完成同样的事情。
你们通常是怎么处理这个问题的呢?
4 个回答
我觉得你想表达的是,“我想要一组投票的所有选项。”如果是这样的话,可以试试这个:
polls = Poll.objects.filter(category='foo')
choices = Choice.objects.filter(poll__in=polls)
时间过去了,这个功能现在在Django 1.4中可用了,主要是通过引入了一个叫做 prefetch_related() 的查询集功能。这个功能实际上做的事情和之前建议的 qbind
函数是一样的。也就是说,它会执行两个查询,然后在Python中进行连接,但现在这个过程是由ORM来处理的。
原来的查询请求现在变成了:
polls = Poll.objects.filter(category = 'foo').prefetch_related('choice_set')
在下面的代码示例中,可以看到 polls
查询集可以用来获取每个 Poll
对应的所有 Choice
对象,而不需要再去数据库查询:
for poll in polls:
for choice in poll.choice_set:
print choice
更新: 从Django 1.4开始,这个功能已经内置了:请查看prefetch_related。
首先,别浪费时间去写像qbind这样的东西,直到你已经写出了一个能正常工作的应用程序,分析过它的性能,并证明N个查询确实是你数据库和负载场景中的一个性能问题。
但也许你已经做过这些了。那么第二个建议是:qbind()能满足你的需求,但如果把它放在一个自定义的QuerySet子类中,并配上一个返回这个自定义QuerySet实例的Manager子类,那就更符合Django的风格了。理想情况下,你甚至可以让它们变得通用,可以在任何反向关系中重复使用。这样你就可以做类似这样的事情:
Poll.objects.filter(category='foo').fetch_reverse_relations('choices_set')
关于Manager/QuerySet技术的一个例子,可以参考这个代码片段,它解决了类似的问题,但针对的是通用外键,而不是反向关系。将你的qbind()函数的核心部分与那里的结构结合起来,应该不难做出一个很好的解决方案。