Jinja的多态宏

时间:2011-06-21 16:06:38

标签: python jinja2

我正在寻找一种方法来使用一个Jinja宏来调用不同的实现,具体取决于传递的对象的类型。基本上,标准的Python方法多态。现在,我正在使用类似于此的丑陋解决方法:

{% macro menuitem(obj) %}
  {% set type = obj.__class__.__name__ %}
  {% if type == "ImageMenuItem" %}
    {{ imagemenuitem(obj) }}
  {% elif type == "FoobarMenuItem" %}
    {{ foobarmenuitem(obj) }}
  {% else %}
    {{ textmenuitem(obj) }}
  {% endif %}
{% endmacro %}

在纯Python中,可以使用模块环境,例如: globals()[x+'menuitem'],这不是很漂亮,但效果很好。我使用Jinja上下文尝试了类似的东西,但后者似乎没有包含宏定义。

有什么更好的方法来实现我所寻求的目标?

2 个答案:

答案 0 :(得分:7)

OOP的本质:多态性。

Create a presentation Layer for your objects:

class MenuPresentation:
    def present(self):
        raise NotImplementedException()

class ImageMenuPresentation(MenuPresentation):
   def present(self):
       return "magic url "

class TextMenuPresentation(MenuPresentation):
   def present(self):
      return "- text value here"

然后只是一个问题:

{% macro menuitem(obj) %}
  {{ obj.present() }}
{% endmacro %}

答案 1 :(得分:2)

我现在已经解决了我的问题,类似于fabrizioM的建议,但有一个值得注意的区别:由于菜单项演示可以(并且大部分时间都是)包含HTML,我不希望直接在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是一个dict,它将菜单项的类型映射到实现其表示的宏。这一切都在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站点地图,或甚至纯文本版本。 (实际上,我们最终将此系统用于网站菜单和站点地图案例。)