是否可以在Django模板中的任何位置使用新创建的上下文变量?

时间:2017-08-16 19:38:22

标签: django django-templates

很长一段时间以来,我一直在努力弄清楚如何使用Django模板中动态创建的列表,这意味着能够:

  • 直接在django模板中创建一个列表,
  • 将新元素添加到该列表
  • 连接2个列表。

这些列表应该能够处理django对象,而不仅仅是简单的字符串。例如,就我而言,我希望我的列表能够存储表单字段(示例如下)。

经过多次研究,我发现除了简单的事情之外我不可能做到这一点,如果我想实现自己的目的,我必须创建自己的自定义标签。我的自定义标记写在下面。请注意this post帮助我这样做了。

我面临的问题

自定义标记有效,我在for loop中使用它。这里生成的列表是根据循环正确演变的,我可以像任何变量一样调用它,同时仍然在循环(因为它是在Django上下文中导出的): {{listName} }

但是!一旦我在那个循环之外,我的列表似乎根本没有更新!就像它只存在于for循环中一样...我首先想到当某些东西被定义为Django模板上下文时,它可以在模板内的任何地方使用,而不仅仅是在定义它的块内部。 我错过了什么吗?这是Django的正常行为吗?我一直无法找到该问题的答案。

自定义标记

@register.tag()
def setList(parser, token):
    """
    Use : {% setList par1 par2 ... parN as listName %}
    'par' can be a simple variable or a list
    To set an empty list: {% setList '' as listName %}
    """
    data = list(token.split_contents())
    if len(data) >= 4 and data[-2] == "as":
        listName = data[-1]
        items = data[1:-2]
        return SetListNode(items, listName)
    else:
        raise template.TemplateSyntaxError(
            "Erreur ! L'utilisation de %r est la suivante : {%% setList par1 par2 ... parN as listName %%}" % data[0]
        )

class SetListNode(template.Node):

  def __init__(self, items, listName):
      self.items = []
      for item in items: self.items.append(template.Variable(item))
      self.listName = listName

  def render(self, context):
      finalList = []
      for item in self.items:
          itemR = item.resolve(context)
          if isinstance(itemR, list): finalList.extend(itemR)
          elif itemR == '': pass
          else: finalList.append(itemR)
      context[self.listName] = list(finalList)
      return "" # django doc : render() always returns a string

在Django模板中使用我的自定义标签

{% setList '' as new_list %}
new_list value is: {{ new_list }} # shows me an empty list: OK!

# then I iter on a forms.RadioSelect field
{% for field in form.fields %}
  {% if field.choice_label in some_other_list %}
    {% setList new_list field as new_list %}
  {% endif %}
  {{ new_list }} # a new item is added to new_list when necessary: OK!
{% endfor %}

{{ new_list }} # just shows an empty list, the one from the begining: THE ISSUE!

所以:看起来我的初始列表只是在for loop本地更新。多么令人失望! 有关如何在循环外使用自定义列表的任何想法?这不可能吗?

非常感谢你花时间帮助我做这件事。我第一次在这里发帖,所以如果有需要,请告诉我!

2 个答案:

答案 0 :(得分:1)

首先,这是一个很好的问题。

现在对业务:Context对象(将变量名称映射到变量值的字典)是堆栈。也就是说,您可以push()pop()它。

有了这些知识,让我们再次看一下代码:

# Enter scoped block.
# Push new_list as empty list onto the context stack.
{% setList '' as new_list %}
new_list value is: {{ new_list }}

# Enter for-loop scoped block.
{% for field in form.fields %}
  {% if field.choice_label in some_other_list %}
    # Push new new_list onto context.
    {% setList new_list field as new_list %}
  {% endif %}

  # Print most current value named new_list (at the top)
  {{ new_list }}

# Exit for-loop.
# Pop the loop variables pushed on to the context to avoid
# the context ending up in an inconsistent state when other
# tags (e.g., include and with) push data to context.
{% endfor %}

# new_list's value is again empty list, 
# since all other values under that name were poped off the stack.
{{ new_list }}

# Pop any values left.
# Exit scoped block.

这是怎么回事?来自django.template.base源代码:

  

[Django模板系统]的工作原理:

     

Lexer.tokenize()函数转换模板字符串(即a   包含标记和自定义模板标记的字符串)到标记,其中   可以是纯文本(TOKEN_TEXT),变量(TOKEN_VAR)或   阻止陈述(TOKEN_BLOCK)。

     

Parser()类在其构造函数中获取标记列表,并且   它的parse()方法返回一个已编译的模板 - 在...下面   引擎盖,Node对象列表。

     

每个Node负责创建某种输出 - 例如   简单文本(TextNode),给定上下文中的变量值   (VariableNode),基本逻辑(IfNode)的结果,结果   循环(ForNode)或其他任何东西。核心Node类型是   TextNodeVariableNodeIfNodeForNode,但插件模块   可以定义自己的自定义节点类型。

     

每个Node都有一个render()方法,该方法需要Context和   返回呈现节点的字符串。例如,render()   VariableNode的方法将变量的值作为字符串返回。   render()的{​​{1}}方法返回渲染的输出   循环内的任何东西,递归地。

     

ForNode类是一个方便的包装器,可以处理   模板编译和渲染。

总而言之,Template的作用是: 它需要一些标记代码(无论是ForNode标记内部),在堆栈上推送一些变量,用它们编译HTML,从堆栈中弹出引入的变量并返回所述HTML。

*此外,您还可以查看for ForNode实施itself。它需要render作为参数并返回context,这是一个字符串。

可悲的是,你无法绕过这种机制。除非你完全写自己的。

干杯。

答案 1 :(得分:0)

有一个解决方案!

好吧,在花了很多时间理解和打印我能理解发生的事情之后,我终于成功了!非常感谢 @Siegmeyer ,他真正帮助我清楚地看到了真正的Django Context对象。

首先,您可以查看{{3>}上的 Django模板上下文源代码。 正如@Siegmeyer所说,Django中的Context可以作为堆栈使用。您将无法将其用作经典字典,尤其是如果要将变量添加到上下文中。请阅读@Siegmeyer的解释,这对我来说已经足够清楚了。此外,他/她告诉我答案而没有给我一个专门满足我需要的现有适当的方法,但也许这是故意让我更努力地阅读文档;-)毕竟这本来是一件好事。

让我们看一下我正在撰写的BaseContext(来自django.template.context源代码)的方法:

def set_upward(self, key, value):
        """
        Set a variable in one of the higher contexts if it exists there,
        otherwise in the current context.
        """
        context = self.dicts[-1]
        for d in reversed(self.dicts):
            if key in d.keys():
                context = d
                break
        context[key] = value

如您所见,set_upward方法完全满足我的需求(自定义列表位于更高的上下文中)。除了看起来它只是在查看 n-1 上下文之外,所以如果你在for循环中定义自定义变量太深,你可能无法在更高的位置访问它级别(> n-1 ,但我没有测试过)。也许避免这种情况的好处是在context_processors.py文件中定义所需的变量,以便可以在模板中的任何位置访问它(虽然不确定)。

自定义标签:结束

最后,我的自定义标记允许我动态定义列表

@register.tag()
def setList(parser, token) [...] # no change, please see my question


class SetListNode(template.Node):

    def __init__(self, items, listName):
        self.items = []
        for item in items: self.items.append(template.Variable(item))
        self.listName = listName

    def render(self, context):
        finalList = []
        for item in self.items:
            itemR = item.resolve(context)
            if isinstance(itemR, list): finalList.extend(itemR)
            elif itemR == '': pass
            else: finalList.append(itemR)
        context.set_upward(self.listName, list(finalList))
        return "" # django doc : render() always return a string

我的问题的用例,自定义列表显示了所需的内容。

如果有什么不清楚,或者您认为我可能遗漏了什么,请随时告诉我!无论如何,感谢阅读!