Контекстные процессоры Django

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

Что такое контекстные процессоры

Контекстные процессоры - это функции Python, которые принимают объект request и возвращают словарь с переменными. Эти переменные автоматически добавляются в контекст каждого шаблона, что делает их доступными без явной передачи из view.

Создание контекстного процессора

Создай файл context_processors.py в своём приложении:

 1from datetime import datetime
 2  from django.conf import settings
 3  from django.contrib.auth.models import User
 4  from django.core.cache import cache
 5
 6  def site_settings(request):
 7      """Добавляет базовые настройки сайта во все шаблоны"""
 8      return {
 9          'site_name': getattr(settings, 'SITE_NAME', 'Мой сайт'),
10          'site_description': getattr(settings, 'SITE_DESCRIPTION', 'Описание сайта'),
11          'current_year': datetime.now().year,
12          'current_month': datetime.now().month,
13          'debug_mode': settings.DEBUG,
14      }
15
16  def user_stats(request):
17      """Добавляет статистику пользователей (кэшированную)"""
18      cache_key = 'global_user_stats'
19      stats = cache.get(cache_key)
20
21      if stats is None:
22          stats = {
23              'total_users': User.objects.count(),
24              'active_users': User.objects.filter(is_active=True).count(),
25              'staff_users': User.objects.filter(is_staff=True).count(),
26          }
27          # Кэшируем на 1 час
28          cache.set(cache_key, stats, 3600)
29
30      return stats
31
32  def navigation_menu(request):
33      """Добавляет навигационное меню во все шаблоны"""
34      menu_items = [
35          {'url': '/', 'title': 'Главная', 'icon': 'home'},
36          {'url': '/about/', 'title': 'О нас', 'icon': 'info'},
37          {'url': '/contact/', 'title': 'Контакты', 'icon': 'mail'},
38          {'url': '/blog/', 'title': 'Блог', 'icon': 'file-text'},
39      ]
40
41      # Подсвечиваем текущую страницу
42      current_path = request.path
43      for item in menu_items:
44          if item['url'] == current_path:
45              item['active'] = True
46          else:
47              item['active'] = False
48
49      return {'navigation_menu': menu_items}
50
51  def social_media_links(request):
52      """Добавляет ссылки на социальные сети"""
53      return {
54          'social_links': {
55              'telegram': 'https://t.me/mysite',
56              'vk': 'https://vk.com/mysite',
57              'youtube': 'https://youtube.com/mysite',
58          }
59      }

Настройка в settings.py

Добавь контекстные процессоры в настройки Django:

 1TEMPLATES = [
 2      {
 3          'BACKEND': 'django.template.backends.django.DjangoTemplates',
 4          'DIRS': [BASE_DIR / 'templates'],
 5          'APP_DIRS': True,
 6          'OPTIONS': {
 7              'context_processors': [
 8                  'django.template.context_processors.debug',
 9                  'django.template.context_processors.request',
10                  'django.contrib.auth.context_processors.auth',
11                  'django.contrib.messages.context_processors.messages',
12                  # Твои кастомные процессоры
13                  'myapp.context_processors.site_settings',
14                  'myapp.context_processors.user_stats',
15                  'myapp.context_processors.navigation_menu',
16                  'myapp.context_processors.social_media_links',
17              ],
18          },
19      },
20  ]

Использование в шаблонах

Теперь все переменные доступны автоматически в любом шаблоне:

 1<!DOCTYPE html>
 2  <html>
 3  <head>
 4      <title>{{ site_name }} - {{ page_title|default:"Главная" }}</title>
 5      <meta name="description" content="{{ site_description }}">
 6  </head>
 7  <body>
 8      <header>
 9          <h1>{{ site_name }}</h1>
10          <p>&copy; {{ current_year }} Все права защищены</p>
11
12          <!-- Навигационное меню -->
13          <nav>
14              <ul class="nav-menu">
15                  {% for item in navigation_menu %}
16                      <li class="nav-item {% if item.active %}active{% endif %}">
17                          <a href="{{ item.url }}">
18                              <i class="icon-{{ item.icon }}"></i>
19                              {{ item.title }}
20                          </a>
21                      </li>
22                  {% endfor %}
23              </ul>
24          </nav>
25      </header>
26
27      <main>
28          {% block content %}{% endblock %}
29      </main>
30
31      <footer>
32          <div class="stats">
33              <p>Всего пользователей: {{ total_users }}</p>
34              <p>Активных: {{ active_users }}</p>
35          </div>
36
37          <div class="social-links">
38              {% for platform, url in social_links.items %}
39                  <a href="{{ url }}" target="_blank" rel="noopener">
40                      {{ platform|title }}
41                  </a>
42              {% endfor %}
43          </div>
44      </footer>
45  </body>
46  </html>

Контекстные процессоры с параметрами

Создай процессоры, которые принимают дополнительные параметры:

 1def dynamic_content(request, content_type='default'):
 2      """Динамический контент в зависимости от типа страницы"""
 3      content_map = {
 4          'home': {
 5              'hero_title': 'Добро пожаловать на главную',
 6              'hero_subtitle': 'Лучший сайт в интернете',
 7              'cta_text': 'Начать сейчас',
 8          },
 9          'blog': {
10              'hero_title': 'Наш блог',
11              'hero_subtitle': 'Полезные статьи и новости',
12              'cta_text': 'Подписаться',
13          },
14          'default': {
15              'hero_title': 'Добро пожаловать',
16              'hero_subtitle': 'Интересный контент',
17              'cta_text': 'Узнать больше',
18          }
19      }
20
21      return content_map.get(content_type, content_map['default'])
22
23  def user_preferences(request):
24      """Персональные настройки пользователя"""
25      if request.user.is_authenticated:
26          return {
27              'user_theme': getattr(request.user, 'theme', 'light'),
28              'user_language': getattr(request.user, 'language', 'ru'),
29              'notifications_enabled': getattr(request.user, 'notifications_enabled', True),
30          }
31      return {
32          'user_theme': 'light',
33          'user_language': 'ru',
34          'notifications_enabled': False,
35      }

Условные контекстные процессоры

Создай процессоры, которые работают только при определённых условиях:

 1def maintenance_mode(request):
 2      """Показывает информацию о техническом обслуживании"""
 3      if getattr(settings, 'MAINTENANCE_MODE', False):
 4          return {
 5              'maintenance_mode': True,
 6              'maintenance_message': getattr(settings, 'MAINTENANCE_MESSAGE', 'Сайт на техническом обслуживании'),
 7              'maintenance_end': getattr(settings, 'MAINTENANCE_END', None),
 8          }
 9      return {'maintenance_mode': False}
10
11  def a_b_testing(request):
12      """A/B тестирование для разных пользователей"""
13      if request.user.is_authenticated:
14          # Простое A/B тестирование по ID пользователя
15          is_variant_b = request.user.id % 2 == 0
16          return {
17              'ab_test_variant': 'B' if is_variant_b else 'A',
18              'show_new_feature': is_variant_b,
19          }
20      return {
21          'ab_test_variant': 'A',
22          'show_new_feature': False,
23      }

Контекстные процессоры для API

Создай процессоры, которые работают с внешними API:

 1import requests
 2  from django.core.cache import cache
 3
 4  def weather_info(request):
 5      """Добавляет информацию о погоде (кэшированную)"""
 6      cache_key = 'weather_info'
 7      weather = cache.get(cache_key)
 8
 9      if weather is None:
10          try:
11              # Пример с OpenWeatherMap API
12              api_key = getattr(settings, 'WEATHER_API_KEY', None)
13              if api_key:
14                  city = getattr(settings, 'WEATHER_CITY', 'Moscow')
15                  url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units=metric"
16                  response = requests.get(url, timeout=5)
17                  if response.status_code == 200:
18                      data = response.json()
19                      weather = {
20                          'temperature': round(data['main']['temp']),
21                          'description': data['weather'][0]['description'],
22                          'humidity': data['main']['humidity'],
23                          'city': city,
24                      }
25                  else:
26                      weather = {'error': 'Не удалось получить данные о погоде'}
27                  # Кэшируем на 30 минут
28                  cache.set(cache_key, weather, 1800)
29              else:
30                  weather = {'error': 'API ключ не настроен'}
31          except Exception as e:
32              weather = {'error': f'Ошибка: {str(e)}'}
33
34      return {'weather': weather}
35
36  def exchange_rates(request):
37      """Курсы валют (кэшированные)"""
38      cache_key = 'exchange_rates'
39      rates = cache.get(cache_key)
40
41      if rates is None:
42          try:
43              # Пример с API курсов валют
44              response = requests.get('https://api.exchangerate-api.com/v4/latest/RUB', timeout=5)
45              if response.status_code == 200:
46                  data = response.json()
47                  rates = {
48                      'usd': round(1 / data['rates']['USD'], 2),
49                      'eur': round(1 / data['rates']['EUR'], 2),
50                      'last_updated': data['date'],
51                  }
52                  # Кэшируем на 1 час
53                  cache.set(cache_key, rates, 3600)
54              else:
55                  rates = {'error': 'Не удалось получить курсы валют'}
56          except Exception as e:
57              rates = {'error': f'Ошибка: {str(e)}'}
58
59      return {'exchange_rates': rates}

Тестирование контекстных процессоров

Создай тесты для проверки работы процессоров:

 1from django.test import TestCase, RequestFactory
 2  from django.contrib.auth.models import User
 3  from django.test.utils import override_settings
 4  from myapp.context_processors import site_settings, user_stats, navigation_menu
 5
 6  class ContextProcessorsTest(TestCase):
 7      def setUp(self):
 8          self.factory = RequestFactory()
 9          self.user = User.objects.create_user(
10              username='testuser',
11              password='testpass'
12          )
13
14      def test_site_settings(self):
15          request = self.factory.get('/')
16          context = site_settings(request)
17
18          self.assertIn('site_name', context)
19          self.assertIn('current_year', context)
20          self.assertIn('debug_mode', context)
21          self.assertIsInstance(context['current_year'], int)
22
23      def test_user_stats(self):
24          request = self.factory.get('/')
25          context = user_stats(request)
26
27          self.assertIn('total_users', context)
28          self.assertIn('active_users', context)
29          self.assertGreaterEqual(context['total_users'], 1)
30
31      def test_navigation_menu(self):
32          request = self.factory.get('/')
33          context = navigation_menu(request)
34
35          self.assertIn('navigation_menu', context)
36          self.assertIsInstance(context['navigation_menu'], list)
37
38          # Проверяем, что текущая страница помечена как активная
39          home_item = next((item for item in context['navigation_menu'] if item['url'] == '/'), None)
40          self.assertIsNotNone(home_item)
41          self.assertTrue(home_item['active'])
42
43      @override_settings(MAINTENANCE_MODE=True)
44      def test_maintenance_mode(self):
45          from myapp.context_processors import maintenance_mode
46          request = self.factory.get('/')
47          context = maintenance_mode(request)
48
49          self.assertTrue(context['maintenance_mode'])
50          self.assertIn('maintenance_message', context)

Лучшие практики

  • Производительность: Кэшируй дорогие операции (API запросы, сложные вычисления)
  • Безопасность: Не передавай чувствительные данные (пароли, ключи API)
  • Именование: Используй понятные имена переменных, избегай конфликтов
  • Обработка ошибок: Обрабатывай исключения в процессорах
  • Тестирование: Покрывай процессоры unit-тестами
  • Документация: Добавляй docstring для всех процессоров
  • Модульность: Разделяй логику на отдельные процессоры
  • Конфигурация: Используй настройки Django для параметров

Частые ошибки и их решения

Ошибка: "TemplateSyntaxError: Invalid filter"

Решение: Проверь правильность регистрации процессора в settings.py

Ошибка: "NameError: name 'request' is not defined"

Решение: Убедись, что функция принимает параметр request

Ошибка: "Processors are not being called"

Решение: Проверь порядок процессоров в TEMPLATES настройках

Ошибка: "Performance issues with context processors"

Решение: Используй кэширование для дорогих операций

Альтернативы контекстным процессорам

В некоторых случаях лучше использовать другие подходы:

  • Template tags: Для логики, специфичной для конкретных шаблонов
  • Middleware: Для данных, которые нужны на уровне запроса
  • View mixins: Для данных, специфичных для конкретных views
  • Template inheritance: Для базовых переменных в базовых шаблонах

FAQ

Q: Когда использовать контекстные процессоры?
A: Для данных, которые нужны во всех шаблонах: настройки сайта, счетчики, глобальные переменные, навигация, статистика.

Q: Могут ли процессоры замедлить сайт?
A: Да, если они выполняют дорогие операции. Всегда используй кэширование для API запросов и сложных вычислений.

Q: Как передать параметры в контекстный процессор?
A: Контекстные процессоры принимают только request. Для параметров используй настройки Django или кэш.

Q: Можно ли отключить процессор для конкретного шаблона?
A: Нет, процессоры применяются ко всем шаблонам. Используй условную логику внутри процессора.

Q: Как тестировать процессоры с внешними API?
A: Используй моки и заглушки в тестах, или отключай внешние вызовы через настройки.

Q: Лучше ли использовать контекстные процессоры или передавать данные из view?
A: Используй процессоры для глобальных данных, а view - для специфичных данных страницы.