4

我想使用实现__getitem__接口的自定义对象来呈现 Jinja2 模板。该对象实现了惰性变量查找,因为不可能从中创建字典(可用变量的数量几乎是无限的,值检索在查询的键上动态工作)。

是否可以使用上下文对象呈现 Jinja2 模板?

# Invalid code, but I'd like to have such an interface.
#

from jinja2 import Template

class Context(object):

    def __getitem__(self, name):
        # Create a value dynamically based on `name`
        if name.startswith('customer'):
             key = name[len('customer_'):]
             return getattr(get_customer(), key)
        raise KeyError(name)

t = Template('Dear {{ customer_first }},\n')
t.render(Context())
4

3 回答 3

3

我现在想出了这个(非常hacky和丑陋的)解决方案。

t = CustomTemplate(source)
t.set_custom_context(Context())
print t.render()

使用以下替换:

from jinja2.environment import Template as JinjaTemplate
from jinja2.runtime import Context as JinjaContext

class CustomContextWrapper(JinjaContext):

    def __init__(self, *args, **kwargs):
        super(CustomContextWrapper, self).__init__(*args, **kwargs)
        self.__custom_context = None

    def set_custom_context(self, custom_context):
        if not hasattr(custom_context, '__getitem__'):
            raise TypeError('custom context object must implement __getitem__()')
        self.__custom_context = custom_context

    # JinjaContext overrides

    def resolve(self, key):
        if self.__custom_context:
            try:
                return self.__custom_context[key]
            except KeyError:
                pass
        return super(CustomContextWrapper, self).resolve(key)

class CustomTemplate(JinjaTemplate):

    def set_custom_context(self, custom_context):
        self.__custom_context = custom_context

    # From jinja2.environment (2.7), modified
    def new_context(self, vars=None, shared=False, locals=None,
                    context_class=CustomContextWrapper):
        context = new_context(self.environment, self.name, self.blocks,
                              vars, shared, self.globals, locals,
                              context_class=context_class)
        context.set_custom_context(self.__custom_context)
        return context

# From jinja2.runtime (2.7), modified
def new_context(environment, template_name, blocks, vars=None,
                shared=None, globals=None, locals=None,
                context_class=CustomContextWrapper):
    """Internal helper to for context creation."""
    if vars is None:
        vars = {}
    if shared:
        parent = vars
    else:
        parent = dict(globals or (), **vars)
    if locals:
        # if the parent is shared a copy should be created because
        # we don't want to modify the dict passed
        if shared:
            parent = dict(parent)
        for key, value in iteritems(locals):
            if key[:2] == 'l_' and value is not missing:
                parent[key[2:]] = value
    return context_class(environment, parent, template_name, blocks)

谁能提供更好的解决方案?

于 2013-08-03T18:30:11.983 回答
1

看起来你有一个函数,get_customer()它返回一个字典,还是一个对象?

为什么不直接将它传递给模板?

from jinja2 import Template
t = Template('Dear {{ customer.first }},\n')
t.render(customer=get_customer())

IIRC,Jinja 对不存在的密钥非常宽容,因此customer.bogus_key不应该崩溃。

于 2013-08-03T22:05:14.270 回答
0

经过大量的挖掘,我找到了最干净的方法。

首先创建jinja2.runtime.Context该实现的子类resolve_or_missingdocs):

from jinja2.runtime import Context


class MyContext(Context):
  """A custom jinja2 context class."""

  def resolve_or_missing(self, key):
    # TODO(you): Add your custom behavior here
    return super(TrackingContext, self).resolve_or_missing(key)

然后你需要做的就是设置context_classJinja 环境的变量(文档

env.context_class = MyContext

我正在使用 Flask,所以我这样做了:

flask.current_app.jinja_environment.context_class = MyContext
于 2021-09-29T12:14:26.713 回答