Курс по Django: https://stepik.org/a/183363
Архив проекта: lesson-11-coolsite.zip
Давайте вернемся
к проекту нашего сайта, откроем файл women/views.py и вспомним, что
функции представления index и show_category имеют
дублирование кода, в частности, они обе считывают список категорий и передают
его шаблону. Пришло время это поправить. Существует несколько способов это
сделать. Можно воспользоваться классами представлений и общий код вынести в
отдельный базовый класс. Но пока у нас функции мы воспользуемся механизмом
создания собственных шаблонных тегов. И с их помощью решим данную задачу.
Django позволяет
использовать два вида пользовательских тегов:
- simple
tags – простые
теги;
- inclusion
tags – включающие
теги.
Подробную
русскоязычную документацию по ним можно посмотреть по ссылке:
https://djbook.ru/rel3.0/howto/custom-template-tags.html
Simple Tags
Вначале мы
создадим простой тег, который будет загружать категории из БД и использоваться
непосредственно в шаблоне. Согласно документации теги должны располагаться в
подкаталоге templatetags каталога
приложения women и являться
пакетом, то есть, содержать файл __init__.py. Сделаем это. Далее,
нам нужно добавить в эту папку еще один python-файл, в котором
и будем прописывать логику нового тега. Файл назовем каким-нибудь понятным
именем, например, women_tags. Импортируем
сюда модуль template для работы с
шаблонами и наши модели:
from django import template
from women.models import *
Следующим шагом
нам нужно создать экземпляр класса Library, через который происходит регистрация
собственных шаблонных тегов:
register = template.Library()
И, далее, определим
функцию, которая будет выполняться при вызове нашего тега из шаблона. Так как
нам нужны будут списки категорий, то функция будет достаточно простой:
def get_categories():
return Category.objects.all()
Название функции
get_categories мы придумываем
сами. Теперь, чтобы связать эту функцию с тегом, или, превратить эту функцию в
тег, используется специальный декоратор, доступный через переменную register:
@register.simple_tag()
def get_categories():
return Category.objects.all()
Все, мы только что
создали свой простой пользовательский тег для использования его в шаблонах.
Давайте им
воспользуемся. Перейдем в базовый шаблон base.html и вначале
выполним загрузку тегов, определенных в файле women_tags:
После этого в
шаблоне (и во всех его дочерних шаблонах) доступен тег по имени get_categories. Однако, если
сейчас обновить главную страницу, то получим ошибку, что women_tags не
зарегистрирован. Это связано с тем, что после добавления нового пакета templatetags и файла women_tags.py необходимо
перезапустить тестовый веб-сервер, чтобы он подхватил изменения в проекте
сайта. После перезагрузки никаких ошибок не появляется.
Далее, в месте
вывода рубрик давайте просто запишем наш новый сформированный тег:
и при обновлении
главной страницы увидим отображение объекта QuerySet. Это говорит о
том, что все работает, так и должно быть, т.к. именно этот объект и возвращает
данный тег. Но как нам теперь перебрать элементы этого объекта? Подставить в
цикл тег get_categories мы не можем, т.к. это не переменная, а тег шаблона. Для
этого в Django в тегах
шаблонов можно использовать специальное ключевое слово as, которое
сформирует ссылку на данные тега. В нашем случае это можно записать так:
{% get_categories as categories %}
Сформируется
переменная categories, которую уже
можно использовать в теге цикла for. Кстати, при обновлении страницы, тег get_categories уже не будет
отображаться на странице, т.к. изменилось его поведение – данные передаются в
переменную (точнее ссылку). Подставим переменную categories в цикл и
обновим страницу. У нас визуально ничего не изменилось, но прежний параметр cats теперь стал не
нужен и его можно не передавать в шаблоны. Значит, в функциях представления index и show_category его можно
убрать. Таким образом, мы с помощью простого пользовательского тега смогли
исключить дублирующий параметр и строчки кода.
Всегда ли тег
будет называться по имени функции? В действительности, мы можем указать любое
другое. Для этого в декораторе register.simple_tag достаточно
указать параметр name и через него определить желаемое имя тега,
например, так:
@register.simple_tag(name='getcats')
def get_categories():
return Category.objects.all()
И, далее, в
шаблоне 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 = Category.objects.all()
return {"cats": cats}
Здесь у нас в
функции происходит чтение всех рубрик из БД, а затем, возвращается словарь с
этими данными. Словарь используется как набор параметров для шаблона, указанный
в параметре декоратора inclusion_tag. То есть, в
шаблоне list_categories.html будет доступна
директива cats со списком всех
рубрик. Именно этот сформированный шаблон и будет возвращаться данным тегом.
Осталось
прописать сам шаблон. Разместим его среди всех остальных шаблонов (хотя, при
необходимости, можно создать отдельный подкаталог и размещать шаблоны тегов в
нем). И скопируем в него следующий фрагмент шаблона из base.html:
{% for c in categories %}
{% if c.pk == cat_selected %}
<li class="selected">{{c.name}}</li>
{% else %}
<li><a href="{{ c.get_absolute_url }}">{{c.name}}</a></li>
{% endif %}
{% endfor %}
Здесь переменную
categories заменим на cats, так как именно
ее мы передаем как параметр этому шаблону, и наш тег готов.
Осталось вызвать
его в базовом шаблоне base.html и вместо вывода
рубрик, записать тег:
Обновляем
главную страницу сайта и видим, что все работает – рубрики выводятся с помощью
нашего включающего тега. (При необходимости перезапустите тестовый веб-сервер).
Правда, в шаблоне list_categories.html отсутствует
директива cat_selected, но мы это сейчас поправим.
Передача параметров пользовательским тегам
Все наши теги
могут принимать некоторые параметры, которые расширяют их функциональность. О
чем здесь речь? Смотрите, для начала обратимся к простому тегу и изменим его
функцию, следующим образом:
@register.simple_tag(name='getcats')
def get_categories(filter=None):
if not filter:
return Category.objects.all()
else:
return Category.objects.filter(pk=filter)
То есть, мы
добавили один именованный параметр filter, который по
умолчанию равен None и функция возвращает все категории. Если же указать
какое-либо числовое значение, то будем получать категорию с указанным идентификатором.
Как теперь все это можно использовать в шалонах? Давайте перейдем в base.html и для начала
просто вызовем этот тег:
При обновлении
главной страницы, увидим список всех категорий. Это пример вызова тега без
параметров. А теперь укажем параметр, например, так:
Увидим первую
рубрику. Или, можно указать его так:
Увидим только
вторую рубрику. Вот так определяются и используются параметры у
пользовательских тегов.
Давайте
проделаем похожую операцию, но с включающим тегом. Определим у него два
именованных параметра:
@register.inclusion_tag('women/list_categories.html')
def show_categories(sort=None, cat_selected=0):
if not sort:
cats = Category.objects.all()
else:
cats = Category.objects.order_by(sort)
return {"cats": cats, "cat_selected": cat_selected}
Первый будет
выполнять сортировку по указанному полю, а второй – недостающий параметр cat_selected. Далее, в
функции делаем выборку в соответствии с параметром sort, а затем,
передаем шаблону дополнительный параметр cat_selected.
Все, теперь наш
включающий тег может принимать два параметра. В шаблоне base.html пропишем его
вызов, например, так:
{% show_categories '-name' cat_selected %}
Здесь первый
параметр –name будет определять
сортировку по названию рубрик, а второй – просто передает нужный для обработки
данных параметр cat_selected. Также можно указать только
какой-либо один параметр, например, так:
{% show_categories cat_selected=cat_selected %}
или так:
{% show_categories sort='name' %}
Но вернем вариант:
{% show_categories cat_selected=cat_selected %}
чтобы тег
работал как положено. Как видите, в Django достаточно
просто создавать и использовать пользовательские теги. Попробуйте
самостоятельно сделать отображение главного меню через пользовательские теги,
чтобы исключить и это дублирование в функциях.
Курс по Django: https://stepik.org/a/183363