Блог на Django #28: Создание тегов шаблонизатора

Django предлагает множество встроенных тегов, таких как {% if %} или {% block %}. Некоторые из них были использованы в ранее рассмотренных шаблонах. С полным списком можно ознакомиться по ссылке: https://docs.djangoproject.com/en/2.0/ref/templates/builtins/.

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

Создание тега

Django предлагает следующие вспомогательные функции, которые позволят с легкостью создавать собственные шаблонные теги:

  • simple_tag: обрабатывает данные и возвращает строку.
  • inclusion_tag: обрабатывает данные и возвращает отрисованный шаблон.

Шаблонные теги могут использоваться только внутри приложений Django.

В папке приложения blog создайте новую папку, назовите ее templatetags и добавьте пустой файл __init__.py. Создайте еще один файл blog_tags.py. Файловая структура приложения блога должна быть следующей:

blog/ 
    __init__.py 
    models.py 
    ... 
    templatetags/ 
        __init__.py 
        blog_tags.py

Важно выбирать правильные названия, поскольку они будут использоваться для загрузки тегов в шаблонах.

Начнем с создания простого тега, который будет получать общее количество опубликованных в блоге постов. Отредактируйте файл blog_tags.py и добавьте следующий код:

from django import template  
from ..models import Post  
  
  
register = template.Library()  
  
  
@register.simple_tag  
def total_posts():  
    return Post.published.count()

Этот тег возвращает количество опубликованных постов. Каждый модуль с шаблонными тегами, чтобы являться действительной библиотекой тегов, должен включать переменную register. Эта переменная — экземпляр template.Library. Она используется для регистрации собственных шаблонных тегов и фильтров. Дальше нужно определить тег total_posts с помощью функции Python и использовать декоратор @register.simple_tag для регистрации его в качестве простого тега. Django использует имя функции в качестве имени тега. Если хочется выбрать другое имя, это можно сделать с помощью атрибута name, например, @register.simple_tag(name='my_tag').

После добавления нового модуля шаблонных тегов нужно перезагрузить сервер разработки Django, чтобы изменения вступили в силу, а новые теги и фильтры можно было использовать.

Перед тем как начинать использовать собственные шаблонные теги, их нужно сделать доступными с помощью тега {% load %}. Как упоминалось ранее, необходимо использовать имя модуля Python, включающего шаблонные теги и фильтры. Откройте шаблон и добавьте сверху {% load blog_tags %} для загрузки модуля тегов шаблона. Затем используйте созданный тег для отображения общего количества опубликованных постов. Просто добавьте тег {% total_posts %} в шаблон. Он будет выглядеть вот так:

{% load blog_tags %}  
{% load static %}  
<!DOCTYPE html>  
<html>  
<head>  
 <title>{% block title %}{% endblock %}</title>  
 <link href="{% static 'css/blog.css' %}" rel="stylesheet">  
</head>
<body>  
 <div id="content">  
  {% block content %}  
      {% endblock %}  
    </div>  
 <div id="sidebar">  
 <h2>My blog</h2>  
 <p>This is my blog. I've written {% total_posts %} posts so far.</p>  
 </div>
</body>  
</html>

Нужно перезагрузить сервер, чтобы проект отслеживал все новые файлы. Остановите его комбинацией Ctrl + C и запустите снова с помощью:

python manage.py runserver

Откройте https://127.0.0.1:8000/blog/ в браузере. Отобразится общее количество опубликованных постов:

Общее количество опубликованных постов

Преимущество собственных тегов в том, что с их помощью можно обрабатывать любые данные и добавлять в любой шаблон вне зависимости от представления. Можно использовать QuerySet или обрабатывать любые данные для отображения результатов в шаблонах.

Теперь создадим еще один тег для отображения последних постов в сайдбаре. Будем использовать тег inclusion (включения). С его помощью можно рендерить шаблон с контекстными переменными, которые шаблонный тег возвращает. Отредактируйте файл blog_tags.py и добавьте следующий код:

@register.inclusion_tag('blog/post/latest_posts.html')  
def show_latest_posts(count=5):  
    latest_posts = Post.published.order_by('-publish')[:count]  
    return {'latest_posts': latest_posts}

В этом коде тег шаблона был зарегистрирован с помощью @register.inclusion_tag. Также был определен шаблон, который тег вернет вместе со значениями с помощью blog/post/latest_posts.html. Тег будет принимать опциональный параметр count со значением по умолчанию 5. Он позволяет определять количество постов, которые необходимо выводить. Используем переменную для ограничения результатов запроса Post.published.order_by('-publish')[:count]. Обратите внимание, что функция возвращает словарь, а не одно значение. Включенный тег должен возвращать словарь значений, который используется как контекст для рендеринга определенного шаблона. Созданный тег шаблона позволяет определять количество постов для отображения по желанию: {% show_latest_posts 3 %}.

Теперь нужно создать новый файл шаблона в папке blog/post/ и назвать его latest_posts.html. Добавьте туда следующий код:

<ul>  
{% for post in latest_posts %}  
  <li>  
    <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>  
  </li>
{% endfor %}  
</ul>

Этот код отображает неупорядоченный список постов с помощью переменной latest_posts, которую возвращает тег шаблона. Теперь нужно отредактировать шаблон blog/base.html и добавить в него тег шаблона для отображения последних трех постов. Код сайдбара должен выглядеть следующим образом:

<div id="sidebar">  
  <h2>My blog</h2>  
  <p>This is my blog. I've written {% total_posts %} posts so far.</p>  
  <h3>Latest posts</h3>  
  {% show_latest_posts 3 %}  
</div>

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

Вернитесь в браузер и обновите странице. Сайдбар будет выглядеть так:

Отображения последних трех постов

Наконец, создадим просто тег шаблона, который сохраняет результат в переменной так, чтобы его можно было использовать в дальнейшем. Создадим тег для отображения наиболее комментируемых постов. Отредактируйте файл blog_tags.py и добавьте следующий импорт и шаблонный тег:

from django.db.models import Count

# ...

@register.simple_tag  
def get_most_commented_posts(count=5):  
    return Post.published.annotate(  
        total_comments=Count('comments')  
    ).order_by('-total_comments')[:count]

В нем с помощью функции annotate() создаем QuerySet, который агрегирует общее количество комментариев под постов. Сейчас используются функция агрегации Count для сохранения количества комментариев и поле total_comments каждого объекта Post. Отсортируем QuerySet по значению этого поля в убывающем порядке. Используем переменную count для ограничения общего количество возвращаемых объектов.

В дополнение к Count в Django есть такие функции агрегации, как Avg, Max, Min и Sum. О них можно почитать здесь https://docs.djangoproject.com/en/2.0/topics/db/aggregation/.

Отредактируйте шаблон blog/base.html и добавьте следующий код к элементу сайдбара <div> (после {% show_latest_posts 3 %}):

<h3>Most commented posts</h3>  
{% get_most_commented_posts as most_commented_posts %}  
<ul>  
  {% for post in most_commented_posts %}  
  <li>  
    <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>  
  </li>  
  {% endfor %}  
</ul>

Сохраним результат в переменной с помощью аргумента as и имени переменной. Для тега шаблона используем {% get_most_commented_posts as most_commented_posts %} для сохранения результата шаблонного тега в новой переменной most_commented_posts. Затем отобразим посты в виде неотсортированного списка.

Откройте браузер и перезагрузите страницу, чтобы увидеть финальный результат:

Отображения наиболее комментируемых постов

Теперь вы знаете, как самостоятельно создавать шаблонные теги. Подробно о них можно почитать здесь https://docs.djangoproject.com/en/2.0/howto/custom-template-tags/.