simonw / public-notes

Public notes as issue threads
25 stars 0 forks source link

Full read-through of the Jinja documentation #4

Closed simonw closed 1 year ago

simonw commented 1 year ago

I use Jinja enough that I should really do a full read-through of the docs to see what I've missed.

https://jinja.palletsprojects.com/en/3.1.x/

simonw commented 1 year ago

The call mechanism is interesting:

{% macro render_dialog(title, class='dialog') -%}
    <div class="{{ class }}">
        <h2>{{ title }}</h2>
        <div class="contents">
            {{ caller() }}
        </div>
    </div>
{%- endmacro %}

{% call render_dialog('Hello World') %}
    This is a simple dialog rendered by using a macro and
    a call block.
{% endcall %}

{% macro dump_users(users) -%}
    <ul>
    {%- for user in users %}
        <li><p>{{ user.username|e }}</p>{{ caller(user) }}</li>
    {%- endfor %}
    </ul>
{%- endmacro %}

{% call(user) dump_users(list_of_user) %}
    <dl>
        <dt>Realname</dt>
        <dd>{{ user.realname|e }}</dd>
        <dt>Description</dt>
        <dd>{{ user.description }}</dd>
    </dl>
{% endcall %}
simonw commented 1 year ago

Submitted a PR:

simonw commented 1 year ago

https://jinja.palletsprojects.com/en/3.1.x/templates/#assignments

Assignments at top level (outside of blocks, macros or loops) are exported from the template like top level macros and can be imported by other templates.

simonw commented 1 year ago

The namespace mechanism is interesting - for working around the fact that assignments can't be seen from outside of their block:

{% set ns = namespace(found=false) %}
{% for item in items %}
    {% if item.check_something() %}
        {% set ns.found = true %}
    {% endif %}
    * {{ item.title }}
{% endfor %}
Found item having something: {{ ns.found }}
simonw commented 1 year ago

I didn't know about block assignments!

{% set navigation %}
    <li><a href="/">Index</a>
    <li><a href="/downloads">Downloads</a>
{% endset %}

Also support filters:

{% set reply | wordwrap %}
    You wrote:
    {{ message }}
{% endset %}
simonw commented 1 year ago
{% include ['special_sidebar.html', 'sidebar.html'] ignore missing %}

That will try to include the first, then the second template and fail silently if none are found.

simonw commented 1 year ago

Import is interesting for macros: https://jinja.palletsprojects.com/en/3.1.x/templates/#import

{% import 'forms.html' as forms %}
<dl>
    <dt>Username</dt>
    <dd>{{ forms.input('username') }}</dd>
    <dt>Password</dt>
    <dd>{{ forms.input('password', type='password') }}</dd>
</dl>
<p>{{ forms.textarea('comment') }}</p>

OR

{% from 'forms.html' import input as input_field, textarea %}

Macros and variables starting with one or more underscores are private and cannot be imported.

simonw commented 1 year ago

Lists, tuples and dictionaries are valid Jinja literals.

simonw commented 1 year ago

Bit surprising (an old bug I think):

Unlike Python, chained pow is evaluated left to right. {{ 3**3**3 }} is evaluated as (3**3)**3 in Jinja, but would be evaluated as 3**(3**3) in Python. Use parentheses in Jinja to be explicit about what order you want.

simonw commented 1 year ago

~ "Converts all operands into strings and concatenates them."

{{ "Hello " ~ name ~ "!" }}

simonw commented 1 year ago

If expressions are neat: https://jinja.palletsprojects.com/en/3.1.x/templates/#if-expression

{% extends layout_template if layout_template is defined else 'default.html' %}
{{ "[{}]".format(page.title) if page.title }}
simonw commented 1 year ago

Lots of built-in filters: https://jinja.palletsprojects.com/en/3.1.x/templates/#list-of-builtin-filters

image
simonw commented 1 year ago

I like batch:

<table>
{%- for row in items|batch(3, '&nbsp;') %}
  <tr>
  {%- for column in row %}
    <td>{{ column }}</td>
  {%- endfor %}
  </tr>
{%- endfor %}
</table>

That second optional argument is used to fill in missing items.

simonw commented 1 year ago

There's a |filesizeformat filter!

I didn't need https://github.com/simonw/datasette/blob/5aa359b86907d11b3ee601510775a85a90224da8/datasette/utils/__init__.py#L849-L858 after all.

simonw commented 1 year ago
simonw commented 1 year ago
Users on this page: {{ users|map(attribute='username')|join(', ') }}
Users on this page: {{ titles|map('lower')|join(', ') }}
simonw commented 1 year ago

|pprint looks useful.

These test ones are cute:

{{ numbers|reject("odd") }}
{{ numbers|select("divisibleby", 3) }}
{{ numbers|select("lessthan", 42) }}
simonw commented 1 year ago

I wonder how good |striptags is?

Looks like it uses markupsafe under the hood, implemented here: https://github.com/pallets/markupsafe/blob/33307792f69e9e6bd9589919620300985955800c/src/markupsafe/__init__.py#L155-L166

Which uses these two regexes:

_strip_comments_re = re.compile(r"<!--.*?-->", re.DOTALL)
_strip_tags_re = re.compile(r"<.*?>", re.DOTALL)
simonw commented 1 year ago

The |tojson filter says: https://jinja.palletsprojects.com/en/3.1.x/templates/#jinja-filters.tojson

The returned string is safe to render in HTML documents and <script> tags. The exception is in HTML attributes that are double quoted; either use single quotes or the |forceescape filter.

Implementation: https://github.com/pallets/jinja/blob/52843b5cbf635b37a82ac0b6c901921a8ee076ff/src/jinja2/utils.py#L657-L663

    return markupsafe.Markup(
        dumps(obj, **kwargs)
        .replace("<", "\\u003c")
        .replace(">", "\\u003e")
        .replace("&", "\\u0026")
        .replace("'", "\\u0027")
    )
simonw commented 1 year ago

|urlencode is handy:

Basic wrapper around urllib.parse.quote() when given a string, or urllib.parse.urlencode() for a dict or iterable.

simonw commented 1 year ago

You can test for if a filter is available or not:

{% if 'markdown' is filter %}
    {{ value | markdown }}
{% else %}
    {{ value }}
{% endif %}
simonw commented 1 year ago

It has lorem ipsum!

jinja-globals.lipsum(n=5, html=True, min=20, max=100)

Generates some lorem ipsum for the template. By default, five paragraphs of HTML are generated with each paragraph between 20 and 100 words. If html is False, regular text is returned. This is useful to generate simple contents for layout testing.

simonw commented 1 year ago

https://jinja.palletsprojects.com/en/3.1.x/templates/#i18n looks very handy:

{% trans %}Hello, {{ user }}!{% endtrans %}
{% trans user=user.username %}Hello, {{ user }}!{% endtrans %}

{% trans book_title=book.title, author=author.name %}
This is {{ book_title }} by {{ author }}
{% endtrans %}

{% trans count=list|length %}
There is {{ count }} {{ name }} object.
{% pluralize %}
There are {{ count }} {{ name }} objects.
{% endtrans %}

When translating blocks of text, whitespace and linebreaks result in hard to read and error-prone translation strings. To avoid this, a trans block can be marked as trimmed, which will replace all linebreaks and the whitespace surrounding them with a single space and remove leading and trailing whitespace.

{% trans trimmed book_title=book.title %}
    This is {{ book_title }}.
    You should read it!
{% endtrans %}

{{ _("Hello, World!") }}

{{ _("Hello, %(user)s!")|format(user=user.username) }}
simonw commented 1 year ago

Interesting extension: https://jinja.palletsprojects.com/en/3.1.x/templates/#expression-statement

{% do navigation.append('a string') %}
simonw commented 1 year ago
<pre>{% debug %}</pre>

Works if you turn on the extension: https://jinja.palletsprojects.com/en/3.1.x/templates/#debug-statement

simonw commented 1 year ago

This used to be an extension but is now built in:

{% with %}
    {% set foo = 42 %}
    {{ foo }}           foo is 42 here
{% endwith %}
foo is not visible here any longer

This too:

{% autoescape true %}
    Autoescaping is active within this block
{% endautoescape %}

{% autoescape false %}
    Autoescaping is inactive within this block
{% endautoescape %}
simonw commented 1 year ago

Extensions: https://jinja.palletsprojects.com/en/3.1.x/extensions/

Mainly i18n - the others are pretty tiny. You can write your own.

simonw commented 1 year ago

https://jinja.palletsprojects.com/en/3.1.x/extensions/#module-jinja2.ext

By writing extensions you can add custom tags to Jinja. This is a non-trivial task and usually not needed as the default tags and expressions cover all common use cases. The i18n extension is a good example of why extensions are useful. Another one would be fragment caching.

Extensions are pretty low-level AST and parser code.

simonw commented 1 year ago

https://jinja.palletsprojects.com/en/3.1.x/integration/#babel describes Babel integration, for extracting translatable strings.

simonw commented 1 year ago

https://jinja.palletsprojects.com/en/3.1.x/switching/ has some short notes on Django template comparisons.

simonw commented 1 year ago

https://jinja.palletsprojects.com/en/3.1.x/faq/ - only 3 FAQs there.

And I'm done!