2012-12-01 13 views
5

Ich habe diese zwei Klassen für ein Messaging-Modul, an dem ich arbeite. Die Idee ist, dass eine Konversation von einer Gruppe von Teilnehmern (zwei oder mehr) repräsentiert wird. Ich bemühe mich, eine Möglichkeit zu finden, eine Konversation durch die Logik zu suchen, die besagt, dass die gewünschte Konversation, die ich zu finden versuche, folgende Teilnehmer hat. Ich versuchte Conversation.objects.filter(participants__in=[p1, p2]) jedoch tut dies eine OR-Stil-Abfrage, p1 ist ein Teilnehmer oder p2 ist ein Teilnehmer. Ich will p1 und p2 und ... pN ist ein Teilnehmer. Irgendeine Hilfe da draußen?Django M2M QuerySet-Filterung für mehrere Fremdschlüssel

class Conversation(models.Model): 
    date_started = models.DateTimeField(auto_now_add=True) 
    participants = models.ManyToManyField(User) 

    def get_messages(self): 
     return Message.objects.filter(conversation=self) 

    def new_message(self, sender, body): 
     Message.objects.create(sender=sender, body=body, conversation=self) 
     self.save() 


class Message(models.Model): 
    sender = models.ForeignKey(User) 
    body = models.TextField() 
    date = models.DateTimeField(auto_now_add=True) 
    conversation = models.ForeignKey(Conversation) 

    def __unicodde__(self): 
     return body + "-" + sender 

Antwort

3

Ich glaube, Sie nur iterativ filtern müssen. Dies könnte völliger Unsinn sein, wie ich ein bisschen Schlaf beraubt bin, aber vielleicht Methode eines Managers wie so:

class ConversationManager(models.Manager): 
    def has_all(self, participants): 
     # Start with all conversations 
     reducedQs = self.get_query_set() 
     for p in participants: 
      # Reduce to conversations that have a participant "p" 
      reducedQs = reducedQs.filter(participants__id=p.id) 
     return reducedQs 

Generell sollten Sie in der Gewohnheit auf Tabellenebene Abfragen Manager Verfahren zur Herstellung, im Gegensatz zu Klassenmethoden. Auf diese Weise bleibt Ihnen ein Abfrage-Set, das Sie bei Bedarf weiter filtern können.

Inspiriert von der Abfrage aller Gruppen, die einen Mitgliedsnamen Paul in the documentation und this answer haben.

+0

Aktuelle beste Antwort, fehlt didaktische Einführung zu verketteten Filtern auf derselben M2M-Relation: -) – vincent

+0

Ich mag das, es scheint wie der richtige Weg, es nicht eine gehackte Lösung zu tun. Vielen Dank! – Mike

0

Erstens würde ich einen Zusammenhang mit Namen das participants Feld hinzufügen:

participants = models.ManyToManyField(User, related_name='conversations') 

Dies ist nicht notwendig, aber IMO lesbar.

Dann können Sie etwas tun:

p1.conversations.filter(participants__in=p2) 

Dies wird alle p1 Gesprächen zurückkehren, wo p2 auch beteiligt ist.

Ich bin mir nicht sicher über die DB-Effizienz dieser Filtermethode, und vielleicht eine andere Art von Datenbank (vielleicht eine Grafik DB wie Neo4j) ist besser geeignet.

+0

Sie müssen keinen verwandten_namen arg hinzufügen, den Sie direkt mit p1.conversation_set.all() aufrufen könnten. Was passiert bei mehr als 2 Teilnehmern? –

+0

Sie können mehr und mehr 'filter()' -Operationen für den resultierenden Abfrage-Satz durchführen - einen für jeden weiteren Teilnehmer. Ich mag deine Lösung besser, aber warum nicht 'Q' Objekte? – Ohad

+0

Es gibt tatsächlich eine ganze Reihe von Möglichkeiten, entweder man könnte die Teilnehmer einfach durchschleifen und die Konversationen filtrieren oder Q-Filter erstellen und in einer Abfrage übergeben –

0

Eine Möglichkeit, es zu tun könnte Python-Sets werden:

#Get the set of conversation ids for each participant 
    p1_conv_set = set(Converstation.objects.filter(participants = p1).values_list('id', flat=True)) 
    p2_conv_set = set(Converstation.objects.filter(participants = p2).values_list('id', flat=True)) 
    . 
    . 
    pn_conv_set = set(Converstation.objects.filter(participants = pN).values_list('id', flat=True)) 
    #Find the common ids for all participants 
    all_participants_intersection = p1_conv_set & p2_conv_set & ....pN_conv_set 
    #Get all the conversation for all the calculated ids 
    Conversation.objects.filter(id__in = all_participants_intersection) 
2

Wenn Sie mehrmals filter() auf dasselbe verwandte Modell verketten, wird die generierte Abfrage einen zusätzlichen JOIN in derselben Tabelle haben.

So haben Sie: Conversation.objects.filter(participants=p1).filter(participants=p2)

Sie dieses Verhalten, indem man die erzeugte Abfrage bestätigen print Conversation.objects.filter(participants=p1).filter(participants=p2).query

See: https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships

Da es noch recht einfach und effizient ist würde ich vermeiden Python Logik nach die Abfrage, die erfordert, zu viele Daten aus der Datenbank zu bringen und dann erneut durch Iterieren zu filtern.

Verwandte Themen