[FIXED] Flask: get the context on the filter

Issue

I’m new to flask. I want to practice filters, read the official documentation but it doesn’t help.

I want to create a firstof filter, i.e. output the first argument variable that is not “false” (Django’s firstof).

Hello {{ "username, 'guest'"|first}}

@app.route('/')
Def index():
    return render_template('index.html') # expect output: hello guests
    # return render_template('index.html', username='Carson') # Hello Carson
 

I’ll do my best

@app.template_filter('firstof')
def firstof_filter(s: str):
    """
    how to use::

        {{ "var1, var2, 'default_val'" | first }}
    """
    For data in s.split(',') :
        data = data.strip()
        if data[0] == data[-1] and data[0] in ("'", '"'):
            return data[1:-1] # constant
        """ # Is there a similar syntax like this?
        For hasattr (app.current.context, data):
            return app.current.context[data]
        """
 

I would appreciate it if you could provide more links about flask-filter. Thank you!

Solution

I found an answer by myself.

It isn’t easy to achieve it with Flask directly.

because

# flask.templating.py

def render_template(template_name_or_list, **context):
    ctx = _app_ctx_stack.top
    ctx.app.update_template_context(context)  # it does not store the context in the ctx. it just adds something to the context
    return _render(
        ctx.app.jinja_env.get_or_select_template(template_name_or_list),
        # ctx.app.jinja_env is an environment (It combined Flask and Jinja, pretty like the Jinja.Environment)
        # get_or_select_template, will run our function and no passing the context so that is why it so hard to do it.
        context,
        ctx.app,
    )

def _render(template, context, app):
    # It seems we can't do anything when the process already here.
    before_render_template.send(app, template=template, context=context)
    rv = template.render(context)
    template_rendered.send(app, template=template, context=context)
    return rv

But we get a clue that is we can change the Jinja.Environment.

you may reference the Custom Filters first before you go ahead.

Solution & demo

from flask import render_template
from jinja2 import contextfilter
from flask.helpers import locked_cached_property
from flask import Flask
from jinja2.runtime import Context
from flask.templating import Environment
from typing import Dict
from pathlib import Path

@contextfilter
def firstof_filter(ctx: Context, s: str):
    ctx: Dict = ctx.parent
    for data in s.split(','):
        data = data.strip()
        if data[0] == data[-1] and data[0] in ("'", '"'):
            return data[1:-1]  # a constant
        if data in ctx:
            return ctx[data]

class MyFlask(Flask):
    @locked_cached_property
    def jinja_env(self):
        env: Environment = self.create_jinja_environment()
        env.filters['firstof'] = firstof_filter  # well, this way is not smart, you are better build a register to get all the filters that you have defined.
        return env

app = MyFlask(__name__, template_folder=Path('./templates'))


@app.route('/')
def test():
    return render_template('test.html', username='Carson') + '\n' + render_template('test.html')


app.run(debug=True)

where test.html

<h1>Hi, {{ "username, 'guest'"| firstof }} </h1>

p.s.

env.filters['firstof'] = firstof_filter Well, this way is not smart. You are better to build a register to get all the filters that you have defined.

you can reference Django: django.template.library.py -> class Library to collect all filters

output

Hi, Carson
Hi, guest

Answered By – Carson

Answer Checked By – David Goodson (Easybugfix Volunteer)

Leave a Reply

(*) Required, Your email will not be published