Архитектура шаблонов Django

Я решил поделиться структурой, которую я использую для шаблонов. Я ее использую как в новых проектах, так и при реструкторизации уже существующих приложений. Этот процесс направлен на получение повторно используемых приложений с сохранением гибкости. Эта структура родилась постепенно в ходе многих тысяч часов работы с шаблонами и является удобной для меня. Ваши предпочтения могут отличаться. И вы же знакомы с организацией шаблонов в поддиректориях приложений? Верно?

Перед тем, как начать, я хотел бы показать одно из моих Django приложений с шаблонами. Gazette - приложение-блог (да, еще одно), над которым я работал и которое я буду использовать в качестве примера в данной статье.

gazette/
    **init**.py
    models.py
    views.py
    static/
    ...
    templates/
        gazette/
            __base.html
            __l_single_col.html
            __l_right_sidebar.html
            __l_left_sidebar.html
            _article_full.html
            _article_summary.html
            _author_list.html
            _author_name.html
            _category_list.html
            _navigation.html
            _tag_list.html
            article_detail.html
            article_list.html
            author_list.html
            category_list.html
            tag_list.html

Знайте ваши шаблоны

Первый принцип процесса разработки моих шаблонов пришел прямиком из The Zen of Python:

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

Расширяемые шаблоны

Расширяемые шаблоны формируют основную структуру ваших HTML страниц. Это шаблоны, основная цель которых быть расширенными другими шаблонами с помощью тега {% extends %}. Чтоб избежать перемешивания расширяемых шаблонов с шаблонами страниц я принял за правило никогда не вызывать расширяемые шаблоны напрямую из кода представлений (view) и никогда не расширять шаблоны, которые не являются расширяемыми. По соглашению о наименовании, я начинаю все свои расширяемые шаблоны с двойного подчеркивания: __base.html. Самым важным шаблоном является __base.html, который содержит скелет HTML для всех ващих страниц. Обычно этот шаблон делается минимальным. У меня он обычно выглядит следующим образом:

{% load staticfiles i18n %}
<header class="navbar navbar-fixed-top">
    {% include "gazette/_navigation.html" %}
</header>
<div id="main" role="main">
    <div class="container">
        {% block content %} {% endblock %}
    </div>
</div>

Как вы можете заметить, я использую Bootstrap для новых проектов, но этот же подход справедлив и для проектов без Bootstrap. Когда я пишу базовый шаблон, я стараюсь делать это осторожно. Я думаю: “Когда другой программист (включая меня через полгода) прийдет в этот проект и ему понадобится создать новый шаблон, могу ли я быть уверенным, что он сможет сделать любой шаблон не меняя базовый”. Результат оборачивается большим кол-вом тегов {% block %}, которые можно переопределить или расширить при необходимости. О теге {% block %} поговорим чуть позже. Если у вас очень простое приложение, то вам многое не нужно из того, что может предоставить __base.html для вашей структуры. И вы можете перейти к написанию шаблонов страниц и включаемых шаблонов. Но если ваше приложение достаточно сложное, то вам может понадобиться создать несколько различных структурных шаблонов (layout templates). В Gazette я использую три структурных шаблона, имена которых начинаются с __l_ (и снова их назначение определяется мгновенно из названия): __l_single_col.html, __l_right_sidebar.html, __l_left_sidebar.html. Я храню их в одной директории с остальными шаблонами согласно принципу:

Плоское лучше, чем вложенное

С другой стороны, если вам необходимо более трех шаблонов, то вы можете поместить их в поддиректорию layouts/. Когда я работаю над проектов с очень сложной сеткой, то часто создаю такую поддиректорию с шаблонами: layouts/100.html, layouts/25_75.html, layouts/50_50.html и т.д. Например один из моих структурных шаблонов, __l_right_sidebar.html :

{% extends "gazette/__base.html" %}

{% block content %}
    <div class="row">
        <div class="span8">
            {% block main_col %}{% endblock %}
        </div>
        <div class="span4">
            {% block side_col %}{% endblock %}
        </div>
    </div>
{% endblock %}

Это очень простой шаблон - настолько простой, что может показаться, что он и не нужен. Но, поверьте мне, этот маленький шаблон предоставляет множество преимуществ при создании ваших шаблонов страниц.

Включаемые шаблоны

Включаемые шаблоны - это шаблоны, которые планируете включать в шаблоны страниц с помощью тега {% include %}. Как и с расширяемыми шаблонами я строго слежу за применением включаемых шаблонов. Вы можете рассматривать их и как некие блоки, из которых строится ваш сайт, собираясь в шаблоне страницы. Есть две основные причины сделать часть вашего сайте включаемым:

  • вы планируете использовать этот модуль повторно на многих страницах и вы хотите сохранить чистоту кода.
  • вы хотите предоставить другому разработчику возможность переопределить этот модуль в вашем приложении без вмешательства в структуру вашего шаблона.

И как всегда, разрабатывайте продуманно: будьте готовы, что другие разработчики (включая вас через полгода) захотят изменить или переопределить ваши шаблоны так, как вы даже не можете представить. Но следите за балансом. Слишком мало включаемых блоков дает мало возможностей для модификации, что раздражает, а слишком много - создает спагетти код, в котором сложно ориентироваться.

Шаблоны страниц

Шаблоны страниц - ядро вашего приложения. Но если вы перед этим создали несколько хороших расширяемых шаблонов и возможно даже несколько включаемых, то вы заметите насколько просто связать все вместе. Это просто клей, который связывает ваши шаблоны вместе. Ваши шаблоны страниц - это именно то, что вызывается из кода представлений (views). Они гармонично связывают расширяемые и включаемые вместе. Например, article_detail.html :

{% extends "gazette/__l_right_sidebar.html" %}

{% block title %}{{ article.title }} | {{ block.super }}{% endblock %}

{% block main_col %}
    {% include "gazette/_article_full.html" %}
{% endblock %}

{% block side_col %}
    {% include "gazette/_tag_list.html" with tags=article.tags.all %}
    {% include "gazette/_category_list.html" with categories=article.categories.all %}
{% endblock %}

Как вы видете, эта страница использует __l_right_sidebar.html. Главная колонка содержит полный текст статьи. Боковая колонка содержит список тегов и список категорий. Этот шаблон настолько легко читаем, насколько возможно. Я упоминал, что выделение отдельного уровня шаблонов структуры приведет к увеличению возможностей и это так. Если вы решите, что список тегов и категорий лучше расположить в левой колонке, то вам достаточно будет лишь поменять первую строку шаблона article_detail.html на:

{% extends "gazette/__l_left_sidebar.html" %}

и содержимое переместится именно туда, куда надо. Дальше больше. Если другой разработчик предпочитает структуру 75% - 25% вместо 66% - 33%, которую использую я в __l_right_sidebar.html, то от может переопределить шаблон:

{% extends "gazette/__base.html" %}

{% block content %}
    <div class="row">
        <div class="span9">{# this is now 75% of the page #}
            {% block main_col %}{% endblock %}
        </div>
        <div class="span3">{# this is now 25% of the page #}
            {% block side_col %}{% endblock %}
        </div>
    </div>
{% endblock %}

и все страницы, которые его используют, изменятся нужным образом. Согласно принятому соглашению о названиях, когда это возможно, я стараюсь следовать соглашению об именях, принятому в Django generic views, т.е. _list.html, _detail.html, _form.html и т.д. Если шаблон не попадает под эти категории, то просто старайтесь давать ему название, четко определяющее то представление (view), к которому он относится.

Заметки о {% block %} и {{ block.super }}

Система наследования шаблонов в Django удивительно мощная и тут очень просто увлечься. Я часто вижу как в базовых шаблонах (т.е. расширяемых) разработчики пишут контент по-умолчанию в блоках:

{% block main %}
    This is content for the front page of my application.
{% endblock %}

предполагая, что он будет переопределен при расширении данного шаблона в других страницах. Я хочу предостеречь вас от использования этого подхода, т.к. он смешивает ваши расширяемые шаблоны с шаблонами страниц и становиться сложнее находить то месно, которое нужно изменить в каждом конкретном случае. (“Где это ? В frontpage.html или в base.html?”). Вместо этого рассматривайте содержимое блоков в расширяемом шаблоне как текст, который может быть использован в разных шаблонах страниц с помощью {{ block.super }}. Простейший пример - это элемент страницы < title > . Я часто вижу следующую структуру в базовом шаблоне:

<title>
    {% block page_title %}{% endblock %} Site Title
</title>

Это не только кажется мне не элегантным (Куда нужно поместить разделитель между заголовком страницы и заголовком сайта, т.е. “Заголовок страницы | Заголовок сайта” - в базовый шаблон или в шаблон страницы? А как быть со страницами, которым не нужен заголовок ?), но и ограничивает разработчика в том, что он может сделать без внесения изменений в базовый шаблон. И я гораздо реже вижу более простую структуру:

<title>{% block title %}Site Title{% endblock %}</title>

Аналогично с css или другими включениями:

<link rel="stylesheet" type="text/css" href="base.css" />
{% block styles %}
{% endblock %}

или, боже упаси,

{% block styles %}
    <link rel="stylesheet" type="text/css" href="base.css" />
{% endblock %}
{% block extra_styles %}
{% endblock %}

Это запутаннее и избыточнее, чем простое:

{% block styles %}
    <link rel="stylesheet" type="text/css" href="base.css" />
{% endblock %}

Позже в ваших шаблонах страниц вы можете добавить другие стили следующим образом:

{% block styles %}
    {{ block.super }}
    <link rel="stylesheet" type="text/css" href="specific_page.css" />
{% endblock %}

Аналогично и с заголовком:

<title>{% block title %}Specific Page | {{ block.super }}{% endblock %}</title>

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

Несколько заметок на последок

Интернационализация

Пожалуйста, используйте интернационализацию ваших шаблонов. Особенно, если вы пишите приложения для повторного использования. Это очень просто с использованием тегов {% trans %} и {% blocktrans %}.

{% include %} и {% with %}

Довольно долгое время я писал ужасный код наподобее этого:

{% with article.categories.all as categories %}
    {% include "gazette/_category_list.html" %}
{% endwith %}

До тех пор, пока не заметил [удобный синтаксис], который доступен с версии Django 1.3:

{% include "gazette/_category_list.html" with categories=article.categories.all %}

Для дополнительной инкапсуляции, попробуйте передать в ваши включаемые шаблоны эксклюзивный контекст:

{% include "gazette/_category_list.html" with categories=article.categories.all only %}

Закрывающиеся теги HTML и язык шаблона Django по своей природе - вложенный, что иногда снижает читаемость.

Особенно при работе с большими блоками. Язык шаблонов Django имеет дополнительный необязательный синтаксис, который помогает отслеживать ваши блоки:

{% block camelot %}
    Pretend this is so much content that
    you lose track of what block you're
    in before the end.
{% endblock camelot %}

А в других случаях? Я часто использую комментарии для имитации этого для других тегов шаблона и HTML:

<div class="article-content">
    A very long article.
</div>{# /.article-content #}

что, как я заметил, сильно повышает наглядность кода.

Обрабатывайте HTML и теги шаблона одинаково

Я всегда считал, что отступы для HTML тегов должны обрабатываться отдельно от отступов тегов шаблона. Возможно это ошибочное мнение было основано на том, что структура результирующего HTML кода имеет большее значение. Это все приводило к тому, что большое количество кода имело вид:

{% block navigation %}
<nav id="navigation">
{% with bookpage.book.pages.all as bookpages %}
    <ul class="book-page-list">
{% for page in bookpages %}
{% if page != bookpage %}
        <li><a href="{{ page.get_absolute_url }}">{{ page.name }}</a></li>
{% else %}
        <li class="active">{{ page.name }}</li>
{% endif %}
{% endfor %}
    </ul>
{% endwith %}
</nav>
{% endblock %}

Этот код можно сделать более читабельным, если рассматривать теги HTML и теги шаблона как равноправные:

{% block navigation %}
    <nav id="navigation">
        {% with bookpage.book.pages.all as bookpages %}
            <ul class="book-page-list">
                {% for page in bookpages %}
                    {% if page != bookpage %}
                        <li><a href="{{ page.get_absolute_url }}">{{ page.name }}</a></li>
                    {% else %}
                        <li class="active">{{ page.name }}</li>
                    {% endif %}
                {% endfor %}
            </ul>
        {% endwith %}
    </nav>
{% endblock %}

Это ли не достаточная причина?

Оригинал: Architecture for Django templates

 
comments powered by Disqus