django - query - ¿Cómo consultar correctamente un ManyToManyField para todos los objetos en una lista(u otro ManyToManyField)?
orm django queries (2)
Otra idea
Ok, supongo que debería haber agregado esto a la otra respuesta, pero cuando empecé, 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 qué tan rápido es esto. Usted puede ser mejor con mis otras sugerencias.
También: este código no está probado
Estoy bastante confundido acerca de la mejor manera de construir una consulta de Django que compruebe si todos los elementos de un campo ManyToMany
(o una lista) están presentes en otro campo ManyToMany
.
Como ejemplo, tengo varias Person
que pueden tener más de una especialidad. También hay Job
que las personas pueden iniciar, pero requieren que uno o más Specialty
sean elegibles para comenzar.
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 solo si tiene todas las especialidades que el trabajo requiere. Entonces, nuevamente por el ejemplo, tenemos tres especialidades:
- Codificación
- Canto
- Bailando
Y tengo un Job
que requiere las especialidades de canto y baile. Una persona con especialidades de canto y baile puede comenzar, pero otra con especialidades de codificación y canto no puede hacerlo, ya que el trabajo requiere una persona que pueda cantar y bailar.
Entonces, ahora necesito una manera de encontrar todos los trabajos que una persona pueda 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 se debe a que el uso de Job.objects.filter(specialty__in=person.specialties.all())
devolverá trabajos que coincidan con cualquiera de las especialidades de la persona, no todas. Usando esta consulta, el trabajo que requiere Cantar y Bailar aparecerá para el codificador de canto, que no es el resultado deseado.
Espero que este ejemplo no sea demasiado complicado. La razón por la que estoy preocupado por esto es que las Especialidades en el sistema probablemente serán mucho más, y el bucle sobre ellas no parece ser la mejor manera de lograrlo. ¡Me pregunto si alguien podría darle un rasguño a esta picazón!
Creo que deberías considerar la utilización de values_list para obtener las especialidades de la persona.
Reemplazar:
[s.name for s in person.specialties]
con:
person.specialties.values_list(''name'', flat=True)
Eso le dará una lista simple (es decir, [[spec1 '','' spec2 '', ...]) que puede usar de nuevo. Y la consulta sql utilizada en el bg también será más rápida porque solo seleccionará ''nombre'' en lugar de hacer una select *
para rellenar los objetos ORM
También puede obtener una mejora de la velocidad al filtrar los trabajos que la persona definitivamente NO puede realizar:
entonces 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? - funciona para django1.1 +)
jobs = Job.objects.filter(required_specialties__in=person.specialties.all())
También puede obtener una mejora al utilizar select_related() en sus consultas de trabajos / personas (ya que tienen una clave externa que está usando)