Описание

Метод filter() в Django предназначен для фильтрации запросов на основе атрибутов модели или их комбинаций, при этом они указываются через запятую, а в SQL запросе будут соединены между собой логическим оператором AND.

Использование же других логических операторов, таких как ORNOT и т.д., напрямую для атрибутов модели не предусмотрено. Вместо этого, для выполнения сложных логических операций используются Q объекты.

Объект класса Q представляет собой абстракцию для условий фильтрации, которые могут быть легко комбинированы и использованы для создания более сложных запросов к базе данных.

Синтаксис

Класс Q наследуется от класса Node, и я не буду забираться сейчас слишком глубоко) на данном этапе достаточно понимать, что класс Q может принимать сразу несколько условий, как позиционными, так и именованными аргументами. И по умолчанию эти аргументы соединяются между собой условием AND.

# from django.db.models import Q
 
class Q(tree.Node):from django.db.models import Q
    """
    Encapsulate filters as objects that can then be combined logically (using
    `&` and `|`).
    """
 
    # Connection types
    AND = "AND"
    OR = "OR"
    XOR = "XOR"
    default = AND
    conditional = True
 
    def __init__(self, *args, _connector=None, _negated=False, **kwargs):
        super().__init__(
            children=[*args, *sorted(kwargs.items())],
            connector=_connector,
            negated=_negated,
        )
		...

Например, condition = Q(title__contains='ль', pk__in=(1, 7, 9, 14)) будет иметь следующий словарь атрибутов:

{'children': [('pk__in', (1, 7, 9, 14)), ('title__contains', 'ль')],
 'connector': 'AND',
 'negated': False}

Где children - список условий для фильтрации, connector - логический оператор, которым объединяются между собой условия из children, и negated - должны ли данные условия отрицаться.

При указании этого условия в фильтре, например, Women.objects.filter(condition), Q объект будет преобразован в следующее условие для SQL запроса:

In [32]: Women.objects.filter(condition)
...
WHERE (**"app_women"."id" IN (1, 7, 9, 14) 
			AND "app_women"."title" LIKE '%ль%'** ESCAPE '\\')

Если при создании Q объекта перед ним указать знак тильды ~, то атрибут negated примет значение True.

condition = ~Q(title__contains='ль')
 
In [35]: condition.__dict__
Out[35]: {'children': [('title__contains', 'ль')], 
					'connector': 'AND', **'negated': True**}

И данный объект будет преобразован в условие с оператором NOT в SQL запросе.

In [36]: Women.objects.filter(condition)
...
WHERE **NOT ("app_women"."title" LIKE '%ль%'** ESCAPE '\\')

Note

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

Объединение Q объектов

Объекты Q можно объединять с помощью операторов & - AND, | - OR и ^ - XOR. В результате такого объединения получается новый Q-объект.

Например, в данном случае я использую оператор pipe |, и атрибут connector принимает значение OR.

In [37]: con1 = Q(title__contains='ль')
 
In [38]: con2 = Q(pk__in=(1, 7, 14, 18))
 
In [39]: **condition = con1 | con2**
 
In [40]: condition.__dict__
Out[40]: 
{'children': [('title__contains', 'ль'), ('pk__in', (1, 7, 14, 18))],
 **'connector': 'OR'**,
 'negated': False}
 

При использовании данного объекта в методе filter, мы получим следующее условие в SQL запросе:

In [41]: Women.objects.filter(condition)
...
WHERE ("app_women"."title" LIKE '%ль%' ESCAPE '\\' **OR** "app_women"."id" IN (1, 7, 14, 18))

Использование с lookups

Если мы хотим использовать Q объекты совместно с обычными лукапами по атрибутам объекта, то Q объекты нужно указывать в начале, как позиционные аргументы.

Также важно запомнить, что при перечислении нескольких Q объектов через & и |, AND и OR операторы в выражении будут указаны на одном уровне.

In [53]: Women.objects.filter(Q(title__contains='ль') | Q(cat_id=4) & Q(pk__in=(1, 2, 3)))
...
WHERE ("app_women"."title" LIKE '%ль%' ESCAPE '\\' 
				OR ("app_women"."cat_id" = 4 
				AND "basefunc_post"."id" IN (1, 2, 3)))

Если же мы используем | и , то условие с оператором OR будет взято в скобки.

In [54]: Women.objects.filter(Q(title__contains='ль') | Q(cat_id=4), Q(pk__in=(1, 2, 3)))
...
WHERE (**("app_women"."title" LIKE '%ль%' ESCAPE '\\' OR "basefunc_post"."cat_id" = 4)** 
				AND "app_women"."id" IN (1, 2, 3))

Note

Так как мы не можем соединять между собой лукапы по атрибутам с Q объектами, используя что-то кроме запятой, то желательно помнить, что Q объекты соединённые между собой | в преобразованных SQL запросах будут взяты в скобки!

Summery

Чтобы получать более сложные условия для фильтрации, используем Q объекты.

С Q объектами можно использовать следующие операторы:

  • для NOT используем ~;
  • для AND используем &;
  • для OR используем |;
  • для XOR используем ^;

Приоритет операторов аналогичен приоритетам в булевой алгебре)

Операторы выше перечислены в соответствии с их приоритетом (наивысший у верхнего).

Можно использовать Q объекты с лукапами атрибутов объекта, но только через запятую и указав Q объекты в начале условия.

〰〰〰 𓆝 𓆟 𓆞 𓆝 𓆟 𓆝 𓆟 𓆞 〰〰〰