Кастомные теги шаблонов Django
Django позволяет создавать собственные теги и фильтры для шаблонов, что значительно расширяет возможности шаблонизации и позволяет выносить сложную логику из шаблонов в Python код.
Структура файла templatetags
Создай папку templatetags
в своём приложении и добавь туда файл __init__.py
и файл с тегами:
Простой тег (simple_tag)
Используй для простых операций, которые возвращают значение:
1from django import template
2 from django.utils import timezone
3 from datetime import timedelta
4
5 register = template.Library()
6
7 @register.simple_tag
8 def get_current_time(format_string):
9 return timezone.now().strftime(format_string)
10
11 @register.simple_tag
12 def calculate_discount(price, discount_percent):
13 return price * (1 - discount_percent / 100)
14
15 @register.simple_tag(takes_context=True)
16 def user_greeting(context):
17 user = context['user']
18 if user.is_authenticated:
19 return f"Привет, {user.username}!"
20 return "Привет, гость!"
Тег включения (inclusion_tag)
Создаёт фрагмент шаблона с собственным контекстом:
1@register.inclusion_tag('myapp/recent_posts.html')
2 def show_recent_posts(count=5):
3 from myapp.models import Post
4 recent_posts = Post.objects.filter(
5 published=True
6 ).order_by('-created_at')[:count]
7 return {'posts': recent_posts}
8
9 @register.inclusion_tag('myapp/user_menu.html', takes_context=True)
10 def user_menu(context):
11 user = context['user']
12 menu_items = []
13
14 if user.is_authenticated:
15 if user.is_staff:
16 menu_items.append({'url': '/admin/', 'title': 'Админка'})
17 menu_items.extend([
18 {'url': '/profile/', 'title': 'Профиль'},
19 {'url': '/logout/', 'title': 'Выход'}
20 ])
21 else:
22 menu_items.extend([
23 {'url': '/login/', 'title': 'Вход'},
24 {'url': '/register/', 'title': 'Регистрация'}
25 ])
26
27 return {'menu_items': menu_items}
Шаблон для inclusion_tag
Создай файл myapp/recent_posts.html
:
1<div class="recent-posts">
2 <h4>Последние посты</h4>
3 {% if posts %}
4 <ul>
5 {% for post in posts %}
6 <li>
7 <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
8 <small>{{ post.created_at|date:"d.m.Y" }}</small>
9 </li>
10 {% endfor %}
11 </ul>
12 {% else %}
13 <p>Постов пока нет</p>
14 {% endif %}
15 </div>
Кастомные фильтры
Фильтры преобразуют значения в шаблонах:
1@register.filter
2 def truncate_chars(value, arg):
3 """Обрезает текст до указанного количества символов"""
4 try:
5 length = int(arg)
6 except ValueError:
7 return value
8
9 if len(value) <= length:
10 return value
11
12 return value[:length] + '...'
13
14 @register.filter(is_safe=True)
15 def highlight_search(text, search_term):
16 """Подсвечивает поисковые термины в тексте"""
17 if not search_term:
18 return text
19
20 from django.utils.html import mark_safe
21 highlighted = text.replace(
22 search_term,
23 f'<mark>{search_term}</mark>'
24 )
25 return mark_safe(highlighted)
26
27 @register.filter
28 def file_size_format(bytes_value):
29 """Форматирует размер файла в читаемом виде"""
30 for unit in ['B', 'KB', 'MB', 'GB']:
31 if bytes_value < 1024.0:
32 return f"{bytes_value:.1f} {unit}"
33 bytes_value /= 1024.0
34 return f"{bytes_value:.1f} TB"
Использование в шаблонах
Сначала загрузи кастомные теги в шаблоне:
1{% load custom_tags %}
2
3 <!-- Простые теги -->
4 <p>Текущее время: {% get_current_time "%H:%M:%S" %}</p>
5 <p>Цена со скидкой: {% calculate_discount 1000 15 %}</p>
6 <p>{% user_greeting %}</p>
7
8 <!-- Теги включения -->
9 {% show_recent_posts 3 %}
10 {% user_menu %}
11
12 <!-- Фильтры -->
13 <p>{{ long_text|truncate_chars:100 }}</p>
14 <p>{{ search_text|highlight_search:query }}</p>
15 <p>Размер файла: {{ file_size|file_size_format }}</p>
Продвинутые возможности
Создание тегов с множественными аргументами:
1@register.simple_tag
2 def pagination_info(page_obj, page_range=5):
3 """Создаёт информацию для пагинации"""
4 current_page = page_obj.number
5 total_pages = page_obj.paginator.num_pages
6
7 start_page = max(1, current_page - page_range // 2)
8 end_page = min(total_pages, start_page + page_range - 1)
9
10 return {
11 'current_page': current_page,
12 'total_pages': total_pages,
13 'start_page': start_page,
14 'end_page': end_page,
15 'has_previous': page_obj.has_previous(),
16 'has_next': page_obj.has_next(),
17 'previous_page': page_obj.previous_page_number() if page_obj.has_previous() else None,
18 'next_page': page_obj.next_page_number() if page_obj.has_next() else None,
19 }
20
21 @register.inclusion_tag('myapp/pagination.html')
22 def render_pagination(page_obj, page_range=5):
23 return pagination_info(page_obj, page_range)
Кэширование тегов
Используй кэширование для дорогих операций:
1from django.core.cache import cache
2
3 @register.simple_tag
4 def cached_user_stats(user_id):
5 """Кэширует статистику пользователя"""
6 cache_key = f"user_stats_{user_id}"
7 stats = cache.get(cache_key)
8
9 if stats is None:
10 # Выполняем дорогую операцию
11 from myapp.models import Post, Comment
12 stats = {
13 'posts_count': Post.objects.filter(author_id=user_id).count(),
14 'comments_count': Comment.objects.filter(author_id=user_id).count(),
15 'last_activity': Post.objects.filter(
16 author_id=user_id
17 ).order_by('-created_at').first()
18 }
19 # Кэшируем на 1 час
20 cache.set(cache_key, stats, 3600)
21
22 return stats
Тестирование кастомных тегов
Создай тесты для проверки работы тегов:
1from django.test import TestCase, RequestFactory
2 from django.contrib.auth.models import User
3 from django.template import Context, Template
4 from myapp.templatetags.custom_tags import register
5
6 class CustomTagsTest(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_multiply_tag(self):
15 template = Template('{% load custom_tags %}{% multiply 5 10 %}')
16 context = Context({})
17 self.assertEqual(template.render(context), '50')
18
19 def test_user_greeting_authenticated(self):
20 request = self.factory.get('/')
21 request.user = self.user
22 template = Template('{% load custom_tags %}{% user_greeting %}')
23 context = Context({'request': request})
24 self.assertIn('testuser', template.render(context))
25
26 def test_truncate_chars_filter(self):
27 template = Template('{% load custom_tags %}{{ "Hello World"|truncate_chars:5 }}')
28 context = Context({})
29 self.assertEqual(template.render(context), 'Hello...')
Лучшие практики
- Именование: Используй понятные имена для тегов и фильтров
- Документация: Добавляй docstring для всех тегов и фильтров
- Производительность: Кэшируй дорогие операции
- Безопасность: Используй
is_safe=True
только для проверенного HTML - Тестирование: Покрывай тегами unit-тестами
- Переиспользование: Создавай универсальные теги для разных приложений
Частые ошибки и их решения
Ошибка: "TemplateSyntaxError: Invalid filter"
Решение: Убедись, что файл __init__.py
существует в папке templatetags
Ошибка: "Template tag not found"
Решение: Проверь правильность загрузки тегов: {% load custom_tags %}
Ошибка: "Context variable not found"
Решение: Используй takes_context=True
для доступа к контексту шаблона
FAQ
Q: Когда использовать кастомные теги?
A: Для логики, которая не помещается в шаблонах: вычисления, форматирование, сложные операции, работа с базой данных.
Q: Можно ли использовать теги в других тегах?
A: Да, но лучше создавать отдельные функции и вызывать их из тегов для лучшей тестируемости.
Q: Как передать переменные из шаблона в тег?
A: Используй параметры тега или takes_context=True
для доступа к контексту.
Q: Нужно ли перезапускать сервер после изменения тегов?
A: Да, Django не перезагружает теги автоматически в production режиме.
Q: Как создать тег, который работает с несколькими приложениями?
A: Создай отдельное Django приложение только для тегов или используй общие утилиты.