2014-05-01 10 views
5

Ho una domanda che sto cercando di risolvere per un giorno.Django prefetch non ha riscontrato duplicati con il tavolo intermedio

Con i modelli

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

Sto cercando di modellare il mondo di citazioni. con relazioni: uno Source con molti Quote s, uno Quote con molti Tag s. Il problema è:

  1. Come posso ottenere tutte le Tag s che sono contenuti in un Source (attraverso i contenuti Quote s)?
  2. con le query minime possibili.
  3. con la quantità di volte che sono contenuti in questa fonte

ho provato quello ingenuo senza prefetch correlate, con un metodo di modello di

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) 

E nel modello:

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

ora ho

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

E nel modello non ho idea di come iterare correttamente per ottenere il Tag s per una fonte, e come vorrei andare a contare loro invece di elencare i tag duplicati.

risposta

3

questo otterrà il risultato in una singola query 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 %} 

prove complementari :)

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

praticamente ho utilizzato i annotate e values funzioni dal ORM. Sono molto potenti perché eseguono automaticamente i join. Sono anche molto efficienti perché colpiscono il database una sola volta e restituiscono solo i campi specificati.

+0

mal test presto, grazie ancora – niklas

+0

@ user9 Ha funzionato come previsto? – Pratyush

+0

Non riesco a farlo funzionare nel modo previsto. l'elenco di valori ha più volte il valore di origine ... (almeno {% per l'origine nelle origini%} non funziona ..), i risultati non sono raggruppati più per origine – niklas

Problemi correlati