Как правильно запросить ManyToManyField для всех объектов в списке (или другом ManyToManyField)?
Я довольно озадачен лучшим способом создания Django-запроса, который проверяет, присутствуют ли все элементы поля ManyToMany
(или список) в другом поле ManyToMany
.
В качестве примера у меня есть несколько Person
s, у которых может быть более одной Specialty. Есть также Job
, которые могут запускаться людьми, но для их запуска требуется один или несколько Specialty
.
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')
Человек может начать работу только в том случае, если у них есть все специальности, требуемые работой. Итак, опять же, для примера, у нас есть три специальности:
И у меня есть Job
, который требует специальностей поющего и танцующего. Человек с пением и танцевальными специальностями может начать его, но другой с навыками кодирования и пения не может - поскольку Иов требует Человека, который может петь и танцевать.
Итак, теперь мне нужен способ найти все задания, которые человек может принять. Это был мой способ справиться с этим, но я уверен, что есть более элегантный подход:
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()
Это связано с тем, что использование Job.objects.filter(specialty__in=person.specialties.all())
будет возвращать задания, соответствующие любым специальностям человека, а не все из них. Используя этот запрос, для пения-кодера появится работа, требующая пения и танца, которая не является желаемым выходом.
Я надеюсь, что этот пример не слишком запутан. Причина, по которой я беспокоюсь об этом, заключается в том, что Specialities в системе, вероятно, будет намного больше, и переключение по ним не похоже на лучший способ достичь этого. Мне интересно, сможет ли кто-нибудь приложить царапину к этому зуду!
Ответы
Ответ 1
Другая идея
Хорошо. Думаю, я должен был добавить это к другому ответу, но когда я начал его, казалось, что это будет другое направление haha
Не нужно итерации:
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: я точно не знаю, насколько это быстро. Вы можете быть лучше с моими другими предложениями.
Также: этот код не проверен
Ответ 2
Я думаю, вы должны посмотреть на values_list, чтобы получить специальные функции лица
Заменить:
[s.name for s in person.specialties]
с:
person.specialties.values_list('name', flat=True)
Это даст вам простой список (т.е. ['spec1', 'spec2',...]), который вы можете использовать снова. И sql-запрос, используемый в bg, также будет быстрее, потому что он будет выбирать только "имя" вместо того, чтобы делать select *
для заполнения объектов ORM
Вы также можете повысить скорость путем фильтрации заданий, которые человек определенно НЕ может выполнить:
поэтому замените:
jobs = Job.objects.all()
с (2 запроса - работает для django 1.0 +)
person_specialties = person.specialties.values_list('id', flat=True)
jobs = Job.objects.filter(required_specialties__id__in=person_specialties)
или с (1 запрос? - работает для django1.1 +)
jobs = Job.objects.filter(required_specialties__in=person.specialties.all())
Вы также можете получить улучшение, используя select_related() в своих запросах на работу/человека (поскольку у них есть внешний ключ, re using)