Ответ 1
Вам не нужно писать свой собственный тег. Это делает {% with %}
.
Я хочу иметь возможность устанавливать переменные в шаблоне в строковые значения. Я написал тег, но он, похоже, не меняет контекст. Предполагаемое использование:
{% define "a string" as my_var %}
Обновление (решение):
class DefineNode(Node):
def __init__(self, var, name):
self.var = var
self.name = name
def __repr__(self):
return "<DefineNode>"
def render(self, context):
context[self.name] = self.var
return ''
@register.tag
def define(parser, token):
"""
Adds a name to the context for referencing an arbitrarily defined string.
For example:
{% define "my_string" as my_string %}
Now anywhere in the template:
{{ my_string }}
"""
bits = list(token.split_contents())
if (len(bits) != 4 or bits[2] != "as") or \
not (bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]):
raise TemplateSyntaxError("%r expected format is '\"string\" as name'" % bits[0])
else:
value = bits[1][1:-1]
name = bits[3]
return DefineNode(value, name)
Вам не нужно писать свой собственный тег. Это делает {% with %}
.
Django уже рассмотрел этот частный случай и предоставляет теги привязки, специальный способ регистрации тегов, которые устанавливают переменную в контекст.
В этом случае вам не нужно заботиться о поиске, обновлении и сохранении контекста. Вы просто делаете это:
@register.assignment_tag
def define(the_string):
return the_string
И вы можете использовать его так же, но он намного чище:
{% define "a string" as my_var %}
Это весь необходимый код.
EDIT:
Как отметил Дирк Бергстром, поскольку версия django 1.9 assignment_tag
устарела. simple_tag
- идеальная замена.
@register.simple_tag
def define(the_string):
return the_string
Ответ скрыт внутри более сложный пример current_time в документации.
Проблема
Вы хотите добавить переменную в контекст. Но вы не хотите возвращаться и добавлять эту переменную ко всем представлениям, которые вызывают все шаблоны, вызывающие тег. Вам просто нужен тег, который может добавить некоторые данные в контекст, где бы он ни захотел. Я искал такие вещи, когда делал эти случайные отвлекающие факторы, которые попадают в боковые панели, и конкретно не связаны с работой основного вида, например.
Метод
Чтобы ввести переменную в контекст, вам нужен доступ к контексту. Для этого пользовательский тег будет вводить node, который добавляет данные в контекст шаблона.
Пример
В этом примере добавляется запрос "coming_events" в контекст, затем выполняется цикл по каждому результату. Он делает это, объявляя пользовательский тег, который отображает node, который добавляет запрос в контекст.
from django import template
from apps.events.models import Event
register = template.Library()
@register.tag
def coming_events(parser, token):
return EventNode()
class EventNode(template.Node):
def render(self, context):
context['coming_events'] = Event.objects.all()
return ''
Вы бы использовали его следующим образом:
{% load events %}
{% coming_events %}
{% for event in coming_events %}
<div class="eventItem">
<p>{{event.title}} {{event.data}}</p>
</div>
{% endfor %}
Дополнительный кредит
Если вы действительно хотите назвать переменную произвольно, например {% coming_events как events%}, посмотрите внимательно на пример в документации и обратите внимание, как они разделяют токен на то, что перед "как", а затем и использовать последнюю часть, чтобы назвать переменную контекста. Вы должны были бы реализовать это.
Обратите внимание, что если я закончил помещать HTML для каждого события в свой собственный выделенный шаблон, мне было бы лучше просто следовать стандарту стандартного тега включения документация. Это решение предлагается, когда вам нужны данные без какого-либо багажа.
Если вы хотите, чтобы переменная была доступна в других блоках шаблонов, вы должны посмотреть http://od-eon.com/blogs/liviu/scope-variables-template-blocks/. В частности, в коде пользовательского тэга вы должны заменить:
context[some_var_name] = some_val
с:
context.dicts[0][some_var_name] = some_val
Это сделает трюк (хотя это, возможно, уродливый трюк, и вы должны рассмотреть альтернативы).
Прежде всего, вы обычно хотите установить переменные контекста в своем представлении. Ввод логики в шаблон - действительно формула для добавленного беспорядка. Тем не менее, настало время, когда вы хотите использовать это, а тэг {% with%} создает беспорядок, так как вам нужно закончить его с помощью {% endwith%}, потеряв переменную. Проблема, с которой я столкнулся, заключается в том, что я не могу включить шаблон, передавая ему значение. Я бы хотел:
{% if criteria %}
{% define 'foo' as some_option %}
{% else %}
{% define 'bar' as some_option %}
{% endif %}
{% include "some_template_partial.html" %}
Это невозможно сделать, используя теги {% with%} без повторного кода:
{% if criteria %}
{% with 'foo' as some_option %}
{% include "some_template_partial.html" %}
{% endwith %}
{% else %}
{% with 'bar' as some_option %}
{% include "some_template_partial.html" %}
{% endwith %}
{% endif %}
Прекрасно, как сейчас, но это будет деградировать в ужасный беспорядок, когда случаи размножаются. Таким образом, этот код был написан:
from django import template
from django.conf import settings
import logging
import re
register = template.Library()
NAMESPACE_PROTECTION = settings.DEBUG
class define_node(template.Node):
def __init__(self, value, key, parse):
self.value = value
self.key = key
self.parse = parse
def render(self, context):
if NAMESPACE_PROTECTION:
if self.key in context:
raise Exception("EPIC NAMESPACE FAIL, CONTEXT HAZ A %s" % self.key)
if self.parse:
context[self.key] = context[self.value]
else:
context[self.key] = self.value
return ''
@register.tag
def define(parser, token):
"""Definition template tag. Use to define variables in your context within the template.
Sorta like the {% with "blah" as blah %} tag, but without the {% endwith %} mess.
Supports two modes:
Literal mode: argument is encapsulated with quotes (e.g. "blah" or 'blah')
variable, is set to the string literal, ex:
{% define "fish" as foo %}
Variable mode: argument is prefixed with a $ (e.g. $blah or $monkey)
variable is copied from another context variable, ex:
{% define $fish as foo %}
Namespace protection is also provided if django.conf.settings.DEBUG is True.
You will get an epic namespace fail if that occurs (please fix it before you deploy)
TODO:
* define override nomenclature if you REALLY want to overwrite a variable
- should decide what nomeclature to use first
* expand on variables so that {% define $array.blah as foo %} will work
(this currently WILL NOT)
"""
try:
tag_name, arg = token.contents.split(None, 1)
except ValueError:
raise template.TemplateSyntaxError, "%r tag requires arguments" % token.contents.split()[0]
m = re.search(r'(.*?) as (\w+)', arg)
if not m:
raise template.TemplateSyntaxError, "%r tag had invalid arguments" % tag_name
value, key = m.groups()
if (value[0] == value[-1] and value[0] in ('"', "'")):
ret = value[1:-1]
parse = False
elif (value[0] == '$'):
ret = value[1:]
parse = True
else:
raise template.TemplateSyntaxError, "%r tag first argument indeciperable" % tag_name
return define_node(ret, key, parse)
Более гибкий способ, которым вы можете это сделать, можно найти в моем сообщении здесь: http://www.soyoucode.com/2011/set-variable-django-template
Вы можете использовать ответ kiril. Это довольно просто. Вы также можете использовать тег set_context
django-libs.
Пример:
{% set_context foo.bar|filter_foo as foobar %}