pallets / jinja

A very fast and expressive template engine.

Home Page:https://jinja.palletsprojects.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Allow passing multiple templates to import, just like include

pawamoy opened this issue · comments

Just like include, I would like to be able to pass multiple template paths/names to the import tag, so that, quoting the docs, each will be tried in order until one is not missing.

This would help in the following case, where instead of defining and adding a test to the env, then conditions inside templates, we could just list the two paths in a single import tag:

Before:

self.env.tests["existing_template"] = lambda template_name: template_name in self.env.list_templates()
{% set lang_pth = "languages/" ~ locale ~ ".html" %}
{% if lang_pth is existing_template %}
    {% import lang_pth as lang %}
{% else %}
    {% import "languages/en.html" as lang %}
{% endif %}

{% import "_base/languages/en.html" as fallback %}

{% macro t(key) %}{{ lang.t(key) or fallback.t(key) }}{% endmacro %}

After:

{% import ["languages/" ~ locale ~ ".html", "_base/languages/en.html"] as lang %}
{% macro t(key) %}{{ lang.t(key) }}{% endmacro %}

I guess it would need to support the from ... import ... syntax too:

{% from ["languages/" ~ locale ~ ".html", "_base/languages/en.html"] import t as translate %}
{% macro t(key) %}{{ translate(key) }}{% endmacro %}

Note to myself:

def _import_common(self, node: t.Union[nodes.Import, nodes.FromImport], frame: Frame) -> None:
    func_name = "get_or_select_template"
    if isinstance(node.template, nodes.Const):
        if isinstance(node.template.value, str):
            func_name = "get_template"
        elif isinstance(node.template.value, (tuple, list)):
            func_name = "select_template"
    elif isinstance(node.template, (nodes.Tuple, nodes.List)):
        func_name = "select_template"

    self.write(f"{self.choose_async('await ')}environment.{func_name}(")
    self.visit(node.template, frame)
    self.write(f", {self.name!r}).")

    if node.with_context:
        f_name = f"make_module{self.choose_async('_async')}"
        self.write(f"{f_name}(context.get_all(), True, {self.dump_local_context(frame)})")
    else:
        self.write(f"_get_default_module{self.choose_async('_async')}(context)")

If this feature request is accepted I'm willing to send a PR 🙂

I'm not sure how this might interact with the caching that Jinja does when compiling templates. import isn't exactly the same mechanism as extends/include. I'm also not sure if it would hinder reasoning about what a given template is going to do, since the import could completely change the behavior of calls rather than only the appearance of blocks.

The behavior and caching of imports already has a lot of old open issues. I'd like old issues like #14, #253, #695, and #916 to be investigated before we decide on adding more complexity to the existing behavior. I'd welcome help with those.

since the import could completely change the behavior of calls rather than only the appearance of blocks

I think there are many ways a Jinja template can become dynamic enough to make it harder to reason about what it does 😄 Surely two templates included in a single include could import and use different macros for example 🤷

But anyway, I totally understand your points. It's very easy for me to land here, send a PR with a test, and just enjoy a new release, but I completely lack the history behind the features, the complexity in supporting them, and the maintenance cost. So not so easy or enjoyable for you, the maintainer(s) 😅

I'll take a look at the issues you mention, and will try to help if I feel I'm capable and have some time to spare!
Thank you for your honest answer and for your time maintaining this amazing library. Jinja is one of the project I enjoy using most (and it's everywhere!) 🚀