Wie frage ich ein ManyToManyField ordnungsgemäß nach allen Objekten in einer Liste (oder einem anderen ManyToManyField) ab?
-
12-09-2019 - |
Frage
Ich bin ziemlich ratlos darüber, wie ich am besten eine Django-Abfrage erstellen kann, die prüft, ob alle die Elemente von a ManyToMany
Feld (oder eine Liste) in einem anderen vorhanden sind ManyToMany
Feld.
Als Beispiel habe ich mehrere Person
s, die mehr als eine Spezialität haben können.es gibt auch Job
Es ist so, dass Leute anfangen können, aber sie brauchen einen oder mehrere Specialty
s, um startberechtigt zu sein.
class Person(models.Model):
name = models.CharField()
specialties = models.ManyToManyField('Specialty')
class Specialty(models.Model):
name = models.CharField()
class Job(models.Model):
required_specialties = models.ManyToManyField('Specialty')
Eine Person kann einen Job beginnen nur wenn sie haben alle die Fachkenntnisse, die der Job erfordert.Zur Veranschaulichung: Wir haben drei Spezialgebiete:
- Codierung
- Singen
- Tanzen
Und ich habe eine Job
Dafür sind die Spezialgebiete Gesang und Tanz erforderlich.Eine Person mit den Spezialisierungen Gesang und Tanz kann damit beginnen, eine Person mit den Spezialisierungen Programmieren und Gesang jedoch nicht – da für den Job eine Person erforderlich ist, die sowohl singen als auch tanzen kann.
Jetzt brauche ich also eine Möglichkeit, alle Jobs zu finden, die eine Person annehmen kann.Das war meine Art, es anzugehen, aber ich bin mir sicher, dass es einen eleganteren Ansatz gibt:
def jobs_that_person_can_start(person):
# we start with all jobs
jobs = Job.objects.all()
# find all specialties that this person does not have
specialties_not_in_person = Specialty.objects.exclude(name__in=[s.name for s in person.specialties])
# and exclude jobs that require them
for s in specialties_not_in_person:
jobs = jobs.exclude(specialty=s)
# the ones left should fill the criteria
return jobs.distinct()
Dies liegt an der Verwendung Job.objects.filter(specialty__in=person.specialties.all())
gibt passende Jobs zurück beliebig der Fachgebiete der Person, nicht alle.Bei Verwendung dieser Abfrage würde der Job, der Singen und Tanzen erfordert, für den singenden Codierer angezeigt, was nicht die gewünschte Ausgabe ist.
Ich hoffe, dass dieses Beispiel nicht zu kompliziert ist.Der Grund, warum ich mir darüber Sorgen mache, ist, dass es wahrscheinlich noch viel mehr Spezialitäten im System geben wird, und es scheint nicht der beste Weg zu sein, dies zu erreichen, indem man sie durchschleift.Ich frage mich, ob irgendjemand diesen Juckreiz lindern könnte!
Lösung
Eine andere Idee
Ok, ich schätze, ich hätte das zu der anderen Antwort hinzufügen sollen, aber als ich damit anfing, schien es, als würde es eine andere Richtung einschlagen, haha
Keine Notwendigkeit zu iterieren:
person_specialties = person.specialties.values_list('pk', flat=True)
non_specialties = Specialties.objects.exclude(pk__in=person_specialties)
jobs = Job.objects.exclude(required_specialties__in=non_specialties)
Notiz: Ich weiß nicht genau, wie schnell das ist.Vielleicht sind Sie mit meinen anderen Vorschlägen besser dran.
Auch:Dieser Code ist ungetestet
Andere Tipps
Ich glaube, Sie bei Verwendung aussehen sollte values_list die Person, die Spezialitäten zu bekommen
Ersetzen Sie:
[s.name for s in person.specialties]
mit:
person.specialties.values_list('name', flat=True)
Das gibt Ihnen eine einfache Liste (dh. [ 'Spec1', 'spec2', ...]), die Sie wieder verwenden können. Und die SQL-Abfrage in der bg verwendet wird auch schneller sein, weil es nur wählt ‚name‘ stattdessen eine select *
tun die ORM-Objekte zu füllen
Sie können auch eine Geschwindigkeitsverbesserung durch Filterung Arbeitsplätze erhalten, dass die Person auf jeden Fall nicht durchführen kann:
so ersetzen:
jobs = Job.objects.all()
mit (2 Abfragen - arbeitet für django 1.0 +)
person_specialties = person.specialties.values_list('id', flat=True)
jobs = Job.objects.filter(required_specialties__id__in=person_specialties)
oder mit (1 Abfrage - arbeitet für django1.1 +)
jobs = Job.objects.filter(required_specialties__in=person.specialties.all())
Sie können auch eine Verbesserung durch die Verwendung select_related () erhalten auf Ihre Jobs / Person Anfragen (da sie einen Fremdschlüssel haben, die Sie verwenden)