2012-12-01 22 views
5

Mam te dwie klasy dla modułu wiadomości, nad którym pracuję. Chodzi o to, że rozmowę reprezentuje grupa uczestników (dwóch lub więcej). Staram się znaleźć sposób na wytropienie rozmowy za pomocą logiki, która mówi, że pożądana rozmowa, którą próbuję znaleźć, ma następujących uczestników. Próbowałem wykonać próbkę w stylu OR, p1 jest uczestnikiem lub p2 jest uczestnikiem. Chcę, aby p1 i p2 oraz ... pN był uczestnikiem. Jakaś pomoc tam jest?Django M2M QuerySet filtrowanie wielu kluczy obcych

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 

Odpowiedz

3

Chyba wystarczy iteracyjnie filtrować. Może to być bzdura jak jestem nieco pozbawiony snu, ale może to metoda menedżer tak:

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 

Generalnie powinieneś dostać w zwyczaju podejmowania metod menedżera zapytań na poziomie tabeli, w przeciwieństwie do metody klasowe. A robiąc to w ten sposób, otrzymujesz zestaw zapytań, który możesz filtrować dalej, jeśli zajdzie taka potrzeba.

Zainspirowany zapytaniem wszystkich grup, które mają nazwę użytkownika Paul w the documentation i this answer.

+0

Aktualna najlepsza odpowiedź, po prostu brakuje dydaktycznego wprowadzenia do przykutych filtrów w tej samej relacji M2M: -) – vincent

+0

Podoba mi się to, wydaje się, że to właściwy sposób, aby to zrobić, a nie zhakowane rozwiązanie. Dzięki! – Mike

0

Po pierwsze, chciałbym dodać nazwę związaną z pola participants:

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

To nie jest konieczne, ale bardziej czytelny IMO.

Następnie można zrobić coś takiego:

p1.conversations.filter(participants__in=p2) 

To wszystko wróci rozmowy p1 p2 gdzie uczestniczy również.

Nie jestem pewien co do wydajności DB tej metody filtrowania, i być może przy użyciu jakiejś innej bazy danych (może DB wykres taki jak Neo4j) jest bardziej odpowiedni.

+0

Nie trzeba dodawać powiązanej nazwy, z której można uzyskać bezpośredni dostęp za pomocą funkcji p1.conversation_set.all(). Co się dzieje, gdy jest więcej niż 2 uczestników? –

+0

Możesz wykonać więcej operacji 'filter()' na wynikowym zestawie zapytań - po jednym na każdego dodatkowego uczestnika. Bardziej lubię twoje rozwiązanie, ale dlaczego nie używać obiektów 'Q'? – Ohad

+0

Istnieje wiele sposobów na zrobienie tego albo można po prostu przepuścić uczestników i filtrować rozmowy lub utworzyć filtr Q i przekazać go w zapytaniu –

0

Jednym ze sposobów może to być za pomocą zestawów Pythona:

#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

Jeśli łańcuch kilka razy filtr() w tym samym modelu powiązanego, wygenerowane zapytanie będzie miało dodatkowe DOŁĄCZ do tej samej tabeli.

Więc trzeba: Conversation.objects.filter(participants=p1).filter(participants=p2)

Można potwierdzić to zachowanie patrząc na wygenerowanym zapytaniu print Conversation.objects.filter(participants=p1).filter(participants=p2).query

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

Ponieważ jest to nadal dość prosty i skuteczny chciałbym uniknąć stosując logikę Pythona po zapytanie, które wymagałoby pobrania zbyt dużej ilości danych z bazy danych, a następnie ponownego filtrowania przez iterację.

Powiązane problemy