Пользовательские теги шаблонов. Декораторы simple_tag и inclusion_tag

Курс по Django: https://stepik.org/a/183363

Архив проекта: 16_sitewomen.zip

На этом занятии мы с вами увидим, как можно создавать свои собственные шаблонные теги и использовать их при формировании HTML-страниц. Для этого Django позволяет использовать два вида пользовательских тегов:

  • simple tags – простые теги;
  • inclusion tags – включающие теги.

Подробную документацию по ним можно посмотреть по ссылке:

https://docs.djangoproject.com/en/4.2/howto/custom-template-tags/

Simple Tags

Вначале мы создадим простой тег, который будет возвращать список категорий и использоваться непосредственно в шаблоне. Согласно документации теги должны располагаться в подкаталоге templatetags каталога приложения women и являться пакетом, то есть, содержать файл __init__.py. Сделаем это. Далее, нам нужно добавить в эту папку еще один python-файл, в котором будем прописывать логику нового тега. Файл назовем каким-нибудь понятным именем, например, women_tags. Импортируем сюда модуль template для работы с шаблонами и модуль views:

from django import template
import women.views as views

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

register = template.Library()

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

def get_categories():
    return views.cats_db

Название функции get_categories мы придумываем сами. Соответственно, в файле women/views.py мы пропишем коллекцию cats_db следующим образом:

cats_db = [
    {'id': 1, 'name': 'Актрисы'},
    {'id': 2, 'name': 'Певицы'},
    {'id': 3, 'name': 'Спортсменки'},
]

Теперь, чтобы связать функцию get_categories с тегом, или, превратить эту функцию в тег, используется специальный декоратор, доступный через переменную register:

@register.simple_tag()
def get_categories():
    return views.cats_db

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

Давайте им воспользуемся. Перейдем в базовый шаблон base.html и вначале выполним загрузку тегов, определенных в файле women_tags:

{% load women_tags %}

После этого в шаблоне (и во всех его дочерних шаблонах) доступен тег по имени get_categories. Однако если сейчас обновить главную страницу, то получим ошибку, что women_tags не зарегистрирован. Это связано с тем, что после добавления нового пакета templatetags и файла women_tags.py необходимо перезапустить тестовый веб-сервер, чтобы он подхватил изменения в проекте сайта. После перезагрузки никаких ошибок не появляется.

Далее, в месте вывода рубрик давайте просто запишем наш новый сформированный тег:

{% get_categories %}

и при обновлении главной страницы увидим отображение списка. Это говорит о том, что все работает, тег возвращает нужные данные. Но как нам теперь перебрать элементы этого объекта? Подставить в цикл тег get_categories мы не можем, т.к. это не переменная, а тег шаблона. Для этого в Django в тегах можно использовать специальное ключевое слово as, которое сформирует ссылку на данные тега. В нашем случае это можно записать так:

{% get_categories as categories %}

Сформируется переменная categories, которую уже можно использовать в теге цикла for. Кстати, при обновлении страницы, тег get_categories уже не будет отображаться на странице, т.к. изменилось его поведение – данные передаются в переменную (точнее ссылку).

Теперь мы можем перебрать список categories и отобразить категории в шаблоне:

         {% get_categories as categories %}
 
         <ul id="leftchapters">
                   <li class="selected">Все категории</li>
 
                   {% for cat in categories %}
                            <li><a href="#">{{cat.name}}</a></li>
                   {% endfor %}
                   
                   <li class="share">
                   <p >Наш канал</p>
                   <a class="share-yt" href="..." target="_blank" rel="nofollow"></a>
                   </li>
         </ul>

Давайте добавим URL-адреса для категорий. Для этого в файле urls.py пропишем следующий маршрут с именем category:

urlpatterns = [
    path('', views.index, name='home'),
    ...
    path('category/<int:cat_id>/', views.show_category, name='category'),
]

И добавим функцию представления show_category для этого маршрута в файле views.py:

def show_category(request, cat_id):
    """Функция-заглушка"""
    return index(request)

Пока она у нас ничего делать не будет. После всех этих действий в шаблоне base.html URL-адреса для категорий можно сформировать с помощью тега url следующим образом:

{% for cat in categories %}
         <li><a href="{% url 'category' cat.id %}">{{cat.name}}</a></li>
{% endfor %}

Все, с помощью простого пользовательского тега мы получаем список категорий и отображаем их в HTML-документе. Также, если нам нужно определить другое имя тега get_categories, то для этого в декораторе register.simple_tag достаточно указать параметр name с другим именем, например, так:

@register.simple_tag(name='getcats')
def get_categories():
    return views.cats_db

И, далее, в шаблоне base.html следует использовать имя 'getcats':

{% getcats as categories %}

Как видите, все достаточно просто.

Inclusion Tags

Второй тип пользовательских тегов – включающий тег, позволяет дополнительно формировать свой собственный шаблон на основе некоторых данных и возвращать фрагмент HTML-страницы. Давайте посмотрим, как с ним можно работать.

Сначала в файле women_tags.py добавим функцию для реализации этого второго тега и, так как она будет возвращать полноценный шаблон, то назовем ее show_categories:

@register.inclusion_tag('women/list_categories.html')
def show_categories():
    cats = views.cats_db
    return {"cats": cats}

Здесь в функции происходит формирование и возврат словаря с необходимыми данными для шаблона list_categories.html. То есть, в шаблоне list_categories.html будет доступна переменная cats со списком всех рубрик. Именно этот сформированный шаблон и будет возвращаться данным тегом.

Осталось прописать сам шаблон. Разместим его среди всех остальных шаблонов (хотя, при необходимости, можно создать отдельный подкаталог и размещать шаблоны тегов в нем). И скопируем в него следующий фрагмент шаблона из base.html:

{% for cat in cats %}
         <li><a href="{% url 'category' cat.id %}">{{cat.name}}</a></li>
{% endfor %}

Здесь переменную categories заменим на cats, так как именно ее мы передаем как параметр этому шаблону, и наш тег готов. Осталось вызвать его в базовом шаблоне base.html и вместо вывода рубрик, записать тег:

{% show_categories %}

Обновляем главную страницу сайта и видим, что все работает – рубрики выводятся с помощью нашего включающего тега. (При необходимости перезапустите тестовый веб-сервер).

Передача параметров пользовательским тегам

Все наши теги могут принимать некоторые параметры, которые расширяют их функциональность. Давайте этим воспользуемся и дополнительно будем передавать параметр с номером выбранной рубрики. Для этого в функции тега show_categories пропишем параметр cat_selected:

@register.inclusion_tag('women/list_categories.html')
def show_categories(cat_selected=0):
    cats = views.cats_db
    return {"cats": cats, "cat_selected": cat_selected}

И изменим шаблон list_categories.html следующим образом:

{% for cat in cats %}
         {% if cat.id == cat_selected %}
                   <li class="selected">{{cat.name}}</li>
         {% else %}
                   <li><a href="{% url 'category' cat.id %}">{{cat.name}}</a></li>
         {% endif %}
{% endfor %}

Теперь наш тег show_categories может принимать один параметр. В шаблоне base.html пропишем его вызов следующим образом:

{% show_categories cat_selected %}

или так:

{% show_categories cat_selected_id=cat_selected %}

Соответственно параметр cat_selected необходимо передать в шаблон base.html. Для этого откорректируем функцию представления index:

def index(request):
    data = {
        'title': 'Главная страница',
        'menu': menu,
        'posts': data_db,
        'cat_selected': 0,   # не обязательная строчка
    }
 
    return render(request, 'women/index.html', context=data)

И функцию представления show_category:

def show_category(request, cat_id):
    data = {
        'title': 'Отображение по рубрикам',
        'menu': menu,
        'posts': data_db,
        'cat_selected': cat_id,
    }
 
    return render(request, 'women/index.html', context=data)

Если теперь запустить веб-сервер и щелкать по рубрикам, то они будут подсвечиваться выделенным синим цветом. Однако первая рубрика всегда остается выделенной. Давайте это тоже поправим и в шаблоне base.html пропишем следующее условие:

         <ul id="leftchapters">
{% if cat_selected == 0 or cat_selected is None %}
                   <li class="selected">Все категории</li>
{% else %}
                   <li><a href="{% url 'home' %}">Все категории</a></li>
{% endif %}
                   ...
         </ul>

Как видите, в Django достаточно просто можно создавать и использовать пользовательские теги.

Курс по Django: https://stepik.org/a/183363

Видео по теме