Jinja2 Components offers a streamlined way to build and reuse your Jinja2 templates from code, inspired by django-components
and jinja2-simple-tags
.
- Modular Design. Break down your templates into reusable components for cleaner code and easier maintenance.
- Simple Syntax. Use Jinja2 syntax to only use your components, lower you templates depth.
- Extensible Framework. Customize your components using Python code.
-
Install the library.
pip install jinja2-components
-
Add extension to the Jinja2 environment.
from jinja2 import Environment from jinja2_components import ComponentsExtension env = Environment(extensions=[ComponentsExtension])
-
Define a component and it's template.
from jinja2 import Template from jinja2_components import Component, register @register(name="hello") class Hello(Component): template = Template("hello")
-
Use component inside template as a tag.
template = env.from_string("{% hello %} world") print(template.render()) # hello world
@register(name="hello")
class Hello(Component):
template = Template("hello")
template = env.from_string("{% hello %} world")
print(template.render())
# hello world
@register(name="button")
class Button(Component):
template = Template("<button>{{ body }}</button>")
block = True
@classmethod
def get_context(cls, caller):
return {"body": str(caller())}
template = env.from_string("{% button %}Hello!{% endbutton %}")
print(template.render())
# <button>Hello!</button>
@register(name="base64")
class Base64(Component):
template = Template("{{ result }}")
block = True
@classmethod
def get_context(cls, caller):
content = str(caller()).encode()
return {"result": b64encode(content).decode()}
template = env.from_string("{% base64 %}hello world{% endbase64 %}")
print(template.render())
# aGVsbG8gd29ybGQ=
template = env.from_string("""\
{% base64 as hw_base64 %}hello world{% endbase64 %}\
Base64 of 'hello world': {{ hw_base64 }}
""")
print(template.render())
# Base64 of 'hello world': aGVsbG8gd29ybGQ=
If the component's template was set directly, then this template also requires extensions. You can pass extensions where required or as a workaround use env.from_string
.
But it possible to use template_str
and template_name
class variables for later instantiation from environment.
@register(name="button")
class Button(Component):
template_str = "<button>{{ body }}</button>"
@register(name="menu")
class Menu(Component):
template_str = """\
<div class="menu">\
{% for btn in buttons %}
{% button body=btn %}\
{% endfor %}
</button>
"""
template = env.from_string("{% menu buttons=[1, 2, 3] %}")
print(template.render())
# <div class="menu">
# <button>1</button>
# <button>2</button>
# <button>3</button>
# </button>
The idea behind this feature is to pass arguments and get rendered component inside the code.
@register(name="button")
class Button(Component):
template_str = "<button>{{ body }}</button>"
@register(name="menu")
class Menu(Component):
template_str = """\
<div class="menu">\
{% for btn in buttons %}
{% button body=btn %}\
{% endfor %}
</button>
"""
template = env.from_string("{% menu buttons=[1, 2, 3] %}")
print(template.render())
# <div class="menu">
# <button>1</button>
# <button>2</button>
# <button>3</button>
# </button>
print(Menu(env, buttons=[1, 2, 3]))
# <div class="menu">
# <button>1</button>
# <button>2</button>
# <button>3</button>
# </button>
Also it is possible to pass initialized components (because the are strings).
@register(name="menu")
class Menu(Component):
template_str = """\
<div class="menu">\
{% for btn in buttons %}
{{ btn }}\
{% endfor %}
</button>
"""
rendered = Menu(env, buttons=[Button(env, body=1), Button(env, body=2), Button(env, body=3)])
print(rendered)
# <div class="menu">
# <button>1</button>
# <button>2</button>
# <button>3</button>
# </button>