2008-11-02 6 views
13

Für meine Django App habe ich Ereignisse, Bewertungen und Benutzer. Bewertungen beziehen sich auf Ereignisse und Benutzer über Fremdschlüssel. Beim Anzeigen einer Liste von Ereignissen möchte ich die Bewertungen des Ereignisses durch eine user_id filtern, damit ich weiß, ob ein Ereignis vom Benutzer bewertet wurde.Django - Filtern nach verwandten Objekten

Wenn ich tun:

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

(request.user.id gibt die user_id des aktuellen angemeldeten Benutzers) ... dann habe ich nur die Ereignisse erhalten, die durch den Benutzer und nicht den gesamten bewertet werden Liste der Ereignisse.

Was ich brauche, kann durch die benutzerdefinierte SQL erzeugt werden:

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

Gibt es einen einfacheren Weg, so muss ich nicht benutzerdefinierte SQL verwenden?

Antwort

15

Die filter-Methode dient zum Filtern, welche Objekte basierend auf den angegebenen Kriterien zurückgegeben werden, also ist es hier nicht erwünscht. Eine Option besteht darin, eine zweite Abfrage durchzuführen, um alle Bewertungen für gegebene Event Objekte für den aktuellen User abzurufen.

Modelle:

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

Ausblick:

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], 
} 

Vorlage:

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

Um Django optimal nutzen zu können, müssen Sie vermeiden, Joins zu versuchen.

Ein "linker äußerer Join" ist eigentlich eine Liste von Objekten mit optionalen Beziehungen.

Es ist einfach eine Liste von Ereignissen, Event.objects.all(). Einige Event-Objekte haben eine Bewertung, andere nicht.

Sie erhalten die Liste der Ereignisse in Ihrer Ansicht. Sie behandeln die optionalen Beziehungen in Ihrer Vorlage.

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

ist ein Startpunkt.

+0

rating_set ist der Manager, der die Beziehung zur Bewertung verwaltet, also wird es immer existieren. Selbst wenn Sie eine .count- oder .all-Datei hinzugefügt hätten, würden Sie dadurch Details zu allen Bewertungen erhalten, nicht nur zu denen für den aktuellen Benutzer. –

+0

Es ist nicht meine Frage :-) e.rating_set wäre ein django.db.models.fields.related.RelatedManager, also würde {% if e.rating_set%} immer passieren. Wenn Sie versuchen, es zu iterieren, erhalten Sie TypeError: Das Objekt 'RelatedManager' ist nicht iterierbar.Sie müssten e.rating_set.all verwenden, um Ihr beabsichtigtes Verhalten zu erhalten. –

4

Zusätzlich zu S.Lotts Vorschlag können Sie select_related() verwenden, um die Anzahl der Datenbankabfragen zu begrenzen. Andernfalls führt Ihre Vorlage bei jedem Durchlauf des Ereignisses eine Abfrage durch.

Der Parameter depth ist nicht erforderlich, aber wenn Ihre anderen Modelle zusätzliche Fremdschlüssel haben, wird die Anzahl der Joins eingeschränkt.

0

Ich glaube, Sie, so etwas zu tun haben.

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