Предыдущая часть — Django Book: основы системы шаблонов.
Давайте рассмотрим систему шаблонов Django поближе, что вы могли лучше понять как она работает. Однако — мы пока не будем её внедрять в наши представления из первой главы. Наша цель сейчас — показать как эта система работает независимо от остальной части проекта на Django (другими словами — обычно вы будете использовать систему шаблонов вместе с представлениями Django, но мы хотели бы показать вам, что эта система — лишь одна из библиотек Django, и вы можете использовать её где и как угодно, а не только с представлениями).
Вот простое описание того, как вы можете использовать систему шаблонов в коде Python:
- Создаём объект
Template
, предоставляя исходный код шаблона как обычную строку; - Вызываем метод
render()
объектаTemplate
и определённым набором переменных (контекст, context), и получаем сформированный шаблон как строку, со всеми переменными и тегами шаблона, в соответствии с контекстом.
Примечание: тут и далее в роли консольного интерпретатора выступает IPython.
Вот как это может выглядеть в коде:
$ python manage.py shell In [1]: from django import template In [2]: t = template.Template('My name is {{ name }}.') In [3]: c = template.Context({'name': 'Adrian'}) In [4]: print t.render(c) My name is Adrian. In [5]: c = template.Context({'name': 'Fred'}) In [6]: print t.render(c) My name is Fred.
Ниже мы рассмотрим этот пример, описывая каждый шаг в деталях.
Содержание
Создание объекта Template
Самый простой способ создания объекта Template
— это напрямую вызвать его экземпляр. Класс Template
находится в модуле django.template
, а его конструктор принимает один аргумент — код самого шаблона. Давайте запустим интерпретатор, что бы посмотреть как весь этот код работает.
В директории, созданной командой django-admin.py startproject
, которую мы выполняли в первой главе, введите команду python manage.py shell
, что бы вызвать интерактивный интерпретатор.
Особенная консоль Python
Если вы раньше пользовались Python, вы можете удивиться — зачем нам запускать python manage.py shell
вместо прямого вызова python
? Обе команды откроют консоль интерпретатора, но python manage.py shell
имеет ключевое отличие: перед запуском интерпретатора она укажет Django какой файл settings.py
необходимо использовать. Многие системы Django, включая систему шаблонов, зависят от ваших настроек, и вы не сможете их использовать, пока фреймворк не будет знать их.
Если вам любопытно, как всё работает «за сценой»: Django ищет переменную окружения DJANGO_SETTINGS_MODULE
, которая должна быть установлена для импорта пути вашего файла настроек settings.py
. Например, значение этой переменной может выглядеть как ‘example.settings
‘ , если ваш проект расположен в директории example
.
Когда вы запускаете python manage.py shell
— эта команда учитывает значение переменной DJANGO_SETTINGS_MODULE
и загружает все необходимые настройки, и мы советуем вам использовать её в наших примерах, что бы уменьшить количвество необходимых действий для настройки вашего окружения.
Давайте рассмотрим некоторые основные элементы системы шаблонов:
In [7]: from django.template import Template In [8]: t = Template('My name is {{ name }}.') In [9]: print t
Если вы выполните этот код в консоли — вы должны увидеть что-то вроде такого:
<django.template.base.Template object at 0x2626190>
Значение 0x2626190 будет отличаться каждый раз, и оно не имеет особого значения (это «идентификатор» объекта Python).
Когда вы создаёте объект Template
, система шаблонов компилирует исходный код шаблона во внешнюю оптимизированную форму данных, готовую к рендренингу. Однако, если ваш код содержит какие-то ошибки — то выполнение Template()
вызовет исключение TemplateSyntaxError
:
In [10]: from django.template import Template In [11]: t = Template('{% notatag %}') --------------------------------------------------------------------------- TemplateSyntaxError Traceback (most recent call last) <ipython-input-11-fdd5f59f5346> in <module>() ----> 1 t = Template('{% notatag %}') ... TemplateSyntaxError: Invalid block tag: 'notatag'
Термин «блок тега» (block tag) тут относится к {% notatag %}
, и является синонимом понятия «тег шаблона».
Система вызывает TemplateSyntaxError
в таких случаях:
- некорректный тег;
- некорректный аргумент тега;
- некорректные фильтры;
- некорректные аргументы фильтров;
- некорректный синтаксис шаблона;
- незакрытые теги (для тегов, которые должны быть закрыты).
Template рендеринг
Как только вы создали объект Template
, вы можете передать ему данные с помощью контекста. Контекст — простой набор имён переменных и связанных с ними значений. Шаблон использует их для наполнения его переменных и определения тегов.
Контекст предоставлен в Django классом Context
из модуля django.template
. Его конструктор принимает один опциональный аргумент — словарь с именами и значениями переменных. Что бы выполнить «сборку» объекта Template
— используется метод render()
с указанием контекста:
In [12]: from django.template import Context, Template In [13]: t = Template('My name is {{ name }}.') In [14]: c = Context({'name': 'Stephane'}) In [15]: t.render(c) Out[15]: u'My name is Stephane.'
Обратите внимание, что выполнение t.render(c)
возвращает объект Unicode, а не простую строку Python, что можно определить по префиксу u
. Django использует Unicode объекты вместо стандартных строк во всем фреймворке. Если вы понимаете зачем это делается — то скажите спасибо Django за то, что он делает это всё сам. А если нет — то знайте, что Unicode в Django даёт возможность вашему приложению безболезненно обеспечивать поддержку огромного количества символов, кроме стандартных английских «A-z».
Словари и контексты
Словари в Python связывают определённые ключи и значения переменных. Контексты в Django весьма схожи с ними, но имеют дополнительную функциональность, которую мы рассмотрим позже.
Имена переменных должны начинаться с буквы (A-Z или a-z), и могут содержать буквы, цифры, знаки подчёркивания и точки (хотя точки имеют особенное значение, которое мы рассмотри ниже в этой статье). Имена переменных чувствительны к регистру.
Вот пример компиляции и рендеринга с использованием шаблона, схожего с примером в начале главы:
In [1]: from django.template import Template, Context In [2]: raw_template = """<p>Dear {{ person_name }},</p> ...: <p>Thanks for placing an order from {{ company }}. It's scheduled to ...: ship on {{ ship_date|date:"F j, Y" }}.</p> ...: ...: {% if ordered_warranty %} ...: <p>Your warranty information will be included in the packaging.</p> ...: {% else %} ...: <p>You didn't order a warranty, so you're on your own when ...: the products inevitably stop working.</p> ...: {% endif %} ...: <p>Sincerely,<br />{{ company }}</p>""" In [3]: t = Template(raw_template) In [4]: import datetime In [5]: c = Context({'person_name': 'John Smith', ...: 'company': 'Outdoor Equipment', ...: 'ship_date': datetime.date(2009, 4, 2), ...: 'ordered_warranty': False}) In [6]: t.render(c) Out[6]: u"<p>Dear John Smith,</p>n<p>Thanks for placing an order from Outdoor Equipment. It's scheduled tonship on April 2, 2009.</p>nn<p>You didn't order a warranty, so you're on your own whennthe products inevitably stop working.</p>nn<p>Sincerely,<br />Outdoor Equipment</p>"
Давайте рассмотрим этот код подробнее:
- Сначала, мы импортируем классы
Template
иContext
из модуляdjango.template
. -
Затем, мы сохраняем исходный код нашего шаблона в переменной
raw_template
. Обратите внимание, что мы используем тройные кавычки для определения строки, так как они позволяют использовать несколько строк, тогда как обычные одинарные или двойные кавычки — могут использоваться только для строки в одну линию. -
Далее, мы создаём объект шаблона с именем
t
, передаваяraw_template
конструктору классаTemplate
. -
Мы импортируем модуль
datetime
из стандартной библиотеки Python, так как он нам потребуется в следующем выражении. -
Затем, мы создаём объект
Context
с именемc
. Конструктор классаContext
в качестве аргумента принимает словарь Python, который связывает имена переменных с их значениями. Тут например, мы указываем, что переменнаяperson_name
имеет значение ‘John Smith’, переменнаяcompany
— ‘Outdoor Equipment‘ и так далее. -
В конце — мы вызываем метод
render()
нашего объекта шаблона, передавая ему аргументом наш контекст, а он нам возвращает сформированный шаблон, т.е. — с добавленными переменными и обработанными тегами.Заметьте, что параграф «You didn’t order a warranty» отображается, так как переменная
ordered_warranty = False
. Так же, обратите внимание на дату April 2, 2009, которая отображается в соответствии с заданным фильтром ‘F j, Y
‘ (мы рассмотрим форматирование строки для фильтраdate
чуть позже).Если вы новичок в Python, вы можете удивиться — почему в выводе присутствует символ новой строки ‘
n
‘, а не выполняется её перенос? Это связанно с особенностью интерпретатора Python — вызовt.render(c)
возвращает строку, а по умолчанию интерактивный интерпретатор отображает представление строки, а не печатает её реальное значение. Если вы хотите увидеть действительное значение значение строки, с использованием символов переноса — используйте операторprint
:print t.render(c)
:
In [7]: print(t.render(c)) <p>Dear John Smith,</p> <p>Thanks for placing an order from Outdoor Equipment. It's scheduled to ship on April 2, 2009.</p> <p>You didn't order a warranty, so you're on your own when the products inevitably stop working.</p> <p>Sincerely,<br />Outdoor Equipment</p>
Несколько контекстов, один Template
Когда у вас уже есть объект Template
— вы можете использовать несколько различных контекстов с ним.
Например:
In [8]: from django.template import Template, Context In [9]: t = Template('Hello, {{ name }}') In [10]: In [10]: print t.render(Context({'name': 'John'})) Hello, John In [11]: print t.render(Context({'name': 'Julie'})) Hello, Julie In [12]: print t.render(Context({'name': 'Pat'})) Hello, Pat
При использовании одного шаблона, как в примере выше, более эффективно будет создать объект шаблона, а затем вызывать render()
несколько раз, по мере необходимости:
# Плохой пример for name in ('John', 'Julie', 'Pat'): t = Template('Hello, {{ name }}') print t.render(Context({'name': name})) # Хороший пример t = Template('Hello, {{ name }}') for name in ('John', 'Julie', 'Pat'): print t.render(Context({'name': name}))
Разбор шаблона в Django выполняется очень быстро, и большая часть реализована через вызов только одного регулярного выражения, в отличии от шаблонов, основанных на XML, которые взывают XML-парсер и могут выполняться на несколько порядков медленее, чем это реализовано в Django.
Поиск переменных в контексте
До сих пор в примерах мы передавали простые значения в контексты — в основном это были строки и datetime.date
. Однако, система шаблонов очень элегантно обрабатывает более сложные структуры, такие как списки, словари и другие объекты.
Ключ для передачи сложных структур данных в шаблон Django — это точка. Используйте её, что бы получить доступ к ключам словарей, атрибутам, методам или индексам объектов.
Давайте продемонстрируем это на примерах. Например, вы хотите передать словарь Python в шаблон. Для получения доступа к ключам словаря — используем точку:
In [13]: from django.template import Template, Context In [14]: person = {'name': 'Sally', 'age': '43'} In [15]: t = Template('{{ person.name }} is {{ person.age }} years old.') In [16]: c = Context({'person': person}) In [17]: print(t.render(c)) Sally is 43 years old.
Так же, точка даёт доступ к атрибутам объекта. Например, объект datetime.date
имеет атрибуты year
, month
и day
. Вы можете использовать точку, что бы получить к ним доступ из шаблона Django:
In [18]: from django.template import Template, Context In [19]: import datetime In [20]: d = datetime.date(1993, 5, 2) In [21]: d.year Out[21]: 1993 In [22]: d.month Out[22]: 5 In [23]: d.day Out[23]: 2 In [24]: t = Template('The month is {{ date.month }} and the year is {{ date.year }}.') In [25]: c = Context({'date': d}) In [26]: print(t.render(c)) The month is 5 and the year is 1993.
В следующем примере мы используем свой класс и продемонстрируем как с помощью точки в переменной получить доступ к атрибутам произвольных объектов:
In [27]: from django.template import Template, Context In [28]: class Person(object): ....: def __init__(self, first_name, last_name): ....: self.first_name, self.last_name = first_name, last_name ....: In [29]: t = Template('Hello, {{ person.first_name }} {{ person.last_name }}.') In [30]: c = Context({'person': Person('John', 'Smith')}) In [31]: print(t.render(c)) Hello, John Smith.
Точки так же можно использовать для ссылок на методы объектов классов. Например, каждая строка в Python имеет методы upper(),
isdigit()
и другие строковые методы. Вы можете вызвать их в Django с помощью точек:
In [32]: from django.template import Template, Context In [33]: t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}') In [34]: print(t.render(Context({'var': 'hello'}))) hello -- HELLO -- False In [35]: print(t.render(Context({'var': '123'}))) 123 -- 123 -- True
Заметьте, что тут не используются скобки при вызове метода объекта. Кроме того, методам нельзя передать аргументы — вы можете использовать только те методы, которые не требуют аргументов (мы рассмотрим эти моменты далее в этой главе).
И напоследок — точки так же используются для доступа к индексам списков:
In [36]: from django.template import Template, Context In [37]: t = Template('Item 2 is {{ items.2 }}.') In [38]: c = Context({'items': ['apples', 'bananas', 'carrots']}) In [39]: print(t.render(c)) Item 2 is carrots.
Нельзя использовать отрицательные индексы списков. Например, переменная шаблона {{ items.-1 }}
вызовет исключение TemplateSyntaxError
.
Списки Python
Напомним, что списки Python используют «нулевой индекс» — первый элемент списка всегда равен 0, второй — 1, и так далее.
Подводя итоги: когда шаблон встречает точку в имени переменной — он выполняет поиск в таком порядке:
- Поиск словаря (например — foo["bar"])
- Поиск атрибута (например — foo.bar)
- Вызов метода (например — foo.bar())
- Поиск индекса списка (например — foo[2])
Будет использован первый тип, который сработает.
Поиск решения для точки может быть включен в несколько уровней. Например, в коде ниже используется {{ person.name.upper }}
, который будет переведён в поиск словаря (person['name'])
, а затем вызова метода (upper())
:
In [40]: from django.template import Template, Context In [41]: person = {'name': 'Sally', 'age': '43'} In [42]: t = Template('{{ person.name.upper }} is {{ person.age }} years old.') In [43]: c = Context({'person': person}) In [44]: print(t.render(c)) SALLY is 43 years old.
Поведение вызова метода
Вызовы методов немного более сложные, чем другие. Вот некоторые моменты, которые стоит знать:
-
Если во время поиска метода он вернёт исключение — оно будет будет использовано как ошибка, если для исключения не установлен атрибут
silent_variable_failure
. Если же для исключенияsilent_variable_failure = True
— переменная создаст пустую строку, например:>>> t = Template("My name is {{ person.first_name }}.") >>> class PersonClass3: ... def first_name(self): ... raise AssertionError, "foo" >>> p = PersonClass3() >>> t.render(Context({"person": p})) Traceback (most recent call last): ... AssertionError: foo >>> class SilentAssertionError(AssertionError): ... silent_variable_failure = True >>> class PersonClass4: ... def first_name(self): ... raise SilentAssertionError >>> p = PersonClass4() >>> t.render(Context({"person": p})) u'My name is .'
-
Вызов метода сработает только в том случае, если метод не требует аргументов. В противном случае — система перейдёт к следующему типу (поиск индекса).
-
Конечно, некоторые методы имеют «побочные эффекты», и будет как минимум глупо, и даже, возможно, небезопасно, предоставлять системе шаблонов к ним доступ.
Например, у вас есть экземпляр
BankAccount
, у которого есть методdelete()
. Если шаблон включает в себя что-то вроде{{ account.delete }}
, гдеaccount
— это объектBankAccount
, то он будет удалён во врем создания шаблона.Что бы избежать этого — установите атрибут функции
alters_data
для метода:def delete(self): # Delete the account delete.alters_data = True
Система шаблона не будет его выполнять. Продолжая пример выше — если шаблон включает в себя
{{ account.delete }}
, а методdelete()
имеет атрибутalters_data = True
, тогда методdelete()
не будет выполнен во время рендеринга шаблона.
Обработка неверных переменных
По умолчанию, если переменная не существует, система шаблонов создаст пустую строку, например:
In [49]: from django.template import Template, Context In [50]: t = Template('Your name is {{ name }}.') In [51]: print(t.render(Context())) Your name is . In [52]: print(t.render(Context({'var': 'hello'}))) Your name is . In [53]: print(t.render(Context({'NAME': 'hello'}))) Your name is . In [54]: print(t.render(Context({'Name': 'hello'}))) Your name is .
Система обработает ошибку без уведомлений, так как она должна быть относительно терпимой к обычным человеческим ошибкам. В этом случае, все поиски были ошибочны, так как имена переменных были неверными. В реальном мире недопустимо, что бы сайт стал недоступен из-за маленькой ошибки в шаблоне.
Объекты контекстов
В большинстве случаев — вы будете инициализировать объект Context
, передавая ему заполненный словарь. Но вы так же моете добавлять или удалять элементы из контекста, как только он создан, используя стандартный синтаксис Python:
In [55]: from django.template import Context In [56]: c = Context({"foo": "bar"}) In [57]: c['foo'] Out[57]: 'bar' In [58]: del c['foo'] In [59]: c['foo'] --------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-59-46058764a7fc> in <module>() ----> 1 c['foo'] /usr/lib/python2.6/site-packages/django/template/context.pyc in __getitem__(self, key) 53 if key in d: 54 return d[key] ---> 55 raise KeyError(key) 56 57 def __delitem__(self, key): KeyError: 'foo' In [60]: c['newvariable'] = 'hello' In [61]: c['newvariable'] Out[61]: 'hello'
Продолжение — Django Book: основные теги и фильтры шаблона.