有没有一个Django模板标签可以让我设置上下文变量?

18

我希望能够在模板中设置变量为字符串值。我写了一个标签,但似乎它没有改变上下文。预期的使用方式如下:

{% 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)

看起来不错。我有一个类似的标签,唯一的区别是我使用了 "register.tag ('define',define)",但注释不应该产生影响,你会期望它们相同。 - Alexander Torstling
也许你遇到了名称冲突?“define”似乎很危险... - Alexander Torstling
8个回答

24

Django已经考虑到这种情况,提供了赋值标签,这是一种特殊的注册标签的方式,可以在上下文中设置变量。

在这种情况下,您无需担心检索、更新和保存上下文。您只需要执行以下操作:

@register.assignment_tag
def define(the_string):
  return the_string

你可以以完全相同的方式使用它,但它更加简洁明了

{% define "a string" as my_var %}

这是你所需要的全部代码。
编辑: 正如Dirk Bergstrom指出的那样,自Django 1.9版本以来,assignment_tag已被弃用。simple_tag是一个完美的替代品。
@register.simple_tag
def define(the_string):
  return the_string

1
所有其他答案都已过时。如果您正在寻找解决方案,请使用此选项! - Mark Chackerian
1
与Django 1.10非常兼容。请注意,不使用“as”将仅显示该值,因此您仍可以将@register.simple_tag更改为@register.assignment_tag,并且不必更改使用它的地方,例如{% your_tag 'something'%},因为这将会显示该值。 - mrmuggles
3
自Django 1.9起,assignment_tag已被弃用。simple_tag是新的主流:https://docs.djangoproject.com/en/1.11/howto/custom-template-tags/#django.template.Library.simple_tag - Dirk Bergstrom

12

答案位于文档中更复杂的current_time示例内部。

问题

您想要将变量添加到上下文中。但是,您不想返回并将该变量添加到调用所有模板的所有视图中,这些视图调用标签。您只希望有一个标签,可以在需要的任何地方向上下文添加一些数据。例如,当渲染随机放置在侧边栏中且与主视图的工作无关的内容时,我正在寻找此类东西。

方法

要将变量注入上下文,您需要访问上下文。为此,您的自定义标记将注入一个节点,该节点将添加数据到模板上下文中。

示例

此示例向上下文添加了一个"coming_events"查询集,然后循环遍历每个结果。它通过声明一个自定义标记来实现,该标记呈现一个节点,该节点将查询集添加到上下文中。

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 as events %},那么请仔细查看文档中的示例,并注意如何将令牌分成“ as ”之前和之后的部分,并使用后一部分来命名上下文变量。你需要实现这个功能。

请注意,如果我最终将每个事件的HTML放入其专用模板中,那么最好遵循文档中的标准包含标签示例。当您需要数据而不带任何附加信息时,建议使用此解决方案。


感谢您提供这么详细的答案,真的帮了我很大的忙。我很惊讶这不是一个更常见的问题,有一个更简单的解决方案。 - Joseph

10

如果你想让变量在其他模板块中可用,你应该看一下http://od-eon.com/blogs/liviu/scope-variables-template-blocks/。特别是在自定义标签代码中,你应该替换:

context[some_var_name] = some_val

使用:

context.dicts[0][some_var_name] = some_val

这样做可以解决问题(尽管这可能是一个丑陋的技巧,你应该考虑其他替代方案)。


7

您不需要编写自己的标签。内置的{% with %}标签可以实现此功能。


是的,你说得对。这个标签与众不同的一点是它不使用结束标签。名称被放入上下文中供其余模板使用。 - hekevintran
7
我很惊讶这个代码片段被标记为正确,因为它没有解决问题:我没有看到'with'创建了一个新的上下文变量;它只是暂时重命名了一个现有的变量。 - John Mee
12
这并不是对问题的解决方案。 "with" 仅在模板的有限部分内临时更改变量名称。它没有将新变量添加到更大的上下文中,而问题所要求的是这样做。 - Cerin

3

您可以使用自定义模板标签来设置变量,如下所示:

在 templatetags 文件夹中创建名为 set_var.py 的文件,其中包含以下代码:

from django import template

register = template.Library()

class SetVarNode(template.Node):

    def __init__(self, var_name, var_value):
        self.var_name = var_name
        self.var_value = var_value

    def render(self, context):
        try:
            value = template.Variable(self.var_value).resolve(context)
        except template.VariableDoesNotExist:
            value = ""
        context[self.var_name] = value
        return u""

def set_var(parser, token):
    """
        {% set <var_name>  = <var_value> %}
    """
    parts = token.split_contents()
    if len(parts) < 4:
        raise template.TemplateSyntaxError("'set' tag must be of the form:  {% set <var_name>  = <var_value> %}")
    return SetVarNode(parts[1], parts[3])

register.tag('set', set_var)

然后,在你的模板中使用它,只需要按照以下步骤进行:
{% load set_var %}

{% set a = 3 %}
{% set b = some_context_variable %}
{% set c = "some string" %}

Value of a is {{a}}
Value of b is {{b}}
Value of c is {{c}}

该网站无法访问! - 4ndt3s
@jahuuar 更新了答案。感谢提醒! - Nicu Surdu

3

首先,在视图中通常需要设置上下文变量。在模板中放置逻辑代码会使代码更加混乱。即便如此,有时候你仍然需要使用它。由于 {% 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's first argument indeciperable" % tag_name
  return define_node(ret, key, parse)

1
实际上,你的例子可以在一行代码中完成: {% include "some_template_partial.html" with some_option=criteria|yesno:"foo,bar" %} - Alan

3
你可以使用Kiril的答案。这很简单。你也可以使用django-libsset_context标签。
示例:
{% set_context foo.bar|filter_foo as foobar %}

很喜欢这个。但不幸的是,django-libs没有正确处理verbatim,所以我不得不将其删除并使用@kiril上面的答案。 - Nimo

0

从Django 4.0开始,这变得更加简单了:

from django import template

register = template.Library()

@register.simple_tag(takes_context=True)
def my_template_tag(context, any_var_or_object):
   result = None
   # do stuff that makes result not None
   context.update({'result': result})
   return ''

然后只需这样使用:

{% my_template_tag user %}
{# say my tag does an ORM query on the user and assigns the resulting queryset #}
{% for item in result %}
   {# do stuff #}
{% endfor %}

正如早期的答案所述 - 您应该尝试在视图中定义您上下文中的所有内容。使用模板标签来处理所有上下文将很快变得难以维护 - 但对于偶尔出现的事情或极其罕见的情况非常有用,当您实际上无法将其放入视图中时

如果您想要打印字符串或其他内容 - 而不是在页面上下文中设置新变量,请参考Kiril的答案

更多信息,请参见: https://docs.djangoproject.com/en/dev/howto/custom-template-tags/#simple-tags


1
据我所知,这个解决方案适用于Django 3.2,并且可能向后兼容到2.0。我需要做的唯一修改是@register.simple_tag(name='my_template_tag', takes_context=True)…。需要注意的是它只能在页面块内使用,如果你想在多个页面块中使用它,你必须设置多次... - typonaut

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接