Один из основных архитектурных принципов шаблонной системы 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), а значит для того, чтобы отрендерить получившийся шаблон, надо чтобы в него эти переменные были переданы. Вот тут и появляется несколько проблем:

В общем, "namespace hell".

Шаблонный тег

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

На самом деле, когда вы работаете с шаблонами, и вся модель приложения лежит в голове, это все действительно не сложно. Зато это дико скучно: ради простого выделения куска шаблона проделывать обезьянью работу по 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

  1. Сергей Петров

    Пример шаблона кое-что напомнил :)

  2. Алексей Захлестин

    как всё сложно.. лучше уж смарти со своей многофнкциональностью..
    {include file="some_other_template.tpl" var1="val1" var2="val2" var3=$some_local_var}

  3. Иван Сагалаев

    Вот я все больше убеждаюсь, что не лучше. Эта сложность — есть в системе. Вопрос только в том, на кого и куда ее переместить. Django сознательно убирает все нагруженные логикой решения из шаблона, и чем больше я с этим работаю, тем больше проникаюсь, хотя сначала тоже были те же мысли: "ну и зачем эти искусственные ограничения".

    Я когда-то работал со Smarty немного, и напарывался на то самое: когда надо что-то поменять или отладить, ты понятия не имеешь, где что начинать искать. Поэтому я теперь за разделение уровней :-)

  4. Алексей Захлестин

    фокус в том, что я могу элементарно написать и кастомный тег для смарти (что время от времени и делаю), но зато ВСЯ логика представления может быть выражена на смарти и я имею возможность не думать о ней при написании кода

  5. Иван Сагалаев

    Вот я бы как раз параметризацию шаблона отнес на уровень бизнес-логики. Презентация — это само содержимое того шаблона.

    Впрочем, вопрос сильно зависит от контекста, спорить не буду :-). Я хотел, в общем-то, просто поделиться, как это в Django делается, для тех, кто в это упрется.

  6. kropp

    но зато ВСЯ логика представления может быть выражена на смарти и я имею возможность не думать о ней при написании кода

    Это очень опасно. Я однажды неправильно разделил логику представления и бизнес-логику. Потом долго мучался, потому что смарти-шаблоны разрослись больше соответствующего php-кода. С тех пор думаю, чем меньше логики в шаблонах, тем лучше.

  7. MkDir

    Подробное сравнение TurboGears и Django:
    http://community.livejournal.com/ru_python/38067.html?thread=242099#t242099
    Я занёс в мемориз.

  8. mars

    Есть такой шаблонизатор — twig, созданный на основе django templates engine. В нем для подобных задач созданы макросы, напоминают html хелперы в mvc фреимворках. Оч удобно и красиво получается. Статье конечно уже 5 лет, но мало ли...

Добавить комментарий