Как правильно запросить 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)