Comment interroger correctement un ManyToManyField pour tous les objets d'une liste (ou un autre ManyToManyField) ?

StackOverflow https://stackoverflow.com/questions/1841931

Question

Je suis plutôt perplexe quant à la meilleure façon de créer une requête Django qui vérifie si tous les éléments d'un ManyToMany champ (ou une liste) sont présents dans un autre ManyToMany champ.

A titre d'exemple, j'en ai plusieurs Persons, qui peuvent avoir plus d'une spécialité.Il y a aussi Jobs que les gens peuvent commencer, mais ils nécessitent un ou plusieurs Specialtys pour être éligible pour être démarré.

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

Une personne peut commencer un emploi seulement si elles ont tous les spécialités que requiert le poste.Ainsi, toujours à titre d’exemple, nous avons trois spécialités :

  • Codage
  • En chantant
  • Dansant

Et j'ai un Job cela nécessite les spécialités Chant et Danse.Une personne ayant des spécialités en chant et en danse peut le commencer, mais une autre avec des spécialités en codage et en chant ne le peut pas, car le poste exige une personne capable à la fois de chanter et de danser.

Alors maintenant, j’ai besoin d’un moyen de trouver tous les emplois qu’une personne peut occuper.C'était ma façon de résoudre le problème, mais je suis sûr qu'il existe une approche plus élégante :

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

C'est parce qu'en utilisant Job.objects.filter(specialty__in=person.specialties.all()) renverra les emplois qui correspondent n'importe lequel des spécialités de la personne, pas toutes.En utilisant cette requête, la tâche qui nécessite Chant et Danse apparaîtrait pour le codeur de chant, ce qui n'est pas le résultat souhaité.

J'espère que cet exemple n'est pas trop compliqué.La raison pour laquelle cela m'inquiète est que les spécialités du système seront probablement beaucoup plus nombreuses, et les parcourir en boucle ne semble pas être le meilleur moyen d'y parvenir.Je me demande si quelqu'un pourrait gratter cette démangeaison !

Était-ce utile?

La solution

Une autre idée

Ok, je suppose que j'aurais dû ajouter ceci à l'autre réponse, mais quand j'ai commencé, il semblait que ça allait prendre une direction différente haha

Pas besoin d'itérer :

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)

note: Je ne sais pas exactement à quelle vitesse cela se produit.Vous feriez peut-être mieux avec mes autres suggestions.
Aussi:Ce code n'est pas testé

Autres conseils

Je pense que vous devriez regarder en utilisant values_list pour obtenir les spécialités de la personne

Remplacer:

[s.name for s in person.specialties]

avec:

person.specialties.values_list('name', flat=True)

Cela vous donnera une liste simple (ie. [ 'Spec1', 'spec2', ...]) que vous pouvez utiliser à nouveau. Et la requête SQL utilisée dans le bg sera également plus rapide car il ne sélectionne « nom » au lieu de faire un select * pour peupler les objets ORM

Vous pouvez aussi obtenir une amélioration de la vitesse par filtrage des emplois que la personne peut certainement pas effectuer:

remplacer:

jobs = Job.objects.all()

(2 requêtes - fonctionne pour django 1.0 +)

person_specialties = person.specialties.values_list('id', flat=True)
jobs = Job.objects.filter(required_specialties__id__in=person_specialties)

ou (1 requête - fonctionne pour django1.1 +)

jobs = Job.objects.filter(required_specialties__in=person.specialties.all())

Vous pouvez également obtenir une amélioration en utilisant select_related () sur vos travaux / requêtes personne (car ils ont une clé étrangère que vous utilisez)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top