Долго не понимал, почему в одном случае запрос «выполняется», а в другом — нет, хотя код похож. Чувствовал себя глупо, когда писал строчку, ждал обращения к базе, а её не было. Раздражение росло. Потом наткнулся на слово «ленивый queryset» — и всё встало на места со стыдливым «ах да, это же очевидно».
Нейтрально: QuerySet не трогает базу, пока его не «исполнят» — например, не итерируют, не превратят в список или не возьмут `.first()`. Сама цепочка фильтров только описывает запрос. Отсюда выходит и оптимизация: накопить фильтры и исполнить один раз.
qs = Habit.objects.filter(name__icontains="python") # БД ещё не тронута
qs = qs.order_by("-created") # всё ещё не тронута
print(len(qs)) # здесь — реальный запрос к базе
# итерировать по qs снова — повторного запроса не будет (кэш)
Страх «я не понимаю ORM» ушёл, когда я наконец увидел за ним простое правило: объект-описание и момент выполнения разные. Противная, но полезная неделя — такие вещи запоминаются навсегда.