Фильтр Django ManyToMany ()

132

У меня есть модель:

class Zone(models.Model):
    name = models.CharField(max_length=128)
    users = models.ManyToManyField(User, related_name='zones', null=True, blank=True)

И мне нужно построить фильтр по строкам:

u = User.objects.filter(...zones contains a particular zone...)

Это должен быть фильтр для пользователя, и это должен быть единственный параметр фильтра. Причина этого в том, что я создаю строку запроса URL для фильтрации списка изменений пользователей администратора:http://myserver/admin/auth/user/?zones=3

Кажется, это должно быть просто, но мой мозг не работает!

Энди Бейкер
источник
8
Я не уверен, правильно ли я понял - это не подходит User.objects.filter(zones__id=<id>)или User.objects.filter(zones__in=<id(s)>)подходит для этого?
Tomasz Zieliński
Это нормально :) Кстати, User.objects.filter(zones__in=<id(s)>)наверное, должно бытьUser.objects.filter(zones__id__in=<id(s)>)
Tomasz Zieliński
22
Просто хотел указать на то, что это работает в Google, только если установлено related_name. Например, zone_set не будет работать. Потрачено полчаса на это :-)

Ответы:

157

Просто повторю то, что сказал Томаш.

В тестах FOO__in=...« многие ко многим» и « многие к одному» можно найти множество примеров фильтров стиля . Вот синтаксис вашей конкретной проблемы:

users_in_1zone = User.objects.filter(zones__id=<id1>)
# same thing but using in
users_in_1zone = User.objects.filter(zones__in=[<id1>])

# filtering on a few zones, by id
users_in_zones = User.objects.filter(zones__in=[<id1>, <id2>, <id3>])
# and by zone object (object gets converted to pk under the covers)
users_in_zones = User.objects.filter(zones__in=[zone1, zone2, zone3])

Синтаксис двойного подчеркивания (__) используется повсеместно при работе с наборами запросов .

istruble
источник
Спасибо @maxm. Обновлено с более актуальной ссылкой на некоторые примеры.
istruble
9
двойное подчеркивание (пример: 3 часа проигрывают этому)
reabow
Скажите, пожалуйста, что делать, если я хочу, чтобы пользователи, находящиеся в наборе зон, а не одна из них? Скажем, найти пользователя, который находится в зоне 1, зоне
ПТ
Посмотрите на ...__inпримеры после # filtering on a few zones, by id. Они показывают фильтрацию для нескольких идентификаторов / объектов (в данном случае). Просто передайте нужные вам идентификаторы / объекты zone1, zone3 и zone10. Или при необходимости добавьте четвертую.
istruble 05
Спасибо. Я выполнял фильтрацию только по одному значению, а не по массиву, содержащему одно значение.
zypro 04
37

Обратите внимание: если пользователь может находиться в нескольких зонах, используемых в запросе, возможно, вы захотите добавить .distinct(). В противном случае вы получите одного пользователя несколько раз:

users_in_zones = User.objects.filter(zones__in=[zone1, zone2, zone3]).distinct()
QB.
источник
1

другой способ сделать это - пройти через промежуточную таблицу. Я бы выразил это в Django ORM так:

UserZone = User.zones.through

# for a single zone
users_in_zone = User.objects.filter(
  id__in=UserZone.objects.filter(zone=zone1).values('user'))

# for multiple zones
users_in_zones = User.objects.filter(
  id__in=UserZone.objects.filter(zone__in=[zone1, zone2, zone3]).values('user'))

было бы хорошо, если бы он не нуждался в .values('user')указанном, но Django (версия 3.0.7), похоже, нуждается в нем.

приведенный выше код в конечном итоге сгенерирует SQL, который выглядит примерно так:

SELECT * FROM users WHERE id IN (SELECT user_id FROM userzones WHERE zone_id IN (1,2,3))

что приятно, потому что у него нет промежуточных соединений, которые могли бы привести к возврату повторяющихся пользователей

Сэм Мейсон
источник
Хия. Сам по себе это не ответ. Вам следует добавить комментарий или отредактировать ответ QB, а не добавлять дополнительный частичный ответ.
Энди Бейкер,
Да - если вы хотите отредактировать свой ответ, чтобы он был полным сам по себе (если у вас недостаточно кармы для редактирования ответа QB?), Тогда это будет лучшим выбором. В идеале на StackOverflow есть «один правильный ответ». Обычно это не получается так аккуратно, но к этому стоит стремиться.
Энди Бейкер,
@AndyBaker согласился! ретроспективно ответ QB, вероятно, должен быть комментарием к ответу istruble, в то время как я думаю, что мой достаточно отчетливый, чтобы требовать отдельного ответа, но хорошо
Сэм Мейсон