Как правильно запросить ManyToManyField для всех объектов в списке (или другой ManyToManyField)? - PullRequest
16 голосов
/ 03 декабря 2009

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

Например, у меня есть несколько Person, которые могут иметь более одной специальности. Есть также 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()) будут возвращаться задания, соответствующие любым специальностям человека, но не всем. Используя этот запрос, для певческого кодера будет отображаться задание, которое требует Singing and Dancing, что не является желаемым результатом.

Я надеюсь, что этот пример не слишком запутанный. Причина, по которой я обеспокоен этим, заключается в том, что специализаций в системе, вероятно, будет намного больше, и их циклирование не кажется лучшим способом для достижения этой цели. Мне интересно, может ли кто-нибудь дать царапину этой зуд!

Ответы [ 2 ]

12 голосов
/ 03 декабря 2009

Другая идея

Хорошо, наверное, я должен был добавить это к другому ответу, но когда я начал, казалось, что это будет другое направление, ха-ха

Нет необходимости повторять:

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 : Я не знаю точно, как быстро это происходит. Возможно, вам будет лучше с другими моими предложениями.
Также: этот код не проверен

4 голосов
/ 03 декабря 2009

Я думаю, вам стоит взглянуть на 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 () в ваших запросах о работе / персонале (поскольку у них есть внешний ключ, который вы используете)

...