Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blocks defined in partial included in base template, fails to be overriden in extended templates #272

Closed
cipriancraciun opened this issue Nov 25, 2024 · 12 comments · Fixed by #279
Assignees

Comments

@cipriancraciun
Copy link
Contributor

Given the following example:

## base.txt

{% block block_in_base %}
block_in_base: from base!
{% endblock %}

{% include "partial.txt" %}
## partial.txt

{% block block_in_partial %}
block_in_partial: from partial!
{% endblock %}
## extended.txt

{% extends "base.txt" %}

{% block block_in_base %}
block_in_base: from extended!
{% endblock %}

{% block block_in_partial %}
block_in_partial: from extended!
{% endblock %}

Namely:

  • we have a base and extended template,
  • the base template indirectly defines a block block_in_partial, via the {% include "partial.txt" %},
  • extended although overrides the contents of the block_in_partial,
  • when extended is rendered, the overriden contents fails to output;

Observed outcome of rendering extended:

block_in_base: from extended!
block_in_partial: from partial!

Expected outcome of rendening extended:

block_in_base: from extended!
block_in_partial: from extended!

For some reason the parser / generator fails to register the block_in_partial (included in base) as an extendable block.

Moreover, the current Rinja documentation supports the case, by implying that {% include ... %} statements should behave as if they wouldn't be there, and the contents of the included template would be spliced at the place of inclusion:

The include statement lets you split large or repetitive blocks into separate template files. Included templates get full access to the context in which they're used, including local variables like those from loops:


What are potential use-cases:

  • sometimes, especially for HTML, the base template can become too large, and the user wants to split it into multiple includes for easier development; moreover, the developer needs a small part of these included files to be controlled by the extending template (like for example the title, some meta in head, footers, headers, etc.);
  • or perhaps there are multiple base templates (for example one for mobile, one for HTML-only, one for modern browses, etc.) which reuse some common sections that are extracted in partials; but the partials need some placeholders to include content controlled by extending templates;
@cipriancraciun
Copy link
Contributor Author

After running git bisect, it appeares that the regression was introduced in Askama at the following commit (before Rinja v0.2 tag):
https://github.com/djc/askama/commit/5cad82f38e800a42717284f20e7e0923add1e32f

@GuillaumeGomez
Copy link
Contributor

The official jinja implementation returns for what you provided:

block_in_base: from base!
block_in_partial: from partial!

I ran it with this (python) code:

import jinja2

templateLoader = jinja2.FileSystemLoader(searchpath="./templates")
templateEnv = jinja2.Environment(loader=templateLoader)
template = templateEnv.get_template("base.txt")
outputText = template.render()

print(outputText)

So rinja is wrong, but in a different way than described. ;)

Kijewski added a commit to Kijewski/rinja that referenced this issue Nov 26, 2024
@Kijewski
Copy link
Collaborator

I can confirm @cipriancraciun's finding. Please see #278.

@GuillaumeGomez GuillaumeGomez self-assigned this Nov 27, 2024
@GuillaumeGomez
Copy link
Contributor

Assigning myself for the fix as I have a good idea on how to fix it.

@GuillaumeGomez
Copy link
Contributor

Wait no. Don't know how but I failed to correctly set the python test... So no, the current output of rinja is the expected one. It should be:

block_in_base: from extended!
block_in_partial: from partial!

The reason is because partial.txt is included before extended.txt, meaning its block is "run" before the one in extended.txt. So it is actually the correct output as I confirmed with the official jinja. Gonna update the test.

@GuillaumeGomez
Copy link
Contributor

I opened #279 which adds some extra explanations about this.

@cipriancraciun
Copy link
Contributor Author

The reason is because partial.txt is included before extended.txt, meaning its block is "run" before the one in extended.txt. So it is actually the correct output as I confirmed with the official jinja. Gonna update the test.

Give that the conclusion of this issue is "work as expected", then I think I don't understand the exact semantics of {% include ... %}.

For me, the following two snippets should be identical with regard to their output:

## base.txt -- that includes partial

{% block block_in_base %}
block_in_base: from base!
{% endblock %}

{% include "partial.txt" %}
## base.txt -- that manually splices the contents of partial

{% block block_in_base %}
block_in_base: from base!
{% endblock %}

{% block block_in_partial %}
block_in_partial: from partial!
{% endblock %}

Thus, in both cases, when the extended extends both blocks, the output should in both cases write block_in_...: from extended.

Else, if one can't override blocks defined in partials, then why allow them to be defined? (From my understanding of Jinja / Rinja / Askama, the main and only usage of blocks is to aid overriding in extended templates.)


Setting this asside, is there another way to "include verbatim" a template source in another template?

@GuillaumeGomez
Copy link
Contributor

The important thing here is which block is encountered first. You extend, and this is template, there is an include where the block is defined, making this block a bit useless imo. Would be nice to have a warning though.

@cipriancraciun
Copy link
Contributor Author

cipriancraciun commented Dec 1, 2024

The important thing here is which block is encountered first. You extend, and this is template, [...]

Could you expand a bit on this please? (I didn't understand what you were trying to convey here.)

Based on my interpretation of the above, I've tried to move the {% extends "base.txt" %} from the beginning of the extended.txt file to the end of the file, but the result was the same (i.e. it prints block_in_partial: from partial!).


What is even weirder, is that if I run your Python Jinja2 snippet, but running the extended.txt template with the {% extends "base.txt" %} at the end, it yields the following:

block_in_base: from extended!
block_in_partial: from extended!
block_in_base: from extended!
block_in_partial: from partial!

Now, I could understand why block_in_{base,partial} are printed twice, but I don't understand why block_in_partial changes interpretation based on when extends is called.

(Or even why extends is allowed after defining blocks.)

@cipriancraciun
Copy link
Contributor Author

Digging a bit into the Jinja2 / Rinja compatibility, I think -- if the target is Jinja2 compatibility -- that then the current Rinja code is indeed "working as expected", because the Jinja2 documentation states the following with regard to includes:

https://jinja.palletsprojects.com/en/stable/templates/#include
The included template can extend another template and override blocks in that template. However, the current template cannot override any blocks that the included template outputs.

Thus in my example, because the partial defines a block, that would be scoped only to the partial itself and the base template it might happen to extend.


However, then my other question stands: how does one actually include verbatim (as in C's #include "some-file.h") another file in the current template?

@GuillaumeGomez
Copy link
Contributor

Use include and not extends I suppose? And avoid including items that use blocks.

Also don't forget that you can actually prevent to have jinja code generated with conditions. Like you can set a variables or a field and then check if this variable/field exist as condition for example, like:

{% if field is defined %}
  {% block block_base %}...{% endblock %}
{% endif %}

Like this, the block will not be defined in the other templates.

@cipriancraciun
Copy link
Contributor Author

Use include and not extends I suppose? And avoid including items that use blocks.

That's the issue, I can't use include because it can't define blocks that can be used outside the included template (i.e. overridable by extends templates of the base template that happens to use include).

To clarify my use-case (which I don't believe is that unique):

  • I want to define a base page template that provides a framework for other pages;
  • thus I need to use extends from the concrete pages,
  • and I need to use blocks (in extended templates) to define the actual content to be slotted into the base page;
  • however, as is nowadays with heavy HTML pages, I don't want to have one big base template HTML, but I would like to split major parts into different includes / partials, which do need to define blocks, but all of these should be scoped into the base template (that uses include), not scoped to the included template;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants