迭代 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)的主要内容,如果未能解决你的问题,请参考以下文章

从 Django 模板中的特定相关对象查询数据

模板中的 Django 动态对象过滤问题

django - 改进迭代查询

循环渲染的 Django 模板查询花费了太多时间

Django - 在模板的for循环中迭代数字

如何通过django中的多级反向外键获取相关对象查询集?