Cómo consultar adecuadamente un ManyToManyField para todos los objetos de una lista (u otra ManyToManyField)?
-
12-09-2019 - |
Pregunta
Estoy bastante confundido acerca de la mejor manera de construir una consulta de Django que comprueba si todos los elementos de un campo ManyToMany
(o una lista) están presentes en otro campo ManyToMany
.
A modo de ejemplo, tengo varias Person
s, que pueden tener más de una especialidad. Hay también Job
s que la gente puede empezar, pero requieren una o más Specialty
s para ser elegible para ser iniciado.
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')
Una persona puede comenzar un trabajo solamente si tienen todos las especialidades que el trabajo requiere. Así, de nuevo por el bien de ejemplo, tenemos tres especialidades:
- Codificación
- Canto
- Baile
Y tengo una Job
que requiere las especialidades de canto y baile. Una persona con canto y baile especialidades puede iniciarlo, pero otro con la codificación y el canto especialidades no puede -. Como requiere el empleo de una persona que puede tanto cantar y bailar
Por lo tanto, ahora necesito una manera de encontrar todos los puestos de trabajo que una persona puede asumir. Esta fue mi manera de abordarlo, pero estoy seguro de que hay un enfoque más elegante:
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()
Esto es porque el uso de Job.objects.filter(specialty__in=person.specialties.all())
volverá trabajos que se ajusten cualquier de las especialidades de la persona, no todos ellos. El uso de esta consulta, el trabajo que requiere de Canto y Baile aparecería para el codificador de canto, que no es la salida deseada.
Espero que este ejemplo no es demasiado complicado. La razón por la que estoy preocupado por esto es que las especialidades en el sistema es probable que haya mucho más, y bucles sobre ellos no parece ser la mejor manera de lograr esto. Me pregunto si alguien podría prestar un rasguño a la picazón!
Solución
Otra idea
bien, supongo que debería haber añadido esto a la otra respuesta, pero cuando empecé en ella, parecía que iba a ser una dirección diferente jaja
No hay necesidad de iterar:
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)
Nota: no sé exactamente lo rápido que es. Usted puede ser mejor con mis otras sugerencias.
También: Este código no se ha probado
Otros consejos
Creo que debería buscar en el uso values_list para obtener especialidades de la persona
Reemplazar:
[s.name for s in person.specialties]
por:
person.specialties.values_list('name', flat=True)
Esto le dará una lista simple (es decir. [ 'Spec1', 'Esp2', ...]) que se puede utilizar de nuevo. Y la consulta SQL que se utiliza en el bg también será más rápido, ya que sólo se seleccione 'nombre' en vez de hacer un select *
para rellenar los objetos ORM
También puede obtener una mejora en la velocidad de filtración por puestos de trabajo que la persona que sin duda no puede realizar:
por lo reemplace:
jobs = Job.objects.all()
con (2 consultas - funciona para django 1.0 +)
person_specialties = person.specialties.values_list('id', flat=True)
jobs = Job.objects.filter(required_specialties__id__in=person_specialties)
o con (1 consulta - que funciona para django1.1 +)
jobs = Job.objects.filter(required_specialties__in=person.specialties.all())
También puede obtener una mejora mediante el uso de select_related () en sus puestos de trabajo / consultas persona (ya que tienen una clave externa que está utilizando)