很长一段时间以来,我一直在努力弄清楚如何使用Django模板中动态创建的列表,这意味着能够:
这些列表应该能够处理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
{% 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
本地更新。多么令人失望! 有关如何在循环外使用自定义列表的任何想法?这不可能吗?
非常感谢你花时间帮助我做这件事。我第一次在这里发帖,所以如果有需要,请告诉我!
答案 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
类型是TextNode
,VariableNode
,IfNode
和ForNode
,但插件模块 可以定义自己的自定义节点类型。每个
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
我的问题的用例,自定义列表显示了所需的内容。
如果有什么不清楚,或者您认为我可能遗漏了什么,请随时告诉我!无论如何,感谢阅读!