Django
Теги шаблонов и фильтры
Поиск…
Пользовательские фильтры
Фильтры позволяют применять функцию к переменной. Эта функция может принимать 0 или 1 аргумент. Вот синтаксис:
{{ variable|filter_name }}
{{ variable|filter_name:argument }}
Фильтры могут быть скованы так, что это совершенно верно:
{{ variable|filter_name:argument|another_filter }}
Если перевести на python, указанная выше строка даст что-то вроде этого:
print(another_filter(filter_name(variable, argument)))
В этом примере мы напишем настраиваемый verbose_name
который применяется к модели (экземпляру или классу) или QuerySet. Он вернет подробное имя модели или ее многословное имя, если для аргумента установлено значение True
.
@register.filter
def verbose_name(model, plural=False):
"""Return the verbose name of a model.
`model` can be either:
- a Model class
- a Model instance
- a QuerySet
- any object refering to a model through a `model` attribute.
Usage:
- Get the verbose name of an object
{{ object|verbose_name }}
- Get the plural verbose name of an object from a QuerySet
{{ objects_list|verbose_name:True }}
"""
if not hasattr(model, '_meta'):
# handle the case of a QuerySet (among others)
model = model.model
opts = model._meta
if plural:
return opts.verbose_name_plural
else:
return opts.verbose_name
Простые теги
Самый простой способ определить собственный тег шаблона - использовать simple_tag
. Это очень просто настроить. Имя функции будет именем тега (хотя вы можете переопределить его), а аргументы будут токенами («слова» разделены пробелами, кроме пробелов, заключенных между кавычками). Он даже поддерживает ключевые слова.
Вот бесполезный тег, который иллюстрирует наш пример:
{% useless 3 foo 'hello world' foo=True bar=baz.hello|capfirst %}
Пусть foo
и baz
являются контекстными переменными, такими как:
{'foo': "HELLO", 'baz': {'hello': "world"}}
Скажем, мы хотим, чтобы этот очень бесполезный тег отображался следующим образом:
HELLO;hello world;bar:World;foo:True<br/>
HELLO;hello world;bar:World;foo:True<br/>
HELLO;hello world;bar:World;foo:True<br/>
Пример конкатенации аргументов повторяется 3 раза (3 - первый аргумент).
Вот как выглядит реализация тега:
from django.utils.html import format_html_join
@register.simple_tag
def useless(repeat, *args, **kwargs):
output = ';'.join(args + ['{}:{}'.format(*item) for item in kwargs.items()])
outputs = [output] * repeat
return format_html_join('\n', '{}<br/>', ((e,) for e in outputs))
format_html_join
позволяет отмечать <br/>
как безопасный HTML, но не содержимое outputs
.
Расширенные пользовательские теги с использованием узла
Иногда то, что вы хотите сделать, слишком сложно для filter
или simple_tag
. Для этого вам нужно создать функцию компиляции и средство визуализации.
В этом примере мы создадим тег шаблона verbose_name
со следующим синтаксисом:
пример | Описание |
---|---|
{% verbose_name obj %} | Подробное имя модели |
{% verbose_name obj 'status' %} | Подробное имя поля "status" |
{% verbose_name obj plural %} | Многословное имя множественного числа модели |
{% verbose_name obj plural capfirst %} | Заглавное многословное имя множественного числа модели |
{% verbose_name obj 'foo' capfirst %} | Расширенное подробное имя поля |
{% verbose_name obj field_name %} | Подробное имя поля из переменной |
{% verbose_name obj 'foo'|add:'_bar' %} | Подробное имя поля "foo_bar" |
Причина, по которой мы не можем сделать это с помощью простого тега, состоит в том, что plural
и capfirst
являются ни переменными, ни строками, они являются «ключевыми словами». Очевидно, мы решили передать их как 'plural'
строк и 'capfirst'
, но они могут конфликтовать с полями с этими именами. Было бы {% verbose_name obj 'plural' %}
означать "многословное имя множественного числа obj
" или "подробное имя obj.plural
"?
Сначала создадим функцию компиляции:
@register.tag(name='verbose_name')
def do_verbose_name(parser, token):
"""
- parser: the Parser object. We will use it to parse tokens into
nodes such as variables, strings, ...
- token: the Token object. We will use it to iterate each token
of the template tag.
"""
# Split tokens within spaces (except spaces inside quotes)
tokens = token.split_contents()
tag_name = tokens[0]
try:
# each token is a string so we need to parse it to get the actual
# variable instead of the variable name as a string.
model = parser.compile_filter(tokens[1])
except IndexError:
raise TemplateSyntaxError(
"'{}' tag requires at least 1 argument.".format(tag_name))
field_name = None
flags = {
'plural': False,
'capfirst': False,
}
bits = tokens[2:]
for bit in bits:
if bit in flags.keys():
# here we don't need `parser.compile_filter` because we expect
# 'plural' and 'capfirst' flags to be actual strings.
if flags[bit]:
raise TemplateSyntaxError(
"'{}' tag only accept one occurrence of '{}' flag".format(
tag_name, bit)
)
flags[bit] = True
continue
if field_name:
raise TemplateSyntaxError((
"'{}' tag only accept one field name at most. {} is the second "
"field name encountered."
).format(tag_name, bit)
field_name = parser.compile_filter(bit)
# VerboseNameNode is our renderer which code is given right below
return VerboseNameNode(model, field_name, **flags)
И теперь рендеринг:
class VerboseNameNode(Node):
def __init__(self, model, field_name=None, **flags):
self.model = model
self.field_name = field_name
self.plural = flags.get('plural', False)
self.capfirst = flags.get('capfirst', False)
def get_field_verbose_name(self):
if self.plural:
raise ValueError("Plural is not supported for fields verbose name.")
return self.model._meta.get_field(self.field_name).verbose_name
def get_model_verbose_name(self):
if self.plural:
return self.model._meta.verbose_name_plural
else:
return self.model._meta.verbose_name
def render(self, context):
"""This is the main function, it will be called to render the tag.
As you can see it takes context, but we don't need it here.
For instance, an advanced version of this template tag could look for an
`object` or `object_list` in the context if `self.model` is not provided.
"""
if self.field_name:
verbose_name = self.get_field_verbose_name()
else:
verbose_name = self.get_model_verbose_name()
if self.capfirst:
verbose_name = verbose_name.capitalize()
return verbose_name