2014-05-01 9 views
5

Mam pytanie, które próbuję rozwiązać na jeden dzień teraz.Django Prefetch powiązane bez duplikatów ze stołem pośrednim

ze wzorami

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) 
    ... 

Próbuję modelować świat cytatów. ze związkami: jedna o wielu różnych Quote, jedna o wielu numerach Quote o wielu numerach Quote. Problem:

  1. Jak mogę uzyskać wszystkie Tag s, które są zawarte w Source (poprzez zawarte Quote S)?
  2. z minimalnymi możliwymi zapytaniami.
  3. z ilością czasach są one zawarte w tym źródle

Próbowałem naiwny jeden bez pobierania wstępnego związanego, z metodą modelu

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) 

I w szablonie:

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

teraz mam

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

W szablonie nie mam pojęcia, jak poprawnie iterować, aby uzyskać Tag s dla jednego źródła, i jak chciałbym je zliczyć zamiast wymieniać duplikaty tagów.

Odpowiedz

3

To będzie uzyskać wynik w jednym zapytaniu 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 %} 

Uzupełniające testy :)

# 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) 

mam w zasadzie wykorzystywane funkcje annotate i values z ORM. Są bardzo potężne, ponieważ automatycznie wykonują połączenia. Są również bardzo wydajne, ponieważ trafiają do bazy danych tylko raz i zwracają tylko te pola, które zostały określone.

+0

źle przetestować to wkrótce, dzięki do tej pory – niklas

+0

@ user9 Czy to działa tak, jak zamierzałeś? – Pratyush

+0

Nie mogę go uruchomić w zamierzony sposób. lista wartości zawiera wiele wartości źródłowych kilka razy ... (co najmniej {% dla źródła w źródłach%} nie działa). Wyniki nie są pogrupowane według źródeł. – niklas

Powiązane problemy