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

«Можно ли в Django кэшировать данные в свойстве (property) экземпляра Class-Based View?» — вопрос из категории Django, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Технически да, но это плохая практика для кэширования данных между запросами из-за особенностей жизненного цикла экземпляров 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)