迭代 Django 中的相关对象:循环查询集或使用单行 select_related(或 prefetch_related)
Posted
技术标签:
【中文标题】迭代 Django 中的相关对象:循环查询集或使用单行 select_related(或 prefetch_related)【英文标题】:Iterating over related objects in Django: loop over query set or use one-liner select_related (or prefetch_related) 【发布时间】:2012-08-18 00:18:56 【问题描述】:我有一个时事通讯应用程序,其中时事通讯在每个问题中包含多篇文章。我想在线显示一个摘要页面,其中列出了通讯年份、数量和标签,然后在一个无序列表中显示该问题中的所有文章。我对 Django 很陌生,所以我正在尝试确定最好的方法。
我已经定义了模型(只是相关部分):
Models.py
:
class Newsletter(models.Model):
volume = models.ForeignKey(Volume)
year = models.IntegerField()
season = models.CharField(max_length=6, choices=VOLUME_SEASON)
label = models.CharField(max_length=20)
number = models.IntegerField()
class Article(models.Model):
newsletter = models.ForeignKey(Newsletter)
section = models.ForeignKey(Section)
title = models.CharField(max_length=200)
我想在网络上看到的样子:
<h2>Spring 2012</h2>
<p>Volume 14, Number 1</p>
<ul>
<li>Foo</li>
<li>Bar</li>
<li>Baz</li>
</ul>
<h2>Winter 2011</h2>
<p>Volume 13, Number 4</p>
<ul>
<li>Boffo</li>
</ul>
很简单。但是,我对撰写观点的最佳方式感到困惑。是否使用:
我zip()
然后在模板中迭代的两个列表
使用select_related()
查询集
使用prefetch_related()
查询集
我使用第一个选项让它工作:
Views.py
:
from django.shortcuts import render_to_response, get_object_or_404
from www.apps.newsletter.models import Newsletter, Article
def index(request):
article_group = []
newsletter = Newsletter.objects.all().order_by('-year', '-number')
for n in newsletter:
article_group.append(n.article_set.all())
articles_per_newsletter = zip(newsletter, article_group)
return render_to_response('newsletter/newsletter_list.html',
'newsletter_list': articles_per_newsletter)
然后使用以下模板进行渲染:
Newsletter_list.html
:
% block content %
% for newsletter, articles in newsletter_list %
<h2> newsletter.label </h2>
<p>Volume newsletter.volume , Number newsletter.number </p>
<p> newsletter.article </p>
<ul>
% for a in articles %
<li> a.title </li>
% endfor %
</ul>
% endfor %
% endblock %
非常简单,但是由于我对 Django 还很陌生,所以我想知道我正在做的事情在其强大的 ORM 方面是否完全低效。如果有更快的方法,我希望不必即时制作列表然后zip()
将两个列表放在一起。
TIA。
【问题讨论】:
【参考方案1】:您现在正在执行的方法将严重效率低下,因为它会导致 1+N 数量的查询。也就是说,1 用于查询所有时事通讯,然后 1 用于每次评估 n.article_set.all()
结果。因此,如果您在第一个查询中有 100 个 Newletter 对象,您将执行 101 个查询。
这是使用prefetch_related
的绝佳理由。它只会导致 2 个查询。 1个获取Newsletters,1个批量获取相关文章。尽管您仍然完全可以继续使用zip
来组织它们,但它们已经被缓存了,所以实际上您可以直接将查询传递给模板并在其上循环。 :
查看
newsletters = Newsletter.objects.prefetch_related('article_set').all()\
.order_by('-year', '-number')
return render_to_response('newsletter/newsletter_list.html',
'newsletter_list': newsletters)
模板
% block content %
% for newsletter in newsletter_list %
<h2> newsletter.label </h2>
<p>Volume newsletter.volume , Number newsletter.number </p>
<p> newsletter.article </p>
<ul>
% for a in newsletter.article_set.all %
<li> a.title </li>
% endfor %
</ul>
% endfor %
% endblock %
【讨论】:
太棒了!我以为我是在以一种低效的方式去做。我刚刚尝试了您的代码示例,不幸的是 newsletter.article_set.all 上的 for 循环没有返回任何内容? % for a in newsletter.articles_set.all % 知道我做错了什么吗?谢谢! 你写的articles_set.all
应该是article_set.all
@tatlar。请参阅following relationships backward 了解更多信息。
咳咳。道歉。非常感谢你们!
@jdi 如果我想要每个时事通讯的最新 5 篇文章,是否有类似的解决方案?
@Acute - 使用slice 模板标签 % for a in newsletter.article_set.all|slice:"5" % 将获得前五个结果。您需要确保已在视图中使用 order_by 对查询集进行了排序。以上是关于迭代 Django 中的相关对象:循环查询集或使用单行 select_related(或 prefetch_related)的主要内容,如果未能解决你的问题,请参考以下文章