Предыдущая часть — 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: основные теги и фильтры шаблона.




