|
Table of contents
|
Теперь, когда вы знаете немного больше о внутренностях шаблонной системы, давайте рассмотрим как можно расширить её возможности с помощью дополнительного кода. Большая часть работы над шаблонами производится в шаблонных тегах и/или фильтрах. Несмотря на то, что шаблонный язык Django поставляется со множеством встроенных тегов и фильтров, скорее всего вы будете создавать свои собственные библиотеки тегов и фильтров, которые будут удовлетворять вашим требованиям. К счастью, это не так сложно сделать. При разработке своих тегов или фильтров, первое, что надо сделать — создать шаблонную библиотеку, это небольшая инфраструктура, которую может использовать Django. Создание шаблонной библиотеки состоит из двух шагов:
Если вы разрабатываете шаблонную библиотеку, которая не связана ни с одной определённой моделью или представлением, то правильно и достаточно стандартно иметь пакет приложения, который содержит только пакет templatetags. Нет никакого ограничения на количество модулей, которые вы можете разместить в пакете templatetags. Просто не забывайте, что оператор {% load %} будет загружать теги и фильтры по имени данного модуля, а не по имени приложения. После создания такого модуля, вам потребуется написать немного кода, в зависимости от того, тег вы реализуете или фильтр.
Для того, чтобы быть правильной библиотекой тегов, модуль
должен содержать в себе переменную
from django import template register = template.Library()
Замечание
Ряд хороших примеров по разработке тегов и фильтров как
обычно находится в исходном коде Django. Изучите файлы
После создания переменной Фильтры это обычные функции языка Python, которые могут принимать от одного до двух аргументов:
Например, в фильтре {{ var|foo:"bar" }},
фильтру foo будет передано содержимое
переменной Функции фильтра должны всегда что-нибудь возвращать. Они не должны вызывать исключения, т.е., они должны тихо игнорировать ошибки. При наличии ошибки, они должны возвращать либо оригинальное значение, либо пустую строку, что привлечёт к себе внимание. Пример определения фильтра: def cut(value, arg):
"Удаляет все значения аргумента arg из строки value"
return value.replace(arg, '')
А это пример того, как можно использовать данный фильтр: {{ somevariable|cut:"0" }}
Большинство фильтров не принимают аргументы. В этом случае, просто не используйте второй аргумент в вашей функции: def lower(value): # Only one argument.
"Преобразовывает регистр строки в строчный"
return value.lower()
После определения своего фильтра требуется зарегистрировать
его в вашем экземпляре класса register.filter('cut', cut)
register.filter('lower', lower)
Метод
Если вы используете Python версии 2.4 и выше, то вы можете
воспользоваться для этого декоратором
@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, '')
Теги более сложны в реализации, чем фильтры, потому что теги могут делать практически всё. В главе «Шаблоны» было описано как работает шаблонная система, два шага: компиляция и рендеринг. Для того, чтобы определить свой шаблонный тег, вам понадобится указать Django как управлять этими шагами при обработке вашего тега.
При компиляции шаблона Django разбирает его текст на
узлы. Каждый узел является экземпляром
django.template.Node и имеет метод
Когда вы вызываете метод Далее в секциях мы рассмотрим все шаги необходимые для создания своего тега:
Для каждого встречающегося тега шаблонный парсер вызывает
функцию, передавая ей содержимое тега и ссылку на себя. Эта
функция отвечает за возвращение экземпляра класса
Например, давайте реализуем шаблонный тег {% current_time %}, который отображает текущую дату и время, отформатированную в соответствии с параметром тега, используя синтаксис strftime (см. http://docs.python.org/library/datetime.html#datetime.date.strftime). Неплохо будет сначала продумать синтаксис тега. В нашем случае, пусть будет так: <p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p>
ЗамечаниеКонечно, этот шаблонный тег является лишним, стандартный тег Django {% now %} делает то же самое и с более простым синтаксисом. Данный шаблонный тег представлен здесь лишь в целях обучения.
Парсер должен выделять параметр и создавать объект
from django import template
def do_current_time(parser, token):
try:
# метод split_contents() знает, что не надо разделять строки в кавычках
tag_name, format_string = token.split_contents()
except ValueError:
# камрады с мест сообщают, что в следующей строке должно использоваться
# token.split_contents()[0], требуется дополнительное подтверждение.
msg = '%r tag requires a single argument' % token.contents[0]
raise template.TemplateSyntaxError(msg)
return CurrentTimeNode(format_string[1:-1])
Разберём этот код:
На втором шаге следует определить подкласс
import datetime
class CurrentTimeNode(template.Node):
def __init__(self, format_string):
self.format_string = format_string
def render(self, context):
now = datetime.datetime.now()
return now.strftime(self.format_string)
Эти два метода ( Аналогично шаблонным фильтрам, эти методы должны тихо игнорировать ошибки. Единственный момент, когда шаблонные теги могут вызвать исключение — момент компиляции.
В конце, следует зарегистрировать тег в экземпляре класса
register.tag('current_time', do_current_time)
Метод
Как и при регистрации фильтра, также возможно использование декоратора (необходим Python версии 2.4+): @register.tag(name="current_time")
def do_current_time(parser, token):
# ...
@register.tag
def shout(parser, token):
# ...
Если вы не укажете аргумент name, как было во втором примере, Django будет использовать имя функции в качестве имени тега. Пример из предыдущей секции просто возвращал значение. Часто бывает полезным устанавливать шаблонную переменную вместо возвращения значения. Для этого авторы шаблонов могут использовать переменные, устанавливаемые их шаблонными тегами.
Для того, чтобы установить переменную в контексте,
используйте словарное назначение для контекстного объекта в
методе class CurrentTimeNode2(template.Node):
def __init__(self, format_string):
self.format_string = format_string
def render(self, context):
now = datetime.datetime.now()
context['current_time'] = now.strftime(self.format_string)
return ''
Следует отметить, что метод Вот так теперь это можно использовать: {% current_time2 "%Y-%M-%d %I:%M %p" %}
<p>The time is {{ current_time }}.</p>
Но с классом Более явное решение — позволить шаблонному тегу указывать имя устанавливаемой переменной, например так: {% 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 = format_string
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):
# This version uses a regular expression to parse tag contents.
try:
# Splitting by None == splitting by spaces.
tag_name, arg = token.contents.split(None, 1)
except ValueError:
msg = '%r tag requires arguments' % 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 had invalid arguments' % tag_name
raise template.TemplateSyntaxError(msg)
if not (fmt[0] == fmt[-1] and fmt[0] in ('"', "'")):
msg = "%r tag's argument should be in quotes" % tag_name
raise template.TemplateSyntaxError(msg)
return CurrentTimeNode3(fmt[1:-1], var_name)
Теперь Шаблонные теги могут работать как блоки, содержащие другие теги (типа {% 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 ''
Метод Таким образом, в предыдущем примере nodelist является списком всех узлов между {% comment %} и {% endcomment %}.
После вызова метода Затем CommentNode.render() просто возвращает пустую строку. Всё, что находилось между тегами {% comment %} и {% endcomment %} игнорируется.
В предыдущем примере, метод
Например, здесь представлен шаблонный тег {% upper %}, который преобразует все буквы в заглавные между собой и тегом {% endupper %}: {% upper %}
This will appear in uppercase, {{ your_name }}.
{% endupper %}
Как и в предыдущем примере мы будет использовать
parser.parse(). В этот раз мы передадим
nodelist в @register.tag
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()
Единственная новая концепция в этом коде это использование
self.nodelist.render(context) в
UpperNode.render(). Он просто вызывает метод
Примеры более сложной обработки можно подсмотреть в исходном
коде {% if %}, {% for %},
{% ifequal %} и {% ifchanged %}. Их код можно найти в
Множество шаблонных тегов принимают только один аргумент, строку или ссылку на шаблонную переменную, и возвращают строку после выполнения обработки, руководствуясь переданным аргументом и некоторой внешней информацией. Например, тег current_time, который мы реализовали ранее, подходит под это описание. Если ему передать строку формата, то он возвратит время в виде строки.
Для того, чтобы упростить создание тегов такого типа Django
предоставляет функции-помощники,
simple_tag. Этот метод принадлежит
django.template.Library, он принимает функцию
одного аргумента, оборачивает её методом
Наша предыдущая функция def current_time(format_string):
return datetime.datetime.now().strftime(format_string)
register.simple_tag(current_time)
А с использованием декоратора, так: @register.simple_tag
def current_time(token):
...
Про simple_tag следует сказать следующее:
Другим общим типом тегов являются теги, которые отображают данные с помощью обработки другого шаблона. Например, интерфейс администратора Django использует свои шаблонные теги для отображения кнопок FIXME. Эти кнопки всегда выглядят одинаково, но их ссылки изменяются в соответствии с объектом, к которому они относятся. Данные кнопки являются отличным примером использования небольшого шаблона, который заполняется информацией из текущего объекта.
Этот тип тегов называется
включениями. Реализацию такого тега лучше демонстрировать на примере. Давайте создадим тег, который создаёт список значений, несколько из которых можно выбрать — класс {% show_results poll %}
Результат будет выглядеть так: <ul> <li>First choice</li> <li>Second choice</li> <li>Third choice</li> </ul>
Сначала, мы определяем функцию, которая получает аргумент и возвращает словарь данных. Следует отметить, что нам требуется возвратить только словарь и ничего более сложного. Этот словарь будет использован как контекст: def show_books_for_author(author):
books = author.book_set.all()
return {'books': books}
Затем мы создаём шаблон, который будет использоваться при обработке тега. Следуя нашему примеру, шаблон будет очень простым: <ul>
{% for book in books %}
<li> {{ book }} </li>
{% endfor %}
</ul>
Наконец, мы создаём и регистрируем тег с помощью метода
Следуя нашему примеру, раз предыдущий шаблон был в файле
register.inclusion_tag('books/books_for_author.html')(show_books_for_author)
И как обычно, Python версии 2.4+ позволяет использование декоратора: @register.inclusion_tag('books/books_for_author.html')
def show_books_for_author(show_books_for_author):
...
Иногда может потребоваться, чтобы ваш тег получал доступ к значениям контекста родительского шаблона. Для решения этой задачи Django предоставляет опцию takes_context. Если вы укажете её при создании шаблонного тега, то тегу не понадобятся обязательные аргументы, функция нижнего уровня будет использовать контекст шаблона из которого данный тег был вызван.
Например, вы реализовали тег включения, который всегда будет
использовать в контексте, содержащем переменные
@register.inclusion_tag('link.html', takes_context=True)
def jump_link(context):
return {
'link': context['home_link'],
'title': context['home_title'],
}
ЗамечаниеПервый параметр функции должен называться context.
Шаблон Jump directly to <a href="{{ link }}">{{ title }}</a>.
Тогда, когда потребуется использовать этот тег, загрузите его библиотеку и вызовите его без аргументов: {% jump_link %}
|
Found misprint?
Select it with the mouse and hit Enter
-
Processed:
33
1
199
25
The full repository of DjangoBook translation you can get on GitHub.
We appreciate your patches!
We are glad to hear your questions, comments or suggestions!
(Open in new tab)
Users number: 601
Русская группа
Ускорить процесс перевода!
ЯМ:41001223475816
|
| © 2008-2012 Ruslan Popov @ gmail.com | Powered by Django 1.2.5 |
Для получения наименования тега в блоках обработки ошибок вместо
msg = '%r tag requires a single argument' % token.contents[0]
необходимо писать
msg = '%r tag requires a single argument' % token.split_contents()[0]
т.к. token.contents[0] лишь указывает на первую букву наименования тэга
В оригинальном тексте именно так и написано