Итерация по связанным объектам в Django: цикл по запросу или использование однострочного select_related (или prefetch_related)

У меня есть приложение для рассылки новостей, в котором у каждого бюллетеня есть несколько статей. Я хочу отобразить сводную страницу в Интернете, в которой перечислены год выпуска, тома и метки, а затем в неупорядоченном списке отображаются все статьи в выпуске. Я совершенно новый для 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() queryset

Я работаю, используя первый вариант:

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() два списка вместе, если есть более быстрый способ.

ТИА.

Ответы

Ответ 1

Подход, который вы сейчас выполняете, будет тяжело неэффективным, потому что это приведет к 1 + N числу запросов. То есть, 1 для запроса всех ваших информационных бюллетеней, а затем 1 за каждый раз, когда вы оцениваете результаты n.article_set.all(). Итак, если у вас есть 100 объектов Newletter в этом первом запросе, вы будете делать 101 запрос.

Это отличная причина для использования prefetch_related. Это приведет только к двум запросам. Один для получения информационных бюллетеней, а 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 %}