我有一个问题,我现在想解决一天。
使用模型
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)
...
我正在尝试模拟引号世界。关系:一个Source
有Quote
个,Quote
有Tag
个。
问题是:
Tag
中包含的所有Source
(通过包含的Quote
)?我尝试过没有预取相关的天真的,使用模型方法
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)
在模板中:
{% for tag in source.source_tags|slice:":5" %}
source.quote
{% endfor %}
现在我有了
sources = Source.objects.all().prefetch_related('quote_set__tags')
在模板中我不知道如何正确迭代以获取一个来源的Tag
,以及我如何计算它们而不是列出重复的标签。
答案 0 :(得分: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 %}
补充测试:)
# 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)
我基本上使用了ORM中的annotate
和values
函数。它们非常强大,因为它们会自动执行连接。它们也非常有效,因为它们只访问数据库一次,并且只返回指定的字段。