2014-05-01 66 views
5

我有一個問題,我現在想解決一個問題。Django prefetch與中間表無關重複

隨着車型

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 s,一個Quote有許多Tag s。 問題是:

  1. 我如何獲得包含在一個Source(通過包含Quote S)所有Tag S'
  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) 

而且在模板:

{% for tag in source.source_tags|slice:":5" %} 
    source.quote 
{% endfor %} 

現在我有

sources = Source.objects.all().prefetch_related('quote_set__tags') 

而在模板中,我不知道如何正確迭代以獲得Tag的一個來源,以及如何計算它們而不是列出重複標籤。

回答

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) 

我已經基本使用了annotatevalues功能從ORM。它們非常強大,因爲它們會自動執行連接。它們也是非常有效的,因爲它們只訪問一次數據庫,並且只返回指定的那些字段。

+0

很快就對它進行測試,至今爲止謝謝 – niklas

+0

@ user9它是否按照您的預期工作? – Pratyush

+0

我無法以預期的方式讓它工作。值列表中的每個源值都是多次...(至少{%for source in source%}不起作用。),結果不再按源進行分組 – niklas