Можно ли в Django кэшировать данные в свойстве (property) экземпляра Class-Based View?

Ответ

Технически да, но это плохая практика для кэширования данных между запросами из-за особенностей жизненного цикла экземпляров View в Django. Однако это приемлемый способ избежать повторных вычислений в рамках одного запроса.

Проблема: Состояние View между запросами

Django может переиспользовать один и тот же экземпляр View для обработки нескольких запросов от разных пользователей. Если вы сохраните данные в атрибуте экземпляра (self.my_data = ...), эти данные могут "протечь" в следующий запрос, что приведет к показу неверной или чужой информации.

Анти-паттерн (не делайте так для межзапросного кэша):

class LeakyView(View):
    _cached_data = None # Атрибут будет жить дольше одного запроса

    def get(self, request):
        if self._cached_data is None:
            # Тяжелая операция, выполняется только один раз для экземпляра
            self._cached_data = heavy_database_query()
        return HttpResponse(self._cached_data)

Правильный подход: Кэширование в рамках одного запроса

Если вам нужно вычислить значение один раз и использовать его в нескольких методах View в течение обработки одного запроса (например, в get_context_data и get), используйте декоратор @cached_property.

from django.utils.functional import cached_property
from django.views.generic import TemplateView

class MyView(TemplateView):
    template_name = "my_template.html"

    @cached_property
    def complex_data(self):
        # Эта тяжелая операция выполнится только один раз за запрос
        print("Выполняется дорогой запрос...")
        return list(MyModel.objects.filter(is_active=True))

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['items'] = self.complex_data # Первый вызов
        return context

    def get(self, request, *args, **kwargs):
        # ... какая-то логика
        print(f"Количество элементов: {len(self.complex_data)}") # Второй вызов, но запрос не повторится
        return super().get(request, *args, **kwargs)

Лучшее решение: Django Cache Framework

Для кэширования данных между запросами всегда используйте встроенный фреймворк кэширования Django. Он безопасен и предоставляет контроль над временем жизни кэша.

from django.core.cache import cache

def get_data_view(request):
    data = cache.get('my_unique_cache_key')
    if data is None:
        data = heavy_database_query()
        cache.set('my_unique_cache_key', data, timeout=300) # Кэш на 5 минут
    return HttpResponse(data)

Ответ 18+ 🔞

О, смотри-ка, какой вопрос подкинули! Технически-то да, можно, но это, блядь, пиздец какая плохая практика, если ты хочешь кэшировать данные между запросами. Потому что жизненный цикл этих View в Django — это отдельный ёперный театр.

В чём, сука, подвох? Состояние View между запросами

Представь себе: Django может взять один и тот же экземпляр твоего View и, как хитрая жопа, подсунуть его для обработки запроса от другого пользователя. И если ты нахрапом запихнул данные в атрибут экземпляра (self.my_data = ...), то эти данные могут, блядь, «протечь» в следующий запрос. И получится, что Вася увидит фотки из отпуска Пети. Пиздец и скандал.

Вот так делать НЕ НАДО, ёпта (анти-паттерн для межзапросного кэша):

class LeakyView(View):
    _cached_data = None  # Этот атрибут будет жить дольше, чем один запрос, вот в чём засада!

    def get(self, request):
        if self._cached_data is None:
            # Тяжёлая операция, выполнится всего раз для экземпляра... и накроет всех
            self._cached_data = heavy_database_query()
        return HttpResponse(self._cached_data)

Нормальный подход: Кэш в рамках одного запроса

А вот если тебе надо что-то посчитать один раз и юзать в разных методах в течение одного запроса (ну, там, в get_context_data и ещё где), то тут уже можно. Бери декоратор @cached_property, он твой друг.

from django.utils.functional import cached_property
from django.views.generic import TemplateView

class MyView(TemplateView):
    template_name = "my_template.html"

    @cached_property
    def complex_data(self):
        # Эта тяжёлая операция выполнится только один раз за запрос
        print("Выполняется дорогой запрос...")
        return list(MyModel.objects.filter(is_active=True))

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['items'] = self.complex_data  # Первый вызов
        return context

    def get(self, request, *args, **kwargs):
        # ... какая-то логика
        print(f"Количество элементов: {len(self.complex_data)}")  # Второй вызов, но запрос не повторится
        return super().get(request, *args, **kwargs)

А если по-взрослому? Django Cache Framework

Для кэширования между запросами — забудь про эти самоделки, блядь. Используй встроенный фреймворк кэширования Django. Он безопасный и даёт контроль над временем жизни кэша, а не как у нас — «авось пронесёт».

from django.core.cache import cache

def get_data_view(request):
    data = cache.get('my_unique_cache_key')
    if data is None:
        data = heavy_database_query()
        cache.set('my_unique_cache_key', data, timeout=300)  # Кэш на 5 минут
    return HttpResponse(data)

Вот так, без выёбок и подводных камней.