beancount / fava

Fava - web interface for Beancount
https://beancount.github.io/fava/
MIT License
1.98k stars 291 forks source link

Extension templates can't include other files #1635

Closed tschicke closed 1 year ago

tschicke commented 1 year ago

Because the extension_report function uses render_template_string to render the extension template file contents, splitting an extension template into multiple files and using {% include "sub_template.html" %} isn't possible, since the extension's template directory isn't part of the Jinja/Flask search path. I tried a hack of temporarily setting fava_app.jinja_loader before calling render_template_string to inject the right search path:

current_loader = fava_app.jinja_loader
fava_app.jinja_loader = jinja2.ChoiceLoader(
    [
        jinja2.FileSystemLoader(
            [str(ext.extension_dir / "templates")]
        ),
        current_loader,
    ]
)
content = Markup(render_template_string(template, extension=ext))
fava_app.jinja_loader = current_loader

This works, and the render_template_string could probably also be switched to a render_template, but it isn't thread safe since I think it's possible for multiple requests get handled simultaneously in Flask. I haven't been able to find a good way to properly use more than one template directory within Flask. The closest thing I've found is that Blueprints can have their own template directories that get used in addition to the base template directory, so I think if a Blueprint got created and registered for each extension then this would be possible. The problem I can see with this is that the beancount file, and therefore the extensions, don't get loaded until the first request is made, at which point fava_app is obviously already running, so new Blueprints can't be registered. If the beancount file was loaded initially on startup then the Blueprints could be created and registered, and I think everything should work correctly. I can try to prepare a pull request that does all of this, but I wasn't sure if there was a good reason that the beancount file doesn't get loaded immediately that is more important than this.

yagebu commented 1 year ago

I don't think we need a Blueprint for this - this could be solved by using a jinja2.FunctionLoader which checks whether an extension (we could add it as g.extension to enable that) is active and then conditionally use a FileSystemLoader for that extension.

tschicke commented 1 year ago

Oh cool, thanks. I'm not too familiar with jinja so I hadn't looked into the different types of loaders. I'll try to get something working with that.

tschicke commented 1 year ago

I have a relatively straightforward change at tschicke/fava@c98f0d69210240b1aff0b73ba6758cd4dd36a44e that fixes this with a jinja2.FunctionLoader, but I don't see a way to get it to pass this linter error: src/fava/application.py:460: error: Incompatible types in assignment (expression has type "ChoiceLoader", variable has type "FileSystemLoader | None") [assignment]