Прочитал статью «Фрагментарное кэширование в MVC веб-фреймворках». Статья описывает проблему кеширования фрагмета отображения, а именно проблему полного разделения контроллера и отображения — контроллер отрабатывает полностью до вызова отображения. Если в отображении мы кешируем фрагмент, это ничего не меняет — контроллер-то уже отработал! В статье описан способ этого избежать: сделать запрос данных «ленивым».
Начав писать, как это должно быть сделано правильно, решил написать, как устроены шаблоны Django, чтобы не-джанговодам тоже было понятно.
Как это сделано в Django?
Управляющими элементами шаблонов Django являются переменные, фильтры и теги.
При рендеринге шаблона переменные заменяются на свое значение, вычисленное в контексте вызова. Синтаксис — двойные фигурные скобки — например:
Фильтры служат для простых преобразований переменных. Синтаксис —
При рендеринге шаблона теги, грубо говоря, заменяются на результаты работы ассоциированной с этим тегом функции на питоне. Синтаксис:
Программист может написать свои фильтры и теги, но об этом позже.
Кроме того, есть три специальных тега:
Все выше перечисленное тривиально и в той или иной форме есть в любом движке шаблонов. Теперь перейдем к особенностям Django: на тегах block и
Основная фишка шаблонов Django — наследование. Шаблон может расширять (уточнять) поведение родительского шаблона.
Любой участок шаблона может быть обернут в блочный тег (естественно, что тег не может начинаться перед, а заканчиваться внутри цикла). Блоку дается имя. Например:
При помощи тега extend мы указываем, какой шаблон мы будем уточнять. Расширяя шаблон, мы можем переопредилить любые блоки, которые есть в родительском шаблоне. Все, что находится вне этих блоков, будет пропущено.
Получается мощный механизм, практически исключающий необходимость повторения частей шаблонов. Вкратце это описано в документации (см. ссылки в конце статьи). Давайте разберем реальный пример.
Допустим, мы хотим сделать сайт, содержащий простые страницы и блог.
От верстальщика мы получили макет страницы, содержащий:
Вот как это выглядит:
Для всех указанных элементов мы создаем соответствующие блочные теги.
Простая страница ложится в этот макет — у нее есть только заголовок и тело.
Теперь перейдем к блогу. В блоге хотелось бы добавить правую колонку для вывода списка тегов и последних статей. Возможно мы захотим добавить правую колонку к каким-нибудь другим страницам сайта. Чтобы избежать копирования «двухколоночности», вынесем ее в отдельный шаблон, переопределив тело страницы у базового.
В блоге будет несколько типов страниц:
У всех страниц правая колонка остается неизменной, поэтому разумно сделать базовую страницу для блога, наследуя ее от двухколоночной базовой страницы.
Теперь приведем примеры внутренних страниц блога (все они наследуются от базовой страницы блога).
Список статей:
Статья:
Список статей, у которых есть определенный тег:
В данном случае, мы воспользовались еще одной хитростью. Ведь этот список ничем не отличается от простого списка статей — он просто отфильтрован по дополнительным параметрам. Поэтому мы унаследовали его от списка статей и при перекрытии тела использовали тег
Как видно, каждый шаблон очень конкретен и отвечает только за свою функциональность. Ему нет необходимости знать о всей странице в целом.
Поклонники других шаблонных систем скажут, что для приведенного примера наследование не нужно. Действительно, то же самое можно реализовать, используя теги подстановки (
В нашем примере на странице статьи блога есть 7 блоков. Два из них — логотип и copyright — не нуждаются в данных. Для остальных пяти контроллеру необходимо предоставить шаблону данные. А именно:
Блоков могло быть намного больше, но непосредственное отношение к статье имеют только заголовок и тело статьи. Зачем статье знать, какие данные нужны этим блокам, откуда и как их получить? Абсолютно незачем — это не ее задача. Django предлагает нам следующее решение этой проблемы.
Для каждого из блоков мы можем написать свой тег, состоящий из мини-контроллера и шаблона. Контроллер знает, как получить данные, шаблон — как отобразить. В том месте, где нам необходим блок, мы вставляем его тег — и все! Например, можно вставить список тегов и последних статей на главную страницу. Главной странице нет необходимости что-либо знать о структуре нашего блога — только факт наличия и имена тегов, реализуемых блогом.
Вот пример тега для вывода списка последних статей в блоге:
Еще одним приемуществом такого подхода является то, что данные запрашиваются непосредственно при вставке тега. Если кэшировать несколько тегов, то будут кэшированы результаты их работы — повторно данные запрашиваться не будут! И не надо изобретать велосипеды, как тут ;)
Понятно, что при аккуратном подходе то же самое можно реализовать без наследования и без пользовательских тегов — подключениями и вызовом функций. Главное приемущество Django — это стройная, логичная и стандартная сруктура решения таких проблем.
Ссылки на официальную документацию:
PS: Это перепечатка с моего блога http://dpp.su/. Кстати, там есть еще несколькоменее интересных статей про Django.
Начав писать, как это должно быть сделано правильно, решил написать, как устроены шаблоны Django, чтобы не-джанговодам тоже было понятно.
Как это сделано в Django?
Структура шаблонов Django
Управляющими элементами шаблонов Django являются переменные, фильтры и теги.
При рендеринге шаблона переменные заменяются на свое значение, вычисленное в контексте вызова. Синтаксис — двойные фигурные скобки — например:
{{ title }}
.Фильтры служат для простых преобразований переменных. Синтаксис —
переменная|имя_фильтра:"параметры"
. Фильтр может встречаться как в переменных, так и в качестве параметра тега. Например: {{ title|lowercase }}
.При рендеринге шаблона теги, грубо говоря, заменяются на результаты работы ассоциированной с этим тегом функции на питоне. Синтаксис:
{% тег параметры %}
, например: {% url blog_article slug=article.slug %}
.Программист может написать свои фильтры и теги, но об этом позже.
Кроме того, есть три специальных тега:
include
, block
и 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 %} |
Статья:
{% extend "blog/base.htm" %} {% block title %} {{ article.title }} - {{ block.super }} {% endblock %} {% block content %} {{ article.text }} {% endblock %} |
Список статей, у которых есть определенный тег:
{% extend "blog/index.htm" %} {% block title %} {{ tag.title }} - {{ block.super }} {% endblock %} {% block content %} {{ tag.title }} {{ tag.text }} {{ block.super }} {% endblock %} |
В данном случае, мы воспользовались еще одной хитростью. Ведь этот список ничем не отличается от простого списка статей — он просто отфильтрован по дополнительным параметрам. Поэтому мы унаследовали его от списка статей и при перекрытии тела использовали тег
{{ block.super }}
— вывести все содержимое родительского блока.Как видно, каждый шаблон очень конкретен и отвечает только за свою функциональность. Ему нет необходимости знать о всей странице в целом.
Поклонники других шаблонных систем скажут, что для приведенного примера наследование не нужно. Действительно, то же самое можно реализовать, используя теги подстановки (
include
, ssi
). Вот только логика и структура этих включений будет намного запутаннее. Получится, что статья должна знать, какие блоки будут на ее странице, и предоставлять данные для всех этих блоков. Тут на помощь приходит еще одна особенность 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 — это стройная, логичная и стандартная сруктура решения таких проблем.
Ссылки на официальную документацию:
- djangodocs: template-inheritance
- djangobook: template-inheritance
- djangobook (перевод): наследование шаблонов
PS: Это перепечатка с моего блога http://dpp.su/. Кстати, там есть еще несколько