sponsfreixes / jinja2-fragments

Render Jinja2 template block as HTML page fragments on Python web frameworks.
MIT License
228 stars 12 forks source link

Any way to work with `extends` / `super()`? #7

Open neilmcguigan opened 1 year ago

neilmcguigan commented 1 year ago

parent.html:

...
<title>{%block title%} - SiteName{%endblock%}</title>
...

child.html:

{%extends "parent.html"%}
{%block title%}Child{{super()}}{%endblock%}  <!-- full title is "Child - SiteName" -->

app.py:


@app.get("/child")
def get_child():
  return render_template("child.html") # WORKS
  # return render_block("child.html", "title") # FAILS

thanks!

sponsfreixes commented 1 year ago

After giving it some thought, I am not confident (for now) that this is a behavior the library should implement.

On your example (and the other examples of extends + super() on Jinja's documentation), this pattern is used to manipulate the <head>. This doesn't seem to align well with the concept of a template fragments: having a component inside a template that you want to render independently in some cases.

I can't see when you would like to render only the head.

It is true that there might be use cases where extends can be used to render the body. If super() is not needed, explicitly rendering the block on the parent instead of on the child should work. I think this helps making it clear from where the fragment is actually coming from. Example:

parent.html.jinja2:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>This is the title</title>
</head>
<body>
    {% block header % }<h1>This is a generic header</h1>{% block header %}
    {% block content %}{% endblock %}
</body>
</html>

child_with_specific_header.html.jinja2:

{%extends "parent.html.jinja2"%}
{% block header % }<h1>This is specific header</h1>{% block header %}
{% block content %}This is my content{% endblock %}

child_with_generic_header.html.jinja2:

{%extends "parent.html.jinja2"%}
{% block content %}This is my content{% endblock %}

app.py:

# All these work
render_template("child_with_specific_header.html.jinja2")
render_template("child_with_generic_header.html.jinja2")
render_block("child_with_specific_header.html.jinja2", "header")
render_block("parent.html.jinja2", "header")

# This uses the fallback mechanism to make explicit from where to get the block:
try:
    return render_block("child_with_generic_header.html.jinja2", "header")
except KeyError:
    return render_block("parent.html.jinja2", "header")

That said, I am open to discussion and be convinced of otherwise, so I am not closing this issue to enable that.

neilmcguigan commented 1 year ago

thanks for the thoughtful response

my use-case is as such:

  1. different pages have different titles, but the site name should be included in all titles
  2. using HTMX to navigate between different pages, but only doing partial rendering (of the "main" block)
  3. want to use HTMX OOB to return some other parts to update the UI, such as the page title, and which part of the navigation bar are "active"
  4. can return <title>foo</title> OOB in HTMX and it works
  5. but for this to occur, need to get the entire page title, including parent to return to HTMX

probably a minor usecase, and a major fix/kludge, so no worries if not. Thought you might have a jinja trick up your sleeve that I don't :)

sponsfreixes commented 1 year ago

That makes me think if, for OOB cases, it would be interesting to have something to render blocks from different templates. Something like render_blocks([("template1.html", "block_foo", {"x"=2, "y"="hello"}), ("template2.html", "block_bar"), ...]).

Then, on your case, you could grab the title block from parent.html (passing the child page title as a variable, so you get your customized title), and whatever else you need to add to the OOB response.