Как правильно запросить ManyToManyField для всех объектов в списке (или другом ManyToManyField)?

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

Вопрос

Я довольно озадачен лучшим способом создания запроса Django, который проверяет, все элементы ManyToMany поле (или список) присутствуют в другом ManyToMany поле.

Например, у меня есть несколько Persons, которые могут иметь более одной специальности.Это также Jobчто люди могут начать, но им требуется один или несколько Specialtys, чтобы иметь право на запуск.

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())

Вы также можете получить улучшение, используя выберите_родственные() по вашим запросам о работе/человеке (поскольку у них есть внешний ключ, который вы используете)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top