2008-11-02 16 views
13

Per la mia app Django ho eventi, valutazioni e utenti. Le classificazioni sono correlate agli eventi e agli utenti tramite chiavi esterne. Quando si visualizza un elenco di eventi, desidero filtrare le classificazioni dell'evento da un id_utente in modo da sapere se un evento è stato valutato dall'utente.Django - filtraggio su oggetti correlati

Se lo faccio:

event_list = Event.objects.filter(rating__user=request.user.id) 

(request.user.id dà l'user_id della corrente utente connesso) ... poi ho solo gli eventi che sono valutati dall'utente e non l'intera lista di eventi.

Che cosa ho bisogno può essere generata attraverso l'SQL personalizzato:

SELECT * 
FROM `events_event` 
LEFT OUTER JOIN (
    SELECT * 
    FROM `events_rating` 
    WHERE user_id = ## 
) AS temp 
ON events_event.id = temp.user_id 

c'è un modo più semplice, quindi non c'è bisogno di utilizzare SQL personalizzato?

risposta

15

Il metodo filter serve per filtrare quali oggetti vengono restituiti in base ai criteri specificati, quindi non è quello che si desidera qui. Un'opzione consiste nel fare una seconda query per recuperare tutte le valutazioni per determinati oggetti Event per l'attuale User.

Modelli:

import collections 

from django.db import models 

class RatingManager(models.Manager): 
    def get_for_user(self, events, user): 
     ratings = self.filter(event__in=[event.id for event in events], 
           user=user) 
     rating_dict = collections.defaultdict(lambda: None) 
     for rating in ratings: 
      rating_dict[rating.event_id] = rating 
     return rating_dict 

class Rating(models.Model): 
    # ... 
    objects = RatingManager() 

Vista:

events = Event.objects.all() 
user_ratings = Rating.objects.get_for_user(events, request.user) 
context = { 
    'events': [(event, user_ratings[event.id]) for event in events], 
} 

Template:

{% for event, user_rating in events %} 
    {% if user_rating %} ... {% endif %} 
{% endfor %} 
1

Per utilizzare al meglio Django, è necessario evitare di provare a fare join.

Un "join esterno sinistro" è in realtà un elenco di oggetti con relazioni opzionali.

È semplicemente un elenco di eventi, Event.objects.all(). Alcuni oggetti Event hanno una valutazione, altri no.

Si ottiene l'elenco degli eventi nella visualizzazione. Gestisci le relazioni opzionali nel tuo modello.

{% for e in event_list %} 
    {{ e }} 
    {% if e.rating_set.all %}{{ e.rating_set }}{% endif %} 
{% endfor %} 

è un punto di partenza.

+0

rating_set è il gestore che gestisce la relazione alla valutazione, quindi esisterà sempre. Anche se hai aggiunto un .count o .all ad esso, ciò ti darebbe i dettagli per tutte le valutazioni, non solo quelle per l'utente corrente. –

+0

Non è una mia domanda :-) e.rating_set sarebbe un django.db.models.fields.related.RelatedManager, quindi {% se e.rating_set%} passerebbe sempre. Cercando di eseguire l'iterazione si otterrebbe TypeError: l'oggetto 'RelatedManager' non è iterabile.Dovresti usare e.rating_set.all per ottenere il tuo comportamento previsto. –

4

Oltre al suggerimento di S.Lott, è possibile utilizzare select_related() per limitare il numero di query del database; in caso contrario, il modello eseguirà una query sul passaggio di ogni evento nel ciclo.

Event.objects.all().select_related(depth=1) 

Il parametro profondità non è necessaria, ma se gli altri modelli hanno le chiavi esterne aggiuntive che limiterà il numero di giunzioni.

0

io penso che si debba fare qualcosa di simile.

events=Event.objects.filter(rating__user=request.user.id) 
ratings='(select rating from ratings where user_id=%d and event_id=event_events.id '%request.user.id 
events=events.extra(select={'rating':ratings})