Django-Admin.ru

filter() и exclude() в Django ORM: lookups, Q и F

Как фильтровать данные в Django ORM: filter и exclude, таблица lookups, фильтр по связям и датам, Q для OR и NOT, F-выражения, distinct и частые ошибки.

Редакция Django-Admin.ru
· обновлено · 13 мин чтения

Содержание · 12 разделов
  1. Модели и данные для примеров
  2. filter() и exclude()
  3. Таблица lookups
  4. Фильтр по связям
  5. Обратные связи и дубликаты
  6. Фильтр по дате и времени
  7. Q-объекты: OR, NOT и вложенные условия
  8. F-выражения: сравнение полей между собой
  9. Цепочка filter() на многозначных связях
  10. exclude() и NULL
  11. Частые ошибки
  12. FieldError: Cannot resolve keyword into field
  13. FieldError: Unsupported lookup
  14. Дубликаты в результате
  15. Поиск не находит кириллицу
  16. Фильтр в цикле
  17. Частые вопросы
  18. Как передать имя поля в filter() из переменной?
  19. Как найти записи с пустым полем: NULL или пустой строкой?
  20. Как отфильтровать по количеству связанных объектов?
  21. Как фильтровать по ключу в JSONField?
  22. Как фильтровать по параметрам из URL?
  23. Итог

Фильтрация в Django ORM строится на двух методах: filter() оставляет записи, подходящие под условие, exclude() — убирает их. Условие записывается как поле__lookup=значение, несколько аргументов объединяются через AND, а для OR и NOT нужны объекты Q:

Book.objects.filter(author__country="RU", rating__gte=4.6)
Book.objects.exclude(author__country="RU")
Book.objects.filter(Q(rating__gte=4.7) | Q(views__gte=12000))

Ниже — полный разбор Django ORM filter: все основные lookups, фильтр по прямым и обратным связям, по датам, Q и F, поведение цепочек на связях «один-ко-многим» и ошибки, которые встречаются чаще всего. Примеры проверены на Django 6.1 и SQLite и работают в Django 5.2 LTS. Обзор остальных методов QuerySet — в шпаргалке по Django ORM.

Модели и данные для примеров

Две модели: автор и книга. Поле pages допускает NULL — это понадобится, чтобы показать ловушку с exclude(). Как описывать поля и связи, разобрано в статье про модели Django.

from django.db import models
from django.utils import timezone


class Author(models.Model):
    name = models.CharField(max_length=200)
    country = models.CharField(max_length=2, blank=True)


class Book(models.Model):
    title = models.CharField(max_length=300)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books")
    published = models.DateField()
    rating = models.DecimalField(max_digits=3, decimal_places=1, default=0)
    views = models.PositiveIntegerField(default=0)
    likes = models.PositiveIntegerField(default=0)
    pages = models.PositiveIntegerField(null=True, blank=True)
    created_at = models.DateTimeField(default=timezone.now)

В базе шесть книг:

titleauthorpublishedratingviewslikespages
Война и мирЛев Толстой1869-01-014.81500021001300
Анна КаренинаЛев Толстой1877-01-014.612000900860
Преступление и наказаниеФёдор Достоевский1866-01-014.7140001600670
ИдиотФёдор Достоевский1869-01-014.48000500NULL
Гордость и предубеждениеДжейн Остин1813-01-284.590001200430
EmmaДжейн Остин1815-12-233.93000150NULL

Есть и четвёртый автор — «Новый автор» без единой книги. В выводе ниже показаны названия найденных книг (или имена авторов) в порядке возврата из базы.

filter() и exclude()

Book.objects.filter(author__country="RU", rating__gte=4.6)
# → ['Война и мир', 'Анна Каренина', 'Преступление и наказание']

Book.objects.exclude(author__country="RU")
# → ['Гордость и предубеждение', 'Emma']

Оба метода возвращают новый QuerySet и не обращаются к базе, пока вы не начнёте перебирать результат. Поэтому их можно сколько угодно комбинировать: qs.filter(...).exclude(...).order_by(...). Исходный QuerySet при этом не меняется:

qs = Book.objects.filter(rating__gte=4.5)
qs2 = qs.exclude(author__country="GB")
# qs  → ['Война и мир', 'Анна Каренина', 'Преступление и наказание', 'Гордость и предубеждение']
# qs2 → ['Война и мир', 'Анна Каренина', 'Преступление и наказание']

Чтобы увидеть SQL, выведите qs.query:

print(Book.objects.filter(published__year=1869).query)
# SELECT ... FROM "library_book"
# WHERE "library_book"."published" BETWEEN 1869-01-01 AND 1869-12-31

Учтите: print(qs.query) подставляет параметры без кавычек, поэтому такой текст не всегда можно выполнить в консоли базы как есть. Он нужен, чтобы понять логику запроса.

Таблица lookups

Lookup — это суффикс после двойного подчёркивания. Без суффикса используется exact.

LookupЧто проверяетПример
exactравенство; =None превращается в IS NULLtitle="Идиот", pages=None
iexactравенство без учёта регистраtitle__iexact="emma"
contains, icontainsподстрока (LIKE '%…%')title__icontains="мир"
startswith, istartswithначало строкиtitle__startswith="Идиот"
endswith, iendswithконец строкиtitle__endswith="мир"
inзначение из списка или подзапросаpk__in=[1, 3]
gt, gte, lt, lteбольше, больше или равно, меньше, меньше или равноrating__gte=4.5
rangeдиапазон включительно (BETWEEN)published__range=(d1, d2)
isnullIS NULL / IS NOT NULLpages__isnull=True
regex, iregexрегулярное выражениеtitle__regex=r"^[А-Я]"
dateдата из DateTimeFieldcreated_at__date=date(2026, 5, 6)
year, iso_year, quarter, monthгод, ISO-год, квартал, месяцpublished__year=1869
week, week_day, iso_week_dayнеделя года, день неделиcreated_at__week_day=2
dayдень месяцаpublished__day=1
time, hour, minute, secondчасти времени из DateTimeFieldcreated_at__hour__gte=18

Части даты можно комбинировать с операторами сравнения: published__year__lt=1850, created_at__hour__gte=18. Список полный для обычных полей. JSONField добавляет свои lookups — обращение по ключу и has_key, а поля PostgreSQL вроде ArrayField — contains, overlap и другие.

Несколько результатов на наших данных:

Book.objects.filter(title__startswith="Идиот")  # → ['Идиот']
Book.objects.filter(pk__in=[1, 3])               # → ['Война и мир', 'Преступление и наказание']
Book.objects.filter(pages__isnull=True)          # → ['Идиот', 'Emma']
Book.objects.filter(pages=None)                  # → ['Идиот', 'Emma'] — то же самое
Book.objects.filter(pk__in=[])                   # → [] — база даже не запрашивается

Важно: в SQLite icontains, iexact и другие i-lookups не учитывают регистр только для латиницы. Book.objects.filter(title__icontains="война") вернёт пустой список, хотя "Война" найдёт «Войну и мир», а title__iexact="emma" сработает. В PostgreSQL регистронезависимый поиск по кириллице работает корректно — ещё одна причина разрабатывать на той же СУБД, что и в продакшне (см. Django и PostgreSQL).

Фильтр по связям

Двойное подчёркивание позволяет пройти по ForeignKey к полям связанной модели — Django сам добавит JOIN:

Book.objects.filter(author__name__startswith="Лев")
# → ['Война и мир', 'Анна Каренина']

Если нужен конкретный объект, передайте его или его ID — это эквивалентные запросы:

tolstoy = Author.objects.get(name="Лев Толстой")
Book.objects.filter(author=tolstoy)
Book.objects.filter(author_id=tolstoy.pk)

В __in можно передать другой QuerySet — он станет подзапросом, а не отдельным обращением к базе:

Book.objects.filter(author__in=Author.objects.filter(country="GB"))
# WHERE "library_book"."author_id" IN (SELECT "U0"."id" FROM "library_author" "U0" WHERE "U0"."country" = GB)
# → ['Гордость и предубеждение', 'Emma']

Обратные связи и дубликаты

В обратную сторону фильтр пишется через related_name (здесь books; без него — имя модели в нижнем регистре, book):

Author.objects.filter(books__title__startswith="Пре")  # → ['Фёдор Достоевский']
Author.objects.filter(books__isnull=True)              # → ['Новый автор']

У автора может быть несколько подходящих книг, и тогда он попадёт в результат несколько раз — JOIN возвращает строку на каждую пару «автор–книга»:

Author.objects.filter(books__rating__gte=4.5)
# → ['Лев Толстой', 'Лев Толстой', 'Фёдор Достоевский', 'Джейн Остин']

Author.objects.filter(books__rating__gte=4.5).distinct()
# → ['Лев Толстой', 'Фёдор Достоевский', 'Джейн Остин']

distinct() убирает повторы, но добавляет к запросу SELECT DISTINCT, что на больших таблицах стоит ресурсов. Альтернатива без дубликатов — подзапрос: Author.objects.filter(pk__in=Book.objects.filter(rating__gte=4.5).values("author")). В PostgreSQL есть ещё distinct("поле") (DISTINCT ON), но он требует, чтобы order_by() начинался с тех же полей.

Фильтр по дате и времени

Для DateField и DateTimeField работают обычные сравнения и lookups частей даты:

from datetime import date

Book.objects.filter(published__year=1869)
# → ['Война и мир', 'Идиот']

Book.objects.filter(published__range=(date(1860, 1, 1), date(1870, 12, 31)))
# → ['Война и мир', 'Преступление и наказание', 'Идиот']

published__year=1869 Django превращает в BETWEEN 1869-01-01 AND 1869-12-31 (это видно в SQL выше), поэтому индекс по полю продолжает работать.

Для DateTimeField есть __date, __hour и другие части времени. «Записи за последние 7 дней» выбираются сравнением с текущим моментом:

from datetime import timedelta

from django.utils import timezone

Book.objects.filter(created_at__gte=timezone.now() - timedelta(days=7))
Book.objects.filter(created_at__date=date(2026, 5, 6))   # → ['Emma']
Book.objects.filter(created_at__hour__gte=18)            # → ['Анна Каренина', 'Идиот']
Book.objects.filter(created_at__week_day=2)              # → ['Война и мир', 'Идиот']

Две особенности:

  • Часовой пояс. При USE_TZ = True lookups __date, __year, __hour и т. д. считаются в текущем часовом поясе (по умолчанию TIME_ZONE из настроек). Книга «Emma» создана 6 мая в 01:30 по Москве — это 5 мая 22:30 по UTC. С TIME_ZONE = "Europe/Moscow" её находит created_at__date=date(2026, 5, 6), а внутри timezone.override("UTC") — уже date(2026, 5, 5). Подробнее о датах и часовых поясах в Python — в статье про datetime.
  • Нумерация дней недели. week_day считает от воскресенья: 1 — воскресенье, 2 — понедельник, …, 7 — суббота. Если удобнее ISO-нумерация (1 — понедельник), используйте iso_week_day.

Q-объекты: OR, NOT и вложенные условия

Q упаковывает условие в объект, который можно комбинировать операторами | (OR), & (AND) и ~ (NOT):

from django.db.models import Q

Book.objects.filter(Q(rating__gte=4.7) | Q(views__gte=12000))
# → ['Война и мир', 'Анна Каренина', 'Преступление и наказание']

Book.objects.filter(~Q(author__country="RU"))
# → ['Гордость и предубеждение', 'Emma']

Скобки задают приоритет, как в обычной логике:

Book.objects.filter(
    Q(author__country="RU") & (Q(rating__gte=4.7) | Q(pages__isnull=True))
)
# WHERE ("library_author"."country" = RU AND ("library_book"."rating" >= 4.70 OR "library_book"."pages" IS NULL))
# → ['Война и мир', 'Преступление и наказание', 'Идиот']

Q и обычные именованные аргументы можно смешивать, но Q должен идти первым — так требует синтаксис Python (позиционные аргументы раньше именованных):

Book.objects.filter(Q(author__country="GB"), rating__gte=4)  # → ['Гордость и предубеждение']
Book.objects.filter(rating__gte=4, Q(author__country="GB"))  # SyntaxError

Условие из списка значений удобно собрать через functools.reduce, например поиск по нескольким словам:

import operator
from functools import reduce

words = ["мир", "Идиот", "Emma"]
cond = reduce(operator.or_, (Q(title__contains=w) for w in words))
print(cond)
# (OR: ('title__contains', 'мир'), ('title__contains', 'Идиот'), ('title__contains', 'Emma'))

Book.objects.filter(cond)  # → ['Война и мир', 'Идиот', 'Emma']

F-выражения: сравнение полей между собой

Справа от lookup обычно стоит константа. Чтобы сравнить одно поле с другим, используйте F() — значение возьмётся из той же строки в базе:

from django.db.models import F

# Лайков больше, чем 10 % просмотров
Book.objects.filter(likes__gt=F("views") / 10)
# → ['Война и мир', 'Преступление и наказание', 'Гордость и предубеждение']

# Просмотров хотя бы в 10 раз больше, чем лайков
Book.objects.filter(views__gte=F("likes") * 10)
# → ['Анна Каренина', 'Идиот', 'Emma']

F тоже ходит по связям: F("author__name"). Деление целого поля на целое число в SQLite и PostgreSQL отбрасывает дробную часть: при views = 7 выражение F("views") / 2 даст 3. В MySQL результат будет дробным. Чтобы получить дробь в любой СУБД, делите на число с точкой (F("views") / 2.0) или приведите поле через Cast("views", FloatField()). Про F в update() для атомарного увеличения счётчиков — в шпаргалке по QuerySet.

Цепочка filter() на многозначных связях

Для обычных полей filter(a).filter(b) и filter(a, b) дают одинаковый результат. На обратных ForeignKey и ManyToMany — нет, и это частый источник ошибок.

Задача: найти авторов, у которых есть книга 1869 года с рейтингом от 4.7.

# Оба условия к ОДНОЙ и той же книге
Author.objects.filter(books__published__year=1869, books__rating__gte=4.7)
# → ['Лев Толстой']

# Условия могут выполниться на РАЗНЫХ книгах
Author.objects.filter(books__published__year=1869).filter(books__rating__gte=4.7)
# → ['Лев Толстой', 'Фёдор Достоевский']

У Достоевского есть «Идиот» 1869 года (4.4) и «Преступление и наказание» с рейтингом 4.7 — это разные книги. Второй filter() создаёт отдельный JOIN, поэтому каждое условие проверяется независимо. Если условия относятся к одному связанному объекту, пишите их в одном вызове filter().

exclude() с несколькими условиями на многозначной связи ведёт себя как цепочка, а не как один filter():

Author.objects.exclude(books__published__year=1869, books__rating__gte=4.7)
# → ['Джейн Остин', 'Новый автор'] — Достоевский тоже исключён

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

Author.objects.exclude(
    books__in=Book.objects.filter(published__year=1869, rating__gte=4.7)
)
# → ['Фёдор Достоевский', 'Джейн Остин', 'Новый автор']

exclude() и NULL

exclude() — не точная инверсия filter(), если в поле бывает NULL:

Book.objects.filter(pages__gte=500)
# → ['Война и мир', 'Анна Каренина', 'Преступление и наказание']

Book.objects.exclude(pages__lt=500)
# → ['Война и мир', 'Анна Каренина', 'Преступление и наказание', 'Идиот', 'Emma']

Django строит условие WHERE NOT (pages < 500 AND pages IS NOT NULL), поэтому книги без количества страниц остаются в выборке. Если они не нужны, добавьте .exclude(pages__isnull=True) или сформулируйте условие через filter().

Частые ошибки

FieldError: Cannot resolve keyword into field

Опечатка в имени поля или попытка фильтровать по свойству (@property) либо методу модели — ORM видит только поля базы:

FieldError: Cannot resolve keyword 'name' into field. Choices are: author, author_id, created_at, id, likes, pages, published, rating, title, views

В сообщении перечислены доступные имена. Вычисляемое значение сначала добавьте через annotate(), а потом фильтруйте по нему.

FieldError: Unsupported lookup

Unsupported lookup 'more' for DecimalField or join on the field not permitted — lookup с таким именем не существует (правильно gt/gte) или вы пытаетесь пройти через поле, которое не является связью.

Дубликаты в результате

Фильтр по обратной связи или ManyToMany вернул один объект несколько раз. Добавьте distinct() или перепишите условие через pk__in с подзапросом.

Поиск не находит кириллицу

icontains на SQLite регистронезависим только для ASCII. Для кириллицы используйте PostgreSQL или храните отдельное поле в нижнем регистре.

Фильтр в цикле

for author in authors: Book.objects.filter(author=author) — отдельный запрос на каждого автора. Используйте author__in=authors или prefetch_related — подробно в статье о проблеме N+1.

Частые вопросы

Как передать имя поля в filter() из переменной?

Соберите условия в словарь и распакуйте его в именованные аргументы:

field = "title"
Book.objects.filter(**{f"{field}__icontains": "мир"})

Если имя поля приходит от пользователя, сверяйте его со списком разрешённых: иначе можно получить FieldError или фильтр по полю, которое не должно быть доступно.

Как найти записи с пустым полем: NULL или пустой строкой?

isnull проверяет только NULL, пустую строку ищут через exact. Если в колонке встречается и то и другое, объедините условия через Q:

Author.objects.filter(Q(country="") | Q(country__isnull=True))

Как отфильтровать по количеству связанных объектов?

Сначала посчитайте связи через annotate(), затем фильтруйте по аннотации как по обычному полю:

from django.db.models import Count

Author.objects.annotate(books_count=Count("books")).filter(books_count__gte=2)
# → ['Лев Толстой', 'Фёдор Достоевский', 'Джейн Остин']

Условие на аннотацию Django переносит в SQL HAVING.

Как фильтровать по ключу в JSONField?

Ключи JSON указывают через двойное подчёркивание, как поля связанной модели. Пример для модели с полем attributes = models.JSONField(default=dict); запросы работают во всех поддерживаемых СУБД, включая SQLite:

Product.objects.filter(attributes__cover="hard")
Product.objects.filter(attributes__has_key="lang")

Lookup contains для JSONField в SQLite и Oracle не поддерживается.

Как фильтровать по параметрам из URL?

Читайте значения из request.GET и добавляйте условие, только если параметр передан:

def book_list(request):
    books = Book.objects.select_related("author")
    if q := request.GET.get("q"):
        books = books.filter(title__icontains=q)
    if country := request.GET.get("country"):
        books = books.filter(author__country=country)
    ...

Когда параметров много, удобнее библиотека django-filter: она строит фильтры и форму по описанию полей. Для API на DRF она подключается как бэкенд фильтрации — пример в руководстве по Django REST Framework.

Итог

filter() и exclude() с lookups закрывают большинство задач выборки, Q добавляет OR и NOT, а F позволяет сравнивать поля между собой. Главные подводные камни — дубликаты при фильтре по обратным связям, разная семантика filter(a, b) и filter(a).filter(b) на многозначных связях и NULL в exclude().

Дальше пригодятся шпаргалка по QuerySet с сортировкой, агрегацией и обновлением и разбор оптимизации запросов N+1. Полный список lookups — в документации Django.

  • #Django ORM
  • #QuerySet
  • #filter
  • #Q-объекты
  • #F-выражения
↑ ↓ выборEnter открытьEsc закрыть