Cómo consultar adecuadamente un ManyToManyField para todos los objetos de una lista (u otra ManyToManyField)?

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

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 Persons, que pueden tener más de una especialidad. Hay también Jobs que la gente puede empezar, pero requieren una o más Specialtys 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!

¿Fue útil?

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)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top