Как правильно запросить ManyToManyField для всех объектов в списке (или другом ManyToManyField)?
-
12-09-2019 - |
Вопрос
Я довольно озадачен лучшим способом создания запроса Django, который проверяет, все элементы ManyToMany
поле (или список) присутствуют в другом ManyToMany
поле.
Например, у меня есть несколько Person
s, которые могут иметь более одной специальности.Это также Job
что люди могут начать, но им требуется один или несколько Specialty
s, чтобы иметь право на запуск.
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())
вернет вакансии, соответствующие любой специальностей человека, а не все.При использовании этого запроса для поющего кодировщика появится задание, требующее пения и танцев, что не является желаемым результатом.
Надеюсь, этот пример не слишком запутан.Причина, по которой меня это беспокоит, заключается в том, что специальностей в системе, вероятно, будет намного больше, и циклический просмотр их не кажется лучшим способом добиться этого.Мне интересно, сможет ли кто-нибудь поцарапать этот зуд!
Решение
Другая идея
Хорошо, думаю, мне следовало добавить это к другому ответу, но когда я начал это делать, казалось, что это будет другое направление, ха-ха
Нет необходимости повторять:
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)
примечание: Я не знаю точно, насколько это быстро.Возможно, вам лучше воспользоваться другими моими предложениями.
Также:Этот код не проверен
Другие советы
Я думаю, вам следует посмотреть на использование список_значений получить специальность человека
Заменять:
[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())
Вы также можете получить улучшение, используя выберите_родственные() по вашим запросам о работе/человеке (поскольку у них есть внешний ключ, который вы используете)