Перейти к содержимому

Шаблоны Django. Наследование.

    Прочитал статью «Фрагментарное кэширование в MVC веб-фреймворках». Статья описывает проблему кеширования фрагмета отображения, а именно проблему полного разделения контроллера и отображения — контроллер отрабатывает полностью до вызова отображения. Если в отображении мы кешируем фрагмент, это ничего не меняет — контроллер-то уже отработал! В статье описан способ этого избежать: сделать запрос данных «ленивым».

    Начав писать, как это должно быть сделано правильно, решил написать, как устроены шаблоны Django, чтобы не-джанговодам тоже было понятно.

    Как это сделано в Django?

    Структура шаблонов Django

    Управляющими элементами шаблонов Django являются переменные, фильтры и теги.

    При рендеринге шаблона переменные заменяются на свое значение, вычисленное в контексте вызова. Синтаксис — двойные фигурные скобки — например: {{ title }}.

    Фильтры служат для простых преобразований переменных. Синтаксис — переменная|имя_фильтра:"параметры". Фильтр может встречаться как в переменных, так и в качестве параметра тега. Например: {{ title|lowercase }}.

    При рендеринге шаблона теги, грубо говоря, заменяются на результаты работы ассоциированной с этим тегом функции на питоне. Синтаксис: {% тег параметры %}, например: {% url blog_article slug=article.slug %}.

    Программист может написать свои фильтры и теги, но об этом позже.

    Кроме того, есть три специальных тега: includeblock и extend. Тег include подставляет запрошенный шаблон (отрендеренный в текущем контексте).

    Все выше перечисленное тривиально и в той или иной форме есть в любом движке шаблонов. Теперь перейдем к особенностям Django: на тегах block и extend строится наследование шаблонов. Остановимся на них подробнее.

    Наследование шаблонов

    Основная фишка шаблонов Django — наследование. Шаблон может расширять (уточнять) поведение родительского шаблона.

    Любой участок шаблона может быть обернут в блочный тег (естественно, что тег не может начинаться перед, а заканчиваться внутри цикла). Блоку дается имя. Например:{% block content %} тело блока {% endblock %}

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

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

    Пример наследования шаблонов

    Допустим, мы хотим сделать сайт, содержащий простые страницы и блог.

    От верстальщика мы получили макет страницы, содержащий:

    • шапку (логотип, заголовок страницы, меню);
    • тело страницы;
    • и «подвал» с информацией о правах распространения.
    {% block head %} {% block title %}{% endblock %} {% block menu %}{% endblock %} {% endblock %} {% block page %} {% block content %} {% endblock %} {% endblock %} {% block footer %} {% block copyright %} {% endblock %} {% endblock %}

    Для всех указанных элементов мы создаем соответствующие блочные теги.

    Простая страница ложится в этот макет — у нее есть только заголовок и тело.

    Теперь перейдем к блогу. В блоге хотелось бы добавить правую колонку для вывода списка тегов и последних статей. Возможно мы захотим добавить правую колонку к каким-нибудь другим страницам сайта. Чтобы избежать копирования «двухколоночности», вынесем ее в отдельный шаблон, переопределив тело страницы у базового.

    {% extend «base.htm» %} {% block page %} {% block content %} {% endblock %} {% block sidebar %} {% endblock %} {% endblock %}

    В блоге будет несколько типов страниц:

    • список статей;
    • статья;
    • список тегов;
    • список статей, у которых есть определенный тег;
    • пр.

    У всех страниц правая колонка остается неизменной, поэтому разумно сделать базовую страницу для блога, наследуя ее от двухколоночной базовой страницы.

    {% extend «base_2col.htm» %} {% block title %} Блог {% endblock %} {% block sidebar %} {% block tags %} {% endblock %} {% block recent %} {% endblock %} {% endblock %}

    Теперь приведем примеры внутренних страниц блога (все они наследуются от базовой страницы блога).

    {% extend «blog/base.htm» %} {% block content %} {% for article in article_list %} «a href=»{% url article_view article.id %}»» {{ article.title }} «/a» {% endfor %} {% endblock %}

    В данном случае, мы воспользовались еще одной хитростью. Ведь этот список ничем не отличается от простого списка статей — он просто отфильтрован по дополнительным параметрам. Поэтому мы унаследовали его от списка статей и при перекрытии тела использовали тег {{ block.super }} — вывести все содержимое родительского блока.

    Как видно, каждый шаблон очень конкретен и отвечает только за свою функциональность. Ему нет необходимости знать о всей странице в целом.

    Поклонники других шаблонных систем скажут, что для приведенного примера наследование не нужно. Действительно, то же самое можно реализовать, используя теги подстановки (includessi). Вот только логика и структура этих включений будет намного запутаннее. Получится, что статья должна знать, какие блоки будут на ее странице, и предоставлять данные для всех этих блоков. Тут на помощь приходит еще одна особенность Django — пользовательские теги.

    Пользовательские теги

    В нашем примере на странице статьи блога есть 7 блоков. Два из них — логотип и copyright — не нуждаются в данных. Для остальных пяти контроллеру необходимо предоставить шаблону данные. А именно:

    • заголовок статьи;
    • меню;
    • тело статьи;
    • список тегов;
    • последние статьи.

    Блоков могло быть намного больше, но непосредственное отношение к статье имеют только заголовок и тело статьи. Зачем статье знать, какие данные нужны этим блокам, откуда и как их получить? Абсолютно незачем — это не ее задача. Django предлагает нам следующее решение этой проблемы.

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

    Вот пример тега для вывода списка последних статей в блоге:# тег @register.inclusion_tag(‘blog/article_recent_list.htm’) def blog_recent_articles_list(): return { ‘article_list’: Article.objects.filter(public=True)[:10], } # шаблон «h4»Последние статьи«/h4» «ul class=»links»» {% for article in article_list %} «li»«a href=»{% url article %}»»{{ article.title}}«/a»«/li» {% endfor %} «/ul»

    Еще одним приемуществом такого подхода является то, что данные запрашиваются непосредственно при вставке тега. Если кэшировать несколько тегов, то будут кэшированы результаты их работы — повторно данные запрашиваться не будут! И не надо изобретать велосипеды, как тут 😉

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

    Ссылки на официальную документацию:

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

    Ваш адрес email не будет опубликован. Обязательные поля помечены *