Про Django ORM и SimpleLazyObject
Недавно я захотел создать собственный middleware, чтобы дополнить объект request, добавив в него дополнительный атрибут. Но я хотел, чтобы этот атрибут вычислялся лениво. Если у вас есть опыт разработки с Django, вы, вероятно, знаете, что он предоставляет ленивые функции, такие как reverse_lazy. Изучая внутреннюю реализацию функции, я обнаружил, что Django предоставляет модуль django.utils.functional, который содержит интересные функции и классы.
Мне понравился SimpleLazyObject, и я заметил, что он используется в middleware под названием django.contrib.auth.middleware.AuthenticationMiddleware. Всё казалось просто пока я не стал использовать "ленивый" атрибут в ORM-запросе Django 😁
Мой атрибут возвращает список значений, который должен использоваться для фильтрации в запросе, но основная проблема в том, как Django обрабатывает эти значения внутри ORM. Класс Query имеет метод resolve_lookup_value, заглянув внутрь него я увидел такое условие:
elif isinstance(value, (list, tuple)):
# The items of the iterable may be expressions and therefore need
# to be resolved independently.
values = (
self.resolve_lookup_value(sub_value, can_reuse, allow_joins, summarize)
for sub_value in value
)
type_ = type(value)
if hasattr(type_, "_make"): # namedtuple
return type_(*values)
return type_(values)
Этот код означает, что ваш экземпляр SimpleLazyObject будет преобразован в экземпляр SimpleLazyObject, содержащий генератор, возвращающий список значений от исходного объекта. И это не работает как я изначально ожидал. Моё решение было довольно простым: поскольку у меня был уникальный набор элементов, я заменил список значений на множество (имейте в виду, что кортеж не подойдёт из-за условия elif, которое проверяет и список, и кортеж).
Если у вас более сложный случай, я бы посоветовал расширить класс LazyObject (SimpleLazyObject является его подклассом) и реализовать свой собственный метод _setup.
