我现在已经解决了我的问题,类似于 fabrizioM 的建议,但有一个显着的区别:由于菜单项表示可以(并且大多数时候确实)包含 HTML,我不想直接在present 方法。所以我最终在 Python 中实现了菜单定义,在 Jinja 中实现了演示,相互递归弥合了差距。
不同类型的菜单项由不同的子类表示:
class MenuItem(object):
def present(self, macromap):
return macromap[type(self).__name__](self, macromap)
class TextLink(MenuItem):
def __init__(self, url, text):
self.url, self.text = url, text
class Section(MenuItem):
def __init__(self, text, items):
self.text, self.items = text, items
class ImageLink(MenuItem):
...
上面引用的macromap 是将菜单项的类型映射到实现其表示的宏的字典。这一切都在 Jinja 中定义:
{% macro TextLink(l, macromap) %}
<a class="menuitem" href="{{l.url|escape}}">
{{ l.text|escape }}
</a>
{% endmacro %}
{% macro Section(s, macromap) %}
<div class="heading">{{s.text}}</div>
<ul class="items">
{% for item in s.items %}
<li>{{ item.present(macromap) }}</li>
{% endfor %}
</ul>
{% endmacro %}
{% set default_map = {'TextLink': TextLink, 'Section': Section, ...}
实际的菜单定义清晰地表达为MenuItem 子类的树:
main_menu = section("Main Menu", [
section("Product Line 1", [
TextLink("/products/...", "A product"),
...
]),
section(...),
])
要开始演示,模板必须调用顶级部分的present 方法,传递一个宏映射来指定如何显示菜单,例如main_menu.present(default_map)。正如在Section 宏中最好看到的那样,菜单项可以让他们的孩子展示自己,他们的present 方法将调用另一个 Jinja 宏,以此类推,递归。
必须显式地传递宏映射不是很漂亮,但它带来了一个宝贵的好处:现在可以轻松地呈现菜单数据的不同表示,而无需触及菜单定义。例如,可以定义宏地图来呈现主网站菜单,或用于移动设备的变体(以防 CSS 不够用),或 XML 站点地图,甚至是纯文本版本。 (我们实际上最终将这个系统用于网站菜单和站点地图案例。)