Чтобы все не решили, что я перестал писать технические посты про Django, вот маленький пост средней полезности.
Если вы какое-то время работаете с Django и используете inclusion-теги, то наверняка сталкивались с неудобством, что кроме переменных контекста, возвращаемого из тега, в шаблон не попадает ничего. В частности, там нет всяких полезных {{ user }}
, {{ MEDIA_URL }}
и прочих "глобальных" вещей из родительских контекстов.
Я одно время был даже пропонентом такого подхода, потому что он "чище": в контекст не сливается куча глобальных переменных, которые теоретически засоряют namespace внутреннего независимого шаблона. Но практика показывает, что все наоборот. Случаев, когда вам нужны глобальные данные в контексте, полно, а вот чтобы они мешались, я как-то не припоминаю совсем. Да и кроме того, поскольку шаблонный язык очень ограничен, прострелить себе ногу с помощью глобальных данных там довольно сложно. Средств мало :-).
Поэтому сейчас я думаю, что на самом деле inclusion-тег должен просто передавать в свой шаблон весь родительский контекст тоже. А пока этого не произошло, я хочу описать способ, которым я это делаю.
Проще всего сделать, конечно так:
@register.inclusion_tag('template.html', takes_context=True)
def my_tag(context):
return {
'user': context['user'],
'MEDIA_URL': context['MEDIA_URL'],
}
Но это скучно и нуждается все время в поддержке по дописыванию нужных переменных. Чтобы это автоматизировать, можно было бы воспользоваться методом типа items()
, выдающему все содержимое, но такого метода у контекста нет. Потому что контекст — это на самом деле не dict
, а стек из dict
ов. Поэтому нужно было бы бежать по всем стеку циклом, и у каждого dict
а брать items(). Но это уже слишком многословно.
Поэтому вместо того, чтобы бороться со стековой структурой контекста, я использую ее себе на благо. Ведь вместо того, чтобы копировать по сути весь контекст, можно естественным образом добавить свои переменные в еще один уровень стека и передать этот же самый контекст в свой шаблон! Тогда шаблон увидит и свои переменные, и все остальные, что были в контексте до этого.
Жаль только, что декоратор inclusion_tag
написан так, что сделать этого не позволяет. Поэтому я пишу тег руками:
class MyTagNode(template.Node):
def render(self, context):
context.update({
# собственные переменные
})
result = template.loader.get_template('template.html').render(context)
context.pop()
return result
@register.tag
def my_tag(parser, token):
return MyTagNode()
Метод context.update
берет переданный dict
и "пушит" его наверх стека. Соответственно context.pop
его потом оттуда удаляет. Вуаля!
Хотя, повторюсь, хотелось бы, чтобы это было дефолтным поведением inclusion-тега. Потому что он все таки удобней, чем написание тега целиком.
Комментарии: 10
Что мешает декоратор свой написать? :)
Да я, в общем-то, не за себя беспокоюсь :-). Просто мне кажется, что если бы это было в Джанге "из коробки", это было бы более естественно.
Эх,
inclusion_tag
,inclusion_tag
- в нем много чего хочется допилить...а еще
*args
и**kwargs
в simple_tagА вот мне совсем не хочется видеть его допиленным. Сказать по правде, я думаю, что он просто сам по себе — уже отживший паттерн. Он решает задачу выдачи контекста в шаблон, а она не является тем, что нужно решать. В моем примере это по сути одна строка. Гораздо важнее и в
simple_tag
, и вinclusion_tag
— это устранение boilerplate-кода по парсингу и ресолвингу переменных из контекста.Что бы мне хотелось — так это чего-то такого:
То есть чтобы мне передавали в функцию и контекст, и вложенные node'ы, если я написал тег как
{% mytag %} .. {% endmytag %}
, а также распарсенные и разресолвленные по нормальным правилам аргументы в тег. А уж как я буду формировать строку на выход — это уже неважно. Если надо будет шаблон вложенный подключить, всегда есть простой и короткий вызовrender_to_string
.Немного не согласен. Он абстрагирует не только от загрузки шаблона, но и дает использовать вызывающий контекст (о ресолвинге параметров не говорим) - что по сути чуть ли не главное его достоинство. Причем этот контекст зачастую более важен для логики самого тега, а не шаблона как такового который его отображает - вот поэтому и ценен.
C "нормальными" правилами тоже проблема - оно не всегда надо, а когда не надо - применение этих правил вызовет синтаксическую ошибку. Взять для примера тег
{% url %}
у которого параметры совсем не по правилам резолвятся. Т.е. дробятся на биты как обычно, а вот логика их интерпретации отличается. Вот с битами в большинстве случаев и идет работа.Самая, на мой взгляд, большая проблема существующей системы расширения шаблонного движка - это её двухголовость (опять таки не рассматриваем шорткаты). Расширение стоит практически из синтаксического
хендлера
и более высокоуровнегонода
. Если бы их соединить в некую одну сущность - то было бы счастье.Но это не отменяет попсовой хотелки - допилить
inclusion_tag
до большей гибкости.Вот так:-)
Я, на самом деле, примерно о том же говорю :-). По задумке
inclusion_tag
должен был решать задачу подгрузки вложенного шаблона. А используют его из-за того, что у него отросло совершенно случайно :-). При это совершенно случайно этого (контекста) нет вsimple_tag
, хотя оно там нужен не меньше. Мой основной поинт, что не нужно разделения на inclusion и simple. Большинство юзерских тегов укладывается в один шаблон использования, что я выше и привел.Поскольку виноват в этом я, я могу совершшено точно сказать, что в этом нет никакой великой цели :-). Я сделал
{% url %}
с такими правилами парсинга только потому, что тогда они были такими же у первого попавшегося мне тогда тега, который принимал "список чего-то" —{% cycle %}
, в котором одним кусочком шла литеральная строка через запятые. Если бы я делал его сейчас, то наверное бы стоило делать так:То есть он должен укладываться в формат разделенных пробелами битов, каждый из которых может быть переменной или строкой, а также может быть парой name=value. Всем было бы проще.
Столько всего уже написали блин :-) поздновато я с коментами... Скажу просто: Иван, давай еще про джангу! :D
Негодование в зале
А как же завет "сделать красиво"?:-)
Вообще ты прав, надо как-то стандартизировать, наконец, обычные параметры и именованные. Добавить логику их обработки в парсер, чтобы совсем избавиться от рутины и неоднозначности.
Точно :) Хочется какой-то апгрейд для simple_tag + inclusion_tag. Может уже есть какое-то решение, а я и не в курсе? :)
Как вариант - в simple_tag добавить возможность передавать контекст.
Не супер-универсально, но очень просто и покрывает очень много случаев. Тот же inclusion_tag можно этим тэгом заменить.
Спасибо - полезный пост :)