203

I am using jinja2, and I want to call a python function as a helper, using a similar syntax as if I were calling a macro. jinja2 seems intent on preventing me from making a function call, and insists I repeat myself by copying the function into a template as a macro.

Is there any straightforward way to do this? And, is there any way to import a whole set of python functions and have them accessible from jinja2, without going through a whole lot of rigamarole (such as writing an extension)?

Lee
  • 2,539
  • 3
  • 19
  • 8

15 Answers15

264

For those using Flask, put this in your __init__.py:

def clever_function():
    return u'HELLO'

app.jinja_env.globals.update(clever_function=clever_function)

and in your template call it with {{ clever_function() }}

user1614572
  • 153
  • 1
  • 7
John32323
  • 2,721
  • 1
  • 14
  • 2
  • can you pass multiple functions like this? – ffghfgh Jul 03 '17 at 13:44
  • 11
    In newer versions (I am using Jinja2 2.9.6) it seems to work much easier. Use the function as you would use a variable (works also in more complex situations): `from jinja2 import Template ##newline## def clever_function(): ##newline## return "Hello" ##newline## template = Template("{{ clever_function() }}") ##newline## print(template.render(clever_function=clever_function))` – Semjon Mössinger Sep 22 '17 at 10:04
  • 2
    Even 8 years later, if you're using Flask, this seems like a cleaner solution than any of the more recent answers. And to answer the old question from @ffghfgh , yes—you can pass multiple functions. – kevinmicke Jul 26 '19 at 22:44
  • 1
    The solution from Semjon is simply brilliant. Works like a charm! – kxtronic Jan 07 '21 at 21:01
  • I believe this answer is nothing specific to Flask, except perhaps that the Jinja2 environment is called `app.jinja_env` ? – Maëlan Apr 25 '21 at 14:17
  • Brilliant. Thanks a ton buddy – Sagar Dawda Jun 25 '21 at 22:13
  • 4
    Nowadays there is a decorator for this for flask.app: `@app.template_global(name)`. https://flask.palletsprojects.com/en/2.0.x/api/#flask.Flask.template_global https://github.com/pallets/flask/blob/7a73171edc3bcffef96ef6367977ab3ae9af9350/src/flask/app.py#L1177 – Nathan Chappell Jul 14 '21 at 11:57
  • For even simpler syntax, you can also use `@app.add_template_global`. No name need be passed. https://flask.palletsprojects.com/en/2.2.x/api/#flask.Flask.add_template_global – silian-rail Mar 07 '23 at 16:08
  • I don't have a `__ini__.py` file but I tried to do this with a button onclick function and it did not work. The browser console returned: "Uncaught SyntaxError: expected expression, got '<'" – theerrormagnet Apr 29 '23 at 13:56
172

Note: This is Flask specific!

I know this post is quite old, but there are better methods of doing this in the newer versions of Flask using context processors.

Variables can easily be created:

@app.context_processor
def example():
    return dict(myexample='This is an example')

The above can be used in a Jinja2 template with Flask like so:

{{ myexample }}

(Which outputs This is an example)

As well as full fledged functions:

@app.context_processor
def utility_processor():
    def format_price(amount, currency=u'€'):
        return u'{0:.2f}{1}'.format(amount, currency)
    return dict(format_price=format_price)

The above when used like so:

{{ format_price(0.33) }}

(Which outputs the input price with the currency symbol)

Alternatively, you can use jinja filters, baked into Flask. E.g. using decorators:

@app.template_filter('reverse')
def reverse_filter(s):
    return s[::-1]

Or, without decorators, and manually registering the function:

def reverse_filter(s):
    return s[::-1]
app.jinja_env.filters['reverse'] = reverse_filter

Filters applied with the above two methods can be used like this:

{% for x in mylist | reverse %}
{% endfor %}
Liam Stanley
  • 1,961
  • 1
  • 11
  • 11
  • 6
    where should these functions exist, init, views or just anywhere? – knk May 25 '14 at 20:27
  • 4
    `__init__.py` assuming you declared `flask.Flask(__name__)` there. – Liam Stanley Jun 12 '14 at 06:09
  • 9
    Down voted as question asked about Jinja2 and answer is Flask specific. – AJP Nov 24 '14 at 00:12
  • 22
    @AJP Still theoretically answers the question. This is ONE way to solve the issue, granted you are also using Flask. A bit like all JavaScript questions often answer giving alternatives with or without jQuery or questions about Python often answer both for Python2 and 3. The question wasn't excluding Flask. (unlike a question about Py2 would exclude a Py3 answer). This answer helped me. – jeromej May 18 '16 at 00:17
  • With Flask, you got a very easy way to add filters to Jinja! I think that's what you should be using. http://flask.pocoo.org/docs/0.10/templating/#registering-filters IMHO context_processor are more about injecting data. – jeromej May 18 '16 at 00:26
  • 1
    I've added a Jinja filter example to my answer as well, @JeromeJ. I wouldn't say that "injecting" is the correct term. You are not _pre-generating data and injecting it into the template_. Adding a context processor basically does the same thing as Jinja filters. – Liam Stanley May 19 '16 at 02:43
  • 3
    Very helpful, and just what I was looking for. Jinja2 is part of a web framework, and as such is not totally independent of the back-end. I work in both Django and Flask with Python and this post, as well as the others here are relevant to me. Trying to over specify a question is as harmful in my opinion as being unnecessarily vague. –  Oct 28 '16 at 16:55
  • In the section above titled "As well as full fledged functions:" what is the technique for adding multiple functions (e.g. another function in addition to the format_price one? Do you just declare them like the format_price example and also place them indented under the def utility_processor() line? – Luke Mar 18 '20 at 22:45
  • @LiamStanley How would this work where the function outputs several variables? e.g. def myfunction(): x=1, y=2, return x,y – Jack Aug 03 '20 at 11:49
  • I know this is old, but THANK YOU! This is absolutely amazing :) – BrettJ Sep 26 '20 at 07:35
  • I just wanted to mention that the function should be created where "app" is being instantiated. This may sound obvious, but in my case I was creating app with app = Flask(__name__) inside another function, so I had to create the utility_processor function inside the function where I was declaring app. – kevininhe Apr 12 '22 at 03:51
94

I think jinja deliberately makes it difficult to run 'arbitrary' python within a template. It tries to enforce the opinion that less logic in templates is a good thing.

You can manipulate the global namespace within an Environment instance to add references to your functions. It must be done before you load any templates. For example:

from jinja2 import Environment, FileSystemLoader

def clever_function(a, b):
    return u''.join([b, a])

env = Environment(loader=FileSystemLoader('/path/to/templates'))
env.globals['clever_function'] = clever_function
Rob Cowie
  • 22,259
  • 6
  • 62
  • 56
  • 5
    I discovered this also -- you can add a module using something like this: `import utils.helpers env.globals['helpers'] = utils.helpers` – Lee May 19 '11 at 20:29
  • @Lee. Yes, you can 'inject' namespaces (modules), functions, class instances etc. It is useful, but not as flexible as other template engines like mako. Still, jinja has other good points. I'd be grateful if you accept the answer if it helped :) – Rob Cowie May 19 '11 at 22:07
  • did the trick for me while doing my app engine project ( webapp2 and jinja2 ) . thanks – Sojan Jose Apr 30 '15 at 13:59
  • @RobCowie after adding the clever_function to dictionary env.globals, how can one call the function from the template. – Jorge Vidinha Aug 31 '16 at 15:29
  • 3
    Thus, `{{ clever_function('a', 'b') }}` – Rob Cowie Sep 04 '16 at 10:50
  • This statement is contraction. If you can't run an arbitrary functions, you need way more logic to achieve what could have been abstracted in a function. – The Fool Jan 22 '22 at 11:23
55
from jinja2 import Template

def custom_function(a):
    return a.replace('o', 'ay')

template = Template('Hey, my name is {{ custom_function(first_name) }} {{ func2(last_name) }}')
template.globals['custom_function'] = custom_function

You can also give the function in the fields as per Matroskin's answer

fields = {'first_name': 'Jo', 'last_name': 'Ko', 'func2': custom_function}
print template.render(**fields)

Will output:

Hey, my name is Jay Kay

Works with Jinja2 version 2.7.3

And if you want a decorator to ease defining functions on template.globals check out Bruno Bronosky's answer

AJP
  • 26,547
  • 23
  • 88
  • 127
  • 8
    Probably because you downvoted everyone else's answers :( – Borko Kovacev Oct 20 '15 at 00:35
  • 20
    @BorkoKovacev that's not a good reason. I only down voted 2 answers; answers which were about Flask rather than Jinja2. If they want to edit their answers to be on topic and about Jinja2 I will up vote them. – AJP Oct 21 '15 at 08:43
  • works perfectly once I noticed that I had over looked the `jinga_html_template.globals['custom_function'] = custom_function` line. Makes a big difference. – Bruno Bronosky Nov 14 '17 at 05:58
  • 2
    I made a function decorator version of this answer. It currently at the bottom with 0 votes :,-( https://stackoverflow.com/a/47291097/117471 – Bruno Bronosky Nov 14 '17 at 17:01
  • 2
    @BrunoBronosky nice. Have up voted :) ... give it another decade and it might be higher than mine :P ... will never catch the flasks though ;P – AJP Jan 24 '18 at 11:38
47

I like @AJP's answer. I used it verbatim until I ended up with a lot of functions. Then I switched to a Python function decorator.

from jinja2 import Template

template = '''
Hi, my name is {{ custom_function1(first_name) }}
My name is {{ custom_function2(first_name) }}
My name is {{ custom_function3(first_name) }}
'''
jinga_html_template = Template(template)

def template_function(func):
    jinga_html_template.globals[func.__name__] = func
    return func

@template_function
def custom_function1(a):
    return a.replace('o', 'ay')

@template_function
def custom_function2(a):
    return a.replace('o', 'ill')

@template_function
def custom_function3(a):
    return 'Slim Shady'

fields = {'first_name': 'Jo'}
print(jinga_html_template.render(**fields))

Good thing functions have a __name__!

Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
  • 1
    This is insanely cool. When you annotate a function in python does it automatically pass the function name to the annotation's function? – Dr. Chocolate Feb 10 '19 at 16:50
  • @mutant_city, yep. Read that Python function decorator link. Great stuff! – Bruno Bronosky Feb 10 '19 at 19:10
  • 1
    @BrunoBronosky Excellent demonstration of a sensible and clean use for python decorators as well. Great post! – dreftymac May 04 '19 at 14:07
  • 1
    What a great implementation! – Philippe Oger Aug 30 '19 at 09:49
  • 1
    I'm not familiar with decorators being so simple! In every tutorial I've come across you need to define an inner function within an outer function, then call @ when do you define a function, which should be acted upon by the decorator. Insanity! This is much easier on the eyes. – jbuddy_13 Mar 17 '21 at 21:03
  • @jbuddy_13 Thank you for the kind words. This is an extremely simple use case of a decorator. It's also very unconventional because the func is returned unchanged. I really only did it because I like the readability and indention of this as compared to @AJP's answer [which I originally used]. When the file got long enough to scroll, I didn't want all the `jinga_html_template.globals` additions at the bottom of the file. When I moved each to after the function it looked unpythonic. My Aspergers wouldn't let it go until it was pretty. – Bruno Bronosky Mar 26 '21 at 14:54
  • How can you also apply [the `@environmentfunction` decorator or similar](https://jinja.palletsprojects.com/en/2.11.x/api/#utilities)? I bet that such decorators return the function without changing its name (only setting some attributes), so you would be able to just chain the two decorators. However can you apply an `@environmentfunction` decorator from your `@template_function` decorator? – Maëlan Apr 25 '21 at 14:32
  • 1
    @Maëlan notice that my decorator takes and returns a function as an object and the name is (irrelevant) set later. So, I could have done `custom_function1 = template_function(custom_function1)`. That suggests that your desired decorator could be used similarly for nesting like so `return environmentalfuction(func)`. However, I'm not at a computer to try this. Let me know if it works for you. – Bruno Bronosky Apr 25 '21 at 14:44
  • Ah, I am not a Python expert, but now it looks obvious! (Sorry, by “the name”, I meant “the `__name__`”.) – Maëlan Apr 25 '21 at 15:04
24

Never saw such simple way at official docs or at stack overflow, but i was amazed when found this:

# jinja2.__version__ == 2.8
from jinja2 import Template

def calcName(n, i):
    return ' '.join([n] * i)

template = Template("Hello {{ calcName('Gandalf', 2) }}")

template.render(calcName=calcName)
# or
template.render({'calcName': calcName})
Matroskin
  • 429
  • 4
  • 5
  • 1
    This answer is by far the best imho. You just pass the function to the template in exactly the same way you pass a value, after all functions are first class citizens in python :) – Mark Kortink Feb 12 '20 at 06:30
16

There's a much simpler decision.

@app.route('/x')
def x():
    return render_template('test.html', foo=y)

def y(text):
    return text

Then, in test.html:

{{ foo('hi') }}
gerardw
  • 5,822
  • 46
  • 39
8

To call a python function from Jinja2, you can use custom filters which work similarly as the globals.

It's quite simple and useful. In a file myTemplate.txt, I wrote:

{{ data | pythonFct }}

And in a python script:

import jinja2

def pythonFct(data):
    return "This is my data: {0}".format(data)
    
input="my custom filter works!"
  
loader = jinja2.FileSystemLoader(path or './')
env = jinja2.Environment(loader=loader)
env.filters['pythonFct'] = pythonFct
result = env.get_template("myTemplate.txt").render(data=input)
print(result)
Timur Bakeyev
  • 296
  • 4
  • 4
Ben9000RPM
  • 101
  • 1
  • 8
7

Use a lambda to connect the template to your main code

return render_template("clever_template", clever_function=lambda x: clever_function x)

Then you can seamlessly call the function in the template

{{clever_function(value)}}
Robert Onslow
  • 215
  • 2
  • 8
  • 1
    Clever usage of lambda functions. –  Jan 06 '16 at 11:28
  • 26
    @odiumediae: No it's not. It's completely unnecessary. Just pass the function handle itself: clever_function=clever_function – vezult Jan 24 '16 at 01:59
  • @vezult I see. How could I miss that? Thanks for clearing that up! –  Jan 24 '16 at 13:47
6

is there any way to import a whole set of python functions and have them accessible from jinja2 ?

Yes there is, In addition to the other answers above, this works for me.

Create a class and populate it with the associated methods e.g

class Test_jinja_object:

    def __init__(self):
        self.myvar = 'sample_var'

    def clever_function (self):
        return 'hello' 

Then create an instance of your class in your view function and pass the resultant object to your template as a parameter for the render_template function

my_obj = Test_jinja_object()

Now in your template, you can call the class methods in jinja like so

{{ my_obj.clever_function () }}
Kudehinbu Oluwaponle
  • 1,045
  • 11
  • 11
  • Equivalent and slightly simpler way: put all functions for templates in a module, import that module and add it as template global. A module is an object that contains functions :) (but not methods — no need for self param and no class reqiured!) – merwok Nov 07 '19 at 19:12
  • @ÉricAraujo What if I only need the set of functions in one or two templates and not all of them. Also, what if I need different sets of python functions in different jinjas templates? Would you still consider it effective to import all of them as template globals rather than put them as methods in a class and only pass the classses with the methods you need. – Kudehinbu Oluwaponle Nov 08 '19 at 21:49
  • To use only in specific templates, I would add the functions (or a module containing functions) only to the template context dict that’s returrned by the views that use those templates. – merwok Nov 11 '19 at 18:52
5

To import all the builtin functions you can use:

app.jinja_env.globals.update(__builtins__)

Add .__dict__ after __builtins__ if this doesn't work.

Based on John32323's answer.

Solomon Ucko
  • 5,724
  • 3
  • 24
  • 45
3

@John32323 's answer is a very clean solution.

Here is the same one, but save into a seperate file, maybe more cleaner.

Create helper file

app\helper.py

from app import app

def clever_function_1():
    return u'HELLO'

def clever_function_2(a, b):
    return a + b



app.jinja_env.globals.update(
    clever_function_1=clever_function_1,
    clever_function_2=clever_function_2,
)

Import from app

app.py

from app import routes
from app import helper   # add this one

Use like this

app\templates\some.html


{{ clever_function_1() }}
{{ clever_function_2(a, b) }}

floox
  • 351
  • 4
  • 21
  • The elegance of this solution is astounding. Good job. I only define the functions in the `app.py` file and then there are no ugly imports. – étale-cohomology Feb 09 '22 at 18:58
3

For those using FastApi, put this in your __init__.py:

from fastapi.templating import Jinja2Templates
templates = Jinja2Templates(directory="templates")

def clever_function():
    return u'HELLO'

templates.env.globals.update(clever_function=clever_function)

and in your template call it with {{ clever_function() }}

liber
  • 166
  • 1
  • 7
2

If you are doing it with Django, you can just pass the function with the context:

context = {
    'title':'My title',
    'str': str,
}
...
return render(request, 'index.html', context)

Now you will be able to use the str function in jinja2 template

Jahid
  • 21,542
  • 10
  • 90
  • 108
0
Creating a global function without passing to the template

@app.template_global('double')
def double(n):
    return 2 * n

Jinja Usage`enter code here`

{{double(77)}}

Or
Creating a filter in jinja.


@app.template_filter('geo_stuff')
def hellome(a,b='dadadd'):
    d=a+'it is jerry'
    return d
jinja use
{{'foo'|geo_stuff}}
George
  • 71
  • 6