@sakuradaj

Как в Django сделать AND фильтрацию на основе M2M связи?

Есть приложение с такими моделями:

class Code(models.Model):
    zip = models.CharField(unique=True)
    # ...

class Item(models.Model):
    code = models.ManyToManyField(Code)
    # ...


Допустим есть zip коды которые необходимо использовать для фильтрации: 1,2,3,4

Нужно получить Item'ы которые обязательно имеют как минимум 4 связи с Code, у которых есть значения Code.zip 1,2,3,4

Это должно работать следующим образом:

for item in Item.objects.filter(...):
    zips = []
    for code in item.code.all():
        zips.append(code.zip)

    print zips
    # Здесь zips должны содержать 1,2,3,4


Я бы хотел еще узнать как фильтровать по точному соответствию, то есть найти Item у которых 4 связи с Code и у которых Code.zip соответствуют 1,2,3,4
(Возможно здесь стоит добавить еще поле в Item которое будет содержать отсортированный список id от связных Code объектов)

Хочу использовать этот QuerySet в Django admin.

Пробовал:
Item.objects.filter(code__zip__in=[1,2,3,4])
Но здесь получается OR выборка.
Еще пробовал с Q объектом, но получается вообще какой-то левый запрос:
qs.filter(reduce(lambda x, y: x & y, [m.models.Q(code__zip=z) for z in [1,2,3,4]]))


Как это реализовать?
  • Вопрос задан
  • 2794 просмотра
Решения вопроса 1
Не уверен в правильности, но может быть, попробовать вот так?

Item.objects.filter(code__zip=1).filter(code__zip=2).filter(code__zip=3).filter(code__zip=4)


По идее, это даст эффект операции AND.

Если не сработает, существует ещё одно решение. Если не против, разобьём его составление на шаги, мне самому так проще писать.
1. Делаем запрос на промежуточной таблице отношения ManyToMany, которая связывает Item и Code, выбирая те пары Item - Code, для которых item__zip входит в нужное множество.
qs = Item.code.through.objects.filter(code__zip__in=[1, 2, 3, 4])


2. В результирующем запросе значения item_id будут повторяться. Нам нужно найти те значения, для которых число строк равняется четырём.
from django.db.models import Count
qs = qs.values('item_id').annotate(count=Count('id')).order_by().filter(count=4)


Надеюсь, что больших ошибок не допустил.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы