Один из основных архитектурных принципов шаблонной системы Django — очень ограниченный синтаксис. Шаблон не должен становиться языком программирования и допускать хоть сколько-нибудь интересную логику. Это сделано ради двух вещей:
- чтобы бизнес-логика не растекалась на уровень представления
- чтобы верстальщик или дизайнер не забивал себе голову программированием
В частности в шаблон нельзя включить другой шаблон, если у него есть параметры (сейчас приведу пример). В случаях, когда стандартной шаблонной системы не хвататет для нужд вашего проекта, рекомендуется создавать свой шаблонный тег. Правда это не то, чтобы трудно, но муторно. Но недавно на просторах багтракинга я отрыл замечательный способ, который еще не документирован официально, но сильно облегчает жизнь.
Задачка
На разных страницах интернет-магазин отображается карточка товара, в которой есть обычные данные: название, фотография, пара характеристик. Все это описывается везде одинаковым HTML'ом, простенький шаблон которого выглядит например так:
<div class="good">
<h4>{{ good.name }}</h4>
<p class="photo"><img src="{{ good.get_image_url }}" alt="">
<ul class="specs">
{% for spec in specs %}
<li>{{ spec }}
{% endfor %}
</ul>
</div>
Самый тупой способ включения этого кусочка в шаблоны — copy-paste — отпадает сразу по вполне понятным причинам: любое изменение превращается в нудный и чреватый ошибками ручной поиск и замену. Следовательно надо вынести кусочек в отдельный файл и включать его по месту.
В принципе, для этого в Django есть тег {% include %}
, который как раз просто вставляет текст одного шаблона в другой. Но во вставляемом шаблоне есть пременные (good
и specs
), а значит для того, чтобы отрендерить получившийся шаблон, надо чтобы в него эти переменные были переданы. Вот тут и появляется несколько проблем:
- в родительском шаблоне уже могут быть объекты товара и характеристик, но называются могут по-другому
- в родительском шаблоне уже могут быть переменные
good
иspecs
, но означать могут другое - если этих переменных в шаблоне нет, то придется помнить, что туда надо их передавать, потому что их требует включаемый кусок (а его сразу не видно)
- в шаблон не получится включить несколько блоков для разных объектов, потому что они названы по-разному
В общем, "namespace hell".
Шаблонный тег
В принципе, любую проблему с шаблонами в Django можно решить, написав отдельный шаблонный тег, который научить принимать нужные параметры . По сути это некая питоновская функция, которая возвращает кусок шаблона, и внутри ее можно делать, в общем-то, все что угодно. Плохо только то, что это на самом деле никакая не функция, а целый набор заклинаний. Вот что надо сделать для нашего случая:
- написать функцию, которая будет принимать строку параметров из шаблона
- в функции завернуть параметры в парсерные объекты
- написать класс, который представляет собственно сам тег и принимает в себя эти парсерные объекты
- в методе
render
этого класса:- рассчитать парсерные объекты с текущим контекстом
- загрузить включаемый шаблон
- передать ему рассчитанные переменные
- вернуть отрендеренный HTML
- зарегистрировать функцию в шаблонной системе в виде тега
На самом деле, когда вы работаете с шаблонами, и вся модель приложения лежит в голове, это все действительно не сложно. Зато это дико скучно: ради простого выделения куска шаблона проделывать обезьянью работу по copy-paste'у питоновского кода и замене в нем слов с одних на другие. Это звучит очень похоже на то, от чего хотелось как раз избавиться.
inclusion_tag
inclusion_tag
— это функция, которая делает всю черновую работу по созданию структуры и регистрации тега, оставляя программисту только саму передачу переменных из параметров тега во включаемый шаблон:
from django.core import template # всякие стандартные импорты
register=template.Library()
@register.inclusion_tag('eshop/good')
def good(good,good_specs):
return {
'good':good,
'specs':good_specs,
}
Здесь "eshop/good" — это название включаемого шаблона, название функции "good" становится названием тега. Сама функция в простейшем случае — это просто переименовыватель. Она возвращает то, что будет передано во включаемый шаблон с нужными названиями, независимо от того, как это называлось в родительском шаблоне. В итоге с точки зрения дизайнера это выглядит так:
{% good selected_good short_spec_list %}
Но простое переименование переменных — это не самое интересное, тег ведь волен делать в системе все что угодно. Например можно параметр характеристик сделать опциональным, и когда его нет, передавать во включаемый шаблон какой-нибудь список по умолчанию:
def good(good,good_specs=None):
return {
'good':good,
'specs':good_specs or specs.get_list(...),
}
В общем, inclusion_tag
— это крайне удобная штука, экономит кучу времени и кода. Теперь только надо не пытаться каждый <div>
сделать шаблонным тегом :-).
Комментарии: 8
Пример шаблона кое-что напомнил :)
как всё сложно.. лучше уж смарти со своей многофнкциональностью..
{include file="some_other_template.tpl" var1="val1" var2="val2" var3=$some_local_var}
Вот я все больше убеждаюсь, что не лучше. Эта сложность — есть в системе. Вопрос только в том, на кого и куда ее переместить. Django сознательно убирает все нагруженные логикой решения из шаблона, и чем больше я с этим работаю, тем больше проникаюсь, хотя сначала тоже были те же мысли: "ну и зачем эти искусственные ограничения".
Я когда-то работал со Smarty немного, и напарывался на то самое: когда надо что-то поменять или отладить, ты понятия не имеешь, где что начинать искать. Поэтому я теперь за разделение уровней :-)
фокус в том, что я могу элементарно написать и кастомный тег для смарти (что время от времени и делаю), но зато ВСЯ логика представления может быть выражена на смарти и я имею возможность не думать о ней при написании кода
Вот я бы как раз параметризацию шаблона отнес на уровень бизнес-логики. Презентация — это само содержимое того шаблона.
Впрочем, вопрос сильно зависит от контекста, спорить не буду :-). Я хотел, в общем-то, просто поделиться, как это в Django делается, для тех, кто в это упрется.
Это очень опасно. Я однажды неправильно разделил логику представления и бизнес-логику. Потом долго мучался, потому что смарти-шаблоны разрослись больше соответствующего php-кода. С тех пор думаю, чем меньше логики в шаблонах, тем лучше.
Подробное сравнение TurboGears и Django:
http://community.livejournal.com/ru_python/38067.html?thread=242099#t242099
Я занёс в мемориз.
Есть такой шаблонизатор — twig, созданный на основе django templates engine. В нем для подобных задач созданы макросы, напоминают html хелперы в mvc фреимворках. Оч удобно и красиво получается. Статье конечно уже 5 лет, но мало ли...