Контекстные процессоры 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>© {{ 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 - для специфичных данных страницы.