Django预取与中间表没有重复

nik*_*las 5 django django-orm

我有一个问题,我现在想解决一天.

随着模型

class Quote(models.Model):
    text = models.TextField()
    source = models.ForeignKey(Source)
    tags = models.ManyToManyField(Tag)
    ...

class Source(models.Model):
    title = models.CharField(max_length=100)
    ...

class Tag(models.Model):
    name = models.CharField(max_length=30,unique=True)
    slug = models.SlugField(max_length=40,unique=True)
    ...
Run Code Online (Sandbox Code Playgroud)

我试图模拟引号的世界.与人际关系:一个Source有很多Quotes,一个Quote有很多Tags.问题是:

  1. 如何获取所有Tag包含在A S Source(通过载Quote县)?
  2. 尽可能少的查询.
  3. 它们包含在该来源中的次数

我用模型方法尝试了没有预取相关的天真的

def source_tags(self):
    tags = Tag.objects.filter(quote__source__id=self.id).distinct().annotate(usage_count=Count('quote'))
    return sorted(tags, key=lambda tag:-tag.usage_count)
Run Code Online (Sandbox Code Playgroud)

在模板中:

{% for tag in source.source_tags|slice:":5" %}
    source.quote
{% endfor %}
Run Code Online (Sandbox Code Playgroud)

我现在有

sources = Source.objects.all().prefetch_related('quote_set__tags')
Run Code Online (Sandbox Code Playgroud)

在模板中我不知道如何正确迭代以获取Tag一个源的s,以及我如何计算它们而不是列出重复的标签.

Pra*_*ush 3

这将在单个 SQL 查询中获得结果:

# views.py
from django.db.models import Count
from .models import Source


def get_tag_count():
    """
    Returns the count of tags associated with each source
    """
    sources = Source.objects.annotate(tag_count=Count('quote__tags')) \
                         .values('title', 'quote__tags__name', 'tag_count') \
                         .order_by('title')
    # Groupe the results as
    # {source: {tag: count}}
    grouped = {}
    for source in sources:
        title = source['title']
        tag = source['quote__tags__name']
        count = source['tag_count']
        if not title in grouped:
            grouped[title] = {}
        grouped[title][tag] = count
    return grouped



# in template.html

{% for source, tags in sources.items %}

    <h3>{{ source }}</h3>

    {% for tag, count in tags.items %}
        {% if tag %}
            <p>{{ tag }} : {{ count }}</p>
        {% endif %}
    {% endfor %}

{% endfor %}
Run Code Online (Sandbox Code Playgroud)

补充测试:)

# tests.py
from django.test import TestCase
from .models import Source, Tag, Quote
from .views import get_tag_count


class SourceTags(TestCase):

    def setUp(self):
        abc = Source.objects.create(title='ABC')
        xyz = Source.objects.create(title='XYZ')

        inspire = Tag.objects.create(name='Inspire', slug='inspire')
        lol = Tag.objects.create(name='lol', slug='lol')

        q1 = Quote.objects.create(text='I am inspired foo', source=abc)
        q2 = Quote.objects.create(text='I am inspired bar', source=abc)
        q3 = Quote.objects.create(text='I am lol bar', source=abc)
        q1.tags = [inspire]
        q2.tags = [inspire]
        q3.tags = [inspire, lol]
        q1.save(), q2.save(), q3.save()

    def test_count(self):
        # Ensure that only 1 SQL query is done
        with self.assertNumQueries(1):
            sources = get_tag_count()
            self.assertEqual(sources['ABC']['Inspire'], 3)
            self.assertEqual(sources['ABC']['lol'], 1)
Run Code Online (Sandbox Code Playgroud)

我基本上使用了ORM 中的annotate和函数。values它们非常强大,因为它们自动执行连接。它们也非常高效,因为它们仅访问数据库一次,并且仅返回指定的字段。