Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Django_-_podrobnoe_rukovodstvo

.pdf
Скачиваний:
311
Добавлен:
01.03.2016
Размер:
4.88 Mб
Скачать

210

Глава 9. Углубленное изучение шаблонов­

те собственную библиотеку с учетом своих потребностей. К счастью, это совсем не сложно.

Создание библиотеки шаблонов­

При разработке тегов или фильтров первым делом нужно создать библиотеку шаблонов­ – инфраструктурный компонент, к которому может подключиться Django.

Создание шаблонной­ библиотеки состоит из двух шагов:

1.Во-первых, необходимо решить, какое приложение Django станет владельцем библиотеки шаблонов­. Если вы создали приложение командой manage.py startapp, то можете поместить библиотеку прямо туда, но можете создать отдельное приложение специально для библиотеки шаблонов­. Мы рекомендуем второй вариант, потому что созданные фильтры могут впоследствии пригодиться и в других проектах.

Влюбом случае не забудьте добавить свое приложение в параметр INSTALLED_APPS. Чуть позже мы еще вернемся к этому вопросу.

2.Во-вторых, создайте каталог templatetags в пакете выбранного приложения Django. Он должен находиться на том же уровне, что и файлы models.py, views.py и т. д. Например:

books/ __init__.py models.py templatetags/ views.py

Создайте в каталоге templatetags два пустых файла: __init__.py (чтобы показать, что это пакет, содержащий код на Python) и файл, который будет содержать определения ваших тегов и фильтров. Имя второго файла вы будете впоследствии указывать, когда понадобится загрузить из него теги. Например, если ваши фильтры и теги находятся в файле poll_extras.py, то в шаблоне­ нужно будет написать:

{% load poll_extras %}

Тег {% load %} опрашивает параметр INSTALLED_APPS и разрешает загружать только те библиотеки шаблонов,­ которые находятся в одном из установленных приложений Django. Это мера предосторожности; вы можете хранить на своем компьютере много библиотек шаблонов,­ но разрешать доступ к ним избирательно.

Если библиотека шаблонов­ не привязана к конкретным моделям и представлениям, то допустимо и совершенно нормально создавать приложение Django, которое содержит только пакет templatetags и ничего больше. В пакете templatetags может быть сколько угодно модулей. Но имейте в виду, что тег {% load %} загружает теги и фильтры по заданному имени модуля, а не по имени приложения.

Расширение системы­ шаблонов­

211

После создания модуля Python вам предстоит написать код, зависящий от того, что именно реализуется: фильтр или тег.

Чтобы считаться корректной библиотекой тегов, модуль должен содержать переменную уровня модуля register, которая является экземпляром класса template.Library. Это структура данных, в которой регистрируются все теги и фильтры. Поэтому в начале модуля поместите такой код:

from django import template

register = template.Library()

Примечание

Ряд хороших примеров тегов и фильтров можно найти в исходном коде Django. Они находятся соответственно в файлах django/template/defaultfilters.py

и django/template/defaulttags.py. Некоторые приложения в django.contrib

также содержат библиотеки шаблонов­.

Переменная register будет использоваться при создании шаблонных­ фильтров и тегов.

Создание собственных шаблонных­ фильтров

Фильтры – это обычные функции Python, принимающие один или два аргумента:

•• Значение переменной (входные данные).

•• Значение аргумента, который может иметь значение по умолчанию или вообще отсутствовать.

Например, в случае {{ var|foo:”bar” }} фильтру foo будет передано содержимое переменной var и аргумент “bar”.

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

Приведем пример определения фильтра:

def cut(value, arg):

“Удаляет вcе вхождения arg из данной строки” return value.replace(arg, ‘’)

А вот пример использования этого фильтра для удаления пробелов из значения переменной:

{{ somevariable|cut:” “ }}

Большинство фильтров не принимают аргумент. В таком случае можете опустить его в определении функции:

212

Глава 9. Углубленное изучение шаблонов­

def lower(value): # Только один аргумент. “Преобразует строку в нижний регистр” return value.lower()

Написав определение фильтра, зарегистрируйте его в экземпляре класса Library, чтобы сделать доступным для языка шаблонов­ Django:

register.filter(‘cut’, cut) register.filter(‘lower’, lower)

Метод Library.filter() принимает два аргумента:

•• Имя фильтра (строка)

•• Сама функция фильтра

Вверсии Python 2.4 и выше метод register.filter() можно использовать

вкачестве декоратора:

@register.filter(name=’cut’) def cut(value, arg):

return value.replace(arg, ‘’)

@register.filter def lower(value):

return value.lower()

Если опустить аргумент name, как показано во втором примере, то в качестве имени фильтра Django будет использовать имя функции.

Ниже приведен пример полной библиотеки шаблонов,­ предоставляющей фильтр cut:

from django import template

register = template.Library()

@register.filter(name=’cut’) def cut(value, arg):

return value.replace(arg, ‘’)

Создание собственных шаблонных­ тегов

Теги сложнее фильтров, потому что могут делать практически все что угодно.

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

Во время компиляции шаблона­ Django преобразует текст шаблона­ в набор узлов. Каждый узел представляет собой экземпляр класса django. template.Node и обладает методом render(). Следовательно, откомпилированный шаблон­ – это просто список объектов Node. Рассмотрим, к примеру, такой шаблон:­

Расширение системы­ шаблонов­

213

Привет, {{ person.name }}.

{% ifequal name.birthday today %} С днем рождения!

{% else %}

Не забудьте заглянуть сюда в свой день рождения, вас ждет приятный сюрприз.

{% endifequal %}

В откомпилированном виде этот шаблон­ представлен таким списком узлов:

•• Текстовый узел: “Привет”

•• Узел переменной: person.name

•• Текстовый узел: “.\n\n”

•• Узел IfEqual: name.birthday и today

При вызове метода render()откомпилированного шаблона­ он вызывает метод render() каждого объекта Node в списке узлов с заданным контекстом. Возвращаемые ими значения объединяются и формируют результат отображения шаблона­. Следовательно, чтобы определить шаблонный­ тег, необходимо описать, как его текстовое представление преобразуется в объект Node (функция компиляции) и что делает метод render().

В следующих разделах мы опишем все этапы создания собственного тега.

Функция компиляции

Встретив шаблонный­ тег, анализатор шаблонов­ вызывает некую функцию Python, передавая ей содержимое тега и сам объект анализатора. Эта функция отвечает за возврат объекта Node, созданного на основе содержимого тега.

Напишем, к примеру, шаблонный­ тег {% current_time %} для вывода текущих даты и времени, отформатированных в соответствии с указанным в теге параметром, который должен быть задан, как принято в функции strftime (см. http://www.djangoproject.com/r/python/strftime/). Прежде всего определимся с синтаксисом тега. Допустим, что в нашем случае он будет выглядеть так:

<p>Сейчас {% current_time “%Y-%m-%d %I:%M %p” %}.</p>

Примечание

На самом деле этот тег избыточен – имеющийся в Django тег {% now %} делает то же самое и к тому же имеет более простой синтаксис. Мы привели этот пример исключительно в учебных целях.

Функция компиляции этого тега должна принять параметр и создать объект Node:

214

Глава 9. Углубленное изучение шаблонов­

from django import template

register = template.Library()

def do_current_time(parser, token): try:

#split_contents() знает, что строки в кавычках разбивать

#не нужно.

tag_name, format_string = token.split_contents() except ValueError:

msg = ‘Тег %r требует один аргумент’ % token.split_contents()[0] raise template.TemplateSyntaxError(msg)

return CurrentTimeNode(format_string[1:-1])

Разберемся, что здесь делается:

•• Любая функция компиляции шаблонного­ тега принимает два аргумента: parser и token. Аргумент parser – это объект, представляющий синтаксический анализатор шаблона­. В этом примере он не используется. Аргумент token – это текущая выделенная анализатором лексема.

•• Атрибут token.contents содержит исходный текст тега. В нашем случае это строка ‘current_time “%Y-%m-%d %I:%M %p”’.

•• Метод token.split_contents() разбивает строку по пробелам, оставляя нетронутыми фрагменты, заключенные в кавычки. Не пользуйтесь методом token.contents.split() (в нем применяется стандартная для Python семантика разбиения строк). Он выполняет разбиение по всем пробелам, в том числе и по пробелам, находящимся внутри кавычек.

•• При обнаружении любой синтаксической ошибки эта функция должна возбудить исключение django.template.TemplateSyntaxError с содержательным сообщением.

•• Не «зашивайте» имя тега в текст сообщения об ошибке, потому что тем самым вы привязываете имя тега к функции. Имя тега всегда можно получить из элемента token.split_contents()[0], даже когда тег не имеет параметров.

•• Функция возвращает объект класса CurrentTimeNode (который мы создадим чуть ниже), содержащий все, что узел должен знать о теге. В данном случае передается только аргумент “%Y-%m-%d %I:%M %p”. Кавычки в начале и в конце строки удаляются с помощью конструкции format_string[1:-1].

•• Функция компиляции шаблонного­ тега обязана вернуть подкласс класса Node; любое другое значение приведет к ошибке.

Создание класса узла шаблона­

Далее мы должны определить подкласс класса Node, имеющий метод render(). В нашем примере этот класс будет называться CurrentTimeNode.

Расширение системы­ шаблонов­

215

import datetime

class CurrentTimeNode(template.Node): def __init__(self, format_string):

self.format_string = str(format_string)

def render(self, context):

now = datetime.datetime.now()

return now.strftime(self.format_string)

Функции __init__() и render() соответствуют двум этапам обработки шаблона­ (компиляция и отображение). Следовательно, функция инициализации должна всего лишь сохранить строку формата для последующего использования, а вся содержательная работа выполняется в функции render().

Как и шаблонные­ фильтры, функции отображения должны молчаливо игнорировать ошибки, а не возбуждать исключения. Шаблонным­ тегам разрешено возбуждать исключения только на этапе компиляции.

Регистрация тега

Наконец, тег необходимо зарегистрировать в экземпляре класса Library вашего модуля. Регистрация пользовательских тегов аналогична регистрации фильтров (см. выше). Достаточно создать объект template. Library и вызвать его метод tag():

register.tag(‘current_time’, do_current_time)

Метод tag() принимает два аргумента:

•• Имя шаблонного­ тега (строка)

•• Функция компиляции

Как и при регистрации фильтра, в версиях начиная с Python 2.4 функцию register.tag можно использовать в качестве декоратора:

@register.tag(name=”current_time”) def do_current_time(parser, token):

# ...

@register.tag

def shout(parser, token):

# ...

Если аргумент name опущен, как во втором примере, то Django возьмет в качестве имени тега имя самой функции.

Запись переменной в контекст

В примере из предыдущего раздела функция отображения тега просто возвращает некоторое значение. Однако вместо этого часто бывает удобно установить значение какой-либо шаблонной­ переменной. Это по-

216

Глава 9. Углубленное изучение шаблонов­

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

Контекст – это обычный словарь, и, чтобы добавить в контекст новую переменную, достаточно просто выполнить операцию присваивания значения элементу словаря в методе render(). Ниже представлена модифицированная версия класса CurrentTimeNode, где метод отображения не возвращает текущее время, а помещает его в шаблонную­ переменную current_time:

class CurrentTimeNode2(template.Node): def __init__(self, format_string):

self.format_string = str(format_string)

def render(self, context):

now = datetime.datetime.now()

context[‘current_time’] = now.strftime(self.format_string) return ‘’

Примечание

Создание функции do_current_time2 и регистрацию тега current_time2 мы оставляем в качестве упражнения для читателя.

Обратите внимание, что метод render() возвращает пустую строку. Этот метод всегда должен возвращать строку, но если от тега требуется всего лишь установить значение шаблонной­ переменной, метод render() должен возвращать пустую строку.

Вот как применяется новая версия тега:

{% current_time2 “%Y-%M-%d %I:%M %p” %} <p>Сейчас {{ current_time }}.</p>

Однако в этом варианте CurrentTimeNode2 имеется неприятность: имя переменной current_time жестко определено в коде. Следовательно, вам придется следить за тем, чтобы больше нигде в шаблоне­ не использовалась переменная {{ current_time }}, потому что тег {% current_time2 %} не глядя затрет ее значение.

Более правильное решение – определять имя устанавливаемой переменной в самом шаблонном­ теге:

{% get_current_time “%Y-%M-%d %I:%M %p” as my_current_time %} <p>The current time is {{ my_current_time }}.</p>

Для этого придется изменить функцию компиляции и класс узла, как показано ниже:

import re

class CurrentTimeNode3(template.Node):

def __init__(self, format_string, var_name): self.format_string = str(format_string)

Расширение системы­ шаблонов­

217

self.var_name = var_name

def render(self, context):

now = datetime.datetime.now()

context[self.var_name] = now.strftime(self.format_string) return ‘’

def do_current_time(parser, token):

#В этой версии для разбора содержимого тега применяется

#регулярное выражение

try:

# Разбиение по None == разбиение по пробелам. tag_name, arg = token.contents.split(None, 1)

except ValueError:

msg = ‘Тег %r требует аргументы’ % token.contents[0] raise template.TemplateSyntaxError(msg)

m = re.search(r’(.*?) as (\w+)’, arg) if m:

fmt, var_name = m.groups() else:

msg = ‘У тега %r недопустимые аргументы’ % tag_name raise template.TemplateSyntaxError(msg)

if not (fmt[0] == fmt[-1] and fmt[0] in (‘”’, “’”)):

msg = “Аргумент тега %r должен быть в кавычках” % tag_name raise template.TemplateSyntaxError(msg)

return CurrentTimeNode3(fmt[1:-1], var_name)

Теперь функция do_current_time() передает конструктору CurrentTime­ Node3 строку формата и имя переменной.

Разбор до обнаружения следующего шаблонного­ тега

Шаблонные­ теги могут выступать в роли блоков, содержащих другие теги (например, {% if %}, {% for %}). Функция компиляции такого тега должна вызвать метод parser.parse().

Вот как реализован стандартный тег {% comment %}:

def do_comment(parser, token):

nodelist = parser.parse((‘endcomment’,)) parser.delete_first_token()

return CommentNode()

class CommentNode(template.Node): def render(self, context):

return ‘’

Метод parser.parse() принимает кортеж с именами шаблонных­ тегов, отмечающих конец данного блока. Он возвращает объект django.template. NodeList, содержащий список всех объектов Node, которые встретились анализатору до появления любого из тегов, перечисленных в кортеже.

218

Глава 9. Углубленное изучение шаблонов­

Так, в предыдущем примере объект nodelist – это список всех узлов между тегами {% comment %} и {% endcomment %}, не считая самих этих тегов.

После вызова parser.parse() анализатор еще не «проскочил» тег {% endcomment %}, поэтому необходимо явно вызвать parser.delete_first_ token(), чтобы предотвратить повторную обработку этого тега.

Метод CommentNode.render() просто возвращает пустую строку. Все находящееся между тегами {% comment %} и {% endcomment %} игнорируется.

Разбор до обнаружения следующего шаблонного­ тега с сохранением содержимого

В предыдущем примере функция do_comment() отбрасывала все, что находится между тегами {% comment %} и {% endcomment %}. Но точно так же можно что-то сделать с кодом, расположенным между шаблонными­ тегами.

Рассмотрим в качестве примера шаблонный­ тег {% upper %}, который переводит в верхний регистр все символы, находящиеся между ним и тегом {% endupper %}:

{% upper %}

Это будет выведено в верхнем регистре, {{ user_name }}. {% endupper %}

Как и в предыдущем примере, мы вызываем метод parser.parse(). Но на этот раз полученный список nodelist передается конструктору Node:

def do_upper(parser, token):

nodelist = parser.parse((‘endupper’,)) parser.delete_first_token()

return UpperNode(nodelist)

class UpperNode(template.Node): def __init__(self, nodelist):

self.nodelist = nodelist

def render(self, context):

output = self.nodelist.render(context) return output.upper()

Из нового в методе UpperNode.render() есть только обращение к методу self.nodelist.render(context), который вызывает метод render() каждого узла в списке.

Другие примеры сложных реализаций метода отображения можно найти в исходном коде тегов {% if %}, {% for %}, {% ifequal %} и {% ifchanged %}, в файле django/template/defaulttags.py.

Вспомогательная функция для создания простых тегов

Многие шаблонные­ теги принимают единственный аргумент – строку или ссылку на шаблонную­ переменную – и возвращают строку после

Расширение системы­ шаблонов­

219

обработки, зависящей только от входного аргумента и какой-то внешней информации. Например, так устроен созданный выше тег current_ time. Мы передаем ему строку формата, а он возвращает время, представленное в виде строки.

Чтобы упростить создание таких тегов, в Django имеется вспомогательная функция simple_tag, реализованная в виде метода класса django. template.Library. Она принимает на входе функцию с одним аргументом, обертывает ее в метод render, выполняет другие необходимые действия, о которых рассказывалось выше, и регистрирует новый тег в системе­ шаблонов­.

С ее помощью функцию current_time можно переписать следующим образом:

def current_time(format_string): try:

return datetime.datetime.now().strftime(str(format_string)) except UnicodeEncodeError:

return ‘’

register.simple_tag(current_time)

Начиная с версии Python 2.4 допускается также использовать синтаксис декораторов:

@register.simple_tag

def current_time(token):

# ...

Отметим несколько моментов, касающихся функции simple_tag:

•• Нашей функции передается только один аргумент.

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

•• Окружающие аргумент кавычки (если они были) уже удалены, поэтому мы получаем готовую строку Unicode.

Включающие теги

Часто встречаются теги, которые выводят какие-то данные за счет отображения другого шаблона­. Например, в административном интерфейсе Django применяются пользовательские теги, которые отображают кнопки «добавить/изменить» вдоль нижнего края формы. Сами кнопки всегда выглядят одинаково, но ассоциированные с ними ссылки меняются в зависимости от редактируемого объекта. Это идеальный случай для использования небольшого шаблона,­ который будет заполняться информацией из текущего объекта.

Такие теги называются включающими. Создание включающего тега лучше всего продемонстрировать на примере. Напишем тег, порождаю-

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]