Code

Opened 6 years ago

Closed 5 years ago

Last modified 4 years ago

#7324 closed (invalid)

{{ block.super }} doesn't work with nested {% block %} statements

Reported by: jeverling Owned by: nobody
Component: Template system Version: master
Severity: Keywords: block.super template
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: yes
Needs tests: no Patch needs improvement: no
Easy pickings: UI/UX:

Description

Hi everybody!

For me, {{block.super}} does not work in nested {% block %} tags.
This works:

{% block menu %}

{{ block.super }}

{% endblock menu %}

This doesn't:

{% block content %}
{% block menu %}
{{ block.super }}
{% endblock menu %}
{% endblock content %}

I hope it's not due to something wrong only for my setup, but I think it may be a bug.

Thanks and best regards!

Attachments (2)

bug7324.zip (4.8 KB) - added by kmtracey 5 years ago.
bug7324-better-testcase.zip (5.8 KB) - added by bradleyw 5 years ago.
Better test case than the useless one from before

Download all attachments as: .zip

Change History (9)

comment:1 Changed 6 years ago by MihaiD

  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset

I have the same problem...

comment:2 Changed 6 years ago by ericholscher

  • Resolution set to invalid
  • Status changed from new to closed

What do you mean by "doesn't work"? Are you expecting this to get the content of the content block, or the previous template's menu block. Also, what happens when it isn't working. Default content, no content? errors?

comment:3 Changed 5 years ago by bradleyw

  • Resolution invalid deleted
  • Status changed from closed to reopened

I also have this problem. Linked is an OS X archive of a project which illustrates the problem with the latest version (r9803) of Django.

Basically, in templates/test.html , I expect the block {% block submain %}{{ block.super }}{% endblock %} to contain "This should be inherited". It returns nothing. This is required for more complex template examples where multi-template inheritance/modification of content is required.

Entire linked project is here: https://dl.getdropbox.com/u/19680/bug7324.zip (I couldn't figure out how to attach the ZIP file to the bug).

comment:4 Changed 5 years ago by kmtracey

  • Resolution set to invalid
  • Status changed from reopened to closed

You might want to try posting a question about what you are trying to do and what is not working on django-users, not here. Based on your sample project I can't tell if you have over-simplified to the point where things are broken or if you have a fundamental misunderstanding of how template inheritance works.

{{ block.super }} provides content for the block from the parent template, which implies the current template is a child template, which implies the first thing in the template containing {{ block.super }} is an {% extends %} tag. Yet you do not have an extends tag in either of your template files. You have also given them both the same name, which makes me think you think that their parent-child relationship will be intuited as a result of the order in which they are found based on how you have listed the containing directories in the settings.py TEMPLATE_DIRS. Nothing like that is done to figure out template inheritance; the parent-child relationship is determined by the use of {% extends %} tags.

So what you describe seeing, that is no content from the templates/test.html {% submain %} block being "inherited" when templates-top/test.html is rendered (since that is the one that will be found, as templates-top is first in the TEMPLATE_DIRS listing), is correct. templates/test.html is not a child template and therefore has no parent block to display for the sumbain block's content.

Changed 5 years ago by kmtracey

comment:5 Changed 5 years ago by bradleyw

Hi Karen,

Indeed I had over-simplified the project, and it was late at night so I forgot to include some stuff. My apologies for wasting your time.

Regardless, I've attached a better patch in the form of bug7324-better-testcase.zip . This should better illustrate what I'm trying to do. There are comments in the templates which indicate what I expect in each case.

In templates-top/test.html, I have a block main with a nested block submain. This template extends templates/new/test.html. If I include a block.super in block main of the child template, the block.super from the nested block submain is included twice. If I remove the "my new sub-content" string from the child template, and remove the parent block block.super, I get none of the inherited content at all. So it seems like the parent block.super is required for the child block.super to have any content, and the child's inherited content will be rendered twice.

If you still disagree that it's a bug then fine, I won't re-open it until you've looked over my new test case, but I feel my improved test case (sorry about the first one) proves this is a bug.

Changed 5 years ago by bradleyw

Better test case than the useless one from before

comment:6 Changed 5 years ago by kmtracey

OK, just for clarity and so people don't have to unzip the project. The parent template new/test.html contains:

{% block main %}
    New content
    {% block submain %}This should be inherited{% endblock %}
{% endblock %}

The child template, templates-top/test.html contains:

{% extends "new/test.html" %}
{% comment %}Gets around infinite recursion bug by namespacing with a directory{% endcomment %}
{% block main %}
    Top-level main block
    {% comment %}remove this block.super and it all disappears. But I only want the block.super from inside "submain"{% endcomment %}
    {{ block.super }}
    {% block submain %}
        my new sub-content
        {{ block.super }}
        {% comment %}I expect this block.super to contain the content of "submain" from new/test.html, because I'm extending it. It is empty, UNLESS "main" has a block.super call, in which case it's echoed TWICE{% endcomment %}
    {% endblock %}
    
{% endblock %}

Consider what the child template is doing. By including a {% block main %} it is saying "I want to override the contents of the block named "main" in the parent template with this content I am specifying here." When it does that, block "submain" ceases to exist, as the "submain" block was declared within block main in the parent. If you discard the contents of the parent template's main block in favor of what is in the child template, then you lose the "submain" block entirely. You can get it back by including block.super in the child's "main" override, but then anything else you have in the child's "main" is simply appended to the parent's "main" content, which doesn't seem to be what you want.

I said this earlier and I'll say it again: django-users would be a better place to outline what you are looking to achieve and get ideas on how to accomplish it. There is a much larger audience there than in the ticket system. So far as I can see there is not a bug here, it's just a case of the template inheritance mechanism not quite working the way you'd like it to.

comment:7 Changed 4 years ago by lanyjie

  • Needs documentation set

There is lack of explicit specification on what should happen in the template system in some situations. The real issue here is this: when you have multiple definitions of the same block, what should happen? In the documentation, it simply says you should avoid such a situation in the same template, but when it happens, we should give a rule to resolve it. We first study the case when the definition of the same block is repeated in the child template (inheritance). The parent template comes first, and then the child template. And the implicit rule seems to be this: the first valid definition in the parent is the place holder (that is, whatever content is determined for this block shall be included in that place). The later definition will override the content, but not the location. So, this observation gives rise to a natural extension of this rule to resolve the issue of multiple definitions in a single file: the later definitions always override the content of the former ones, but only the first definition (note, this means no valid prior definitions exists) determines the place to include the final content. With this in mind, let's take a look at the test case above:

{% extends "new/test.html" %}
{% comment %}Gets around infinite recursion bug by namespacing with a directory{% endcomment %}
{% block main %}
    Top-level main block
    {% comment %}remove this block.super and it all disappears. But I only want the block.super from inside "submain"{% endcomment %}
    {{ block.super }}
    {% block submain %}
        my new sub-content
        {{ block.super }}
        {% comment %}I expect this block.super to contain the content of "submain" from new/test.html, because I'm extending it. It is empty, UNLESS "main" has a block.super call, in which case it's echoed TWICE{% endcomment %}
    {% endblock %}
    
{% endblock %}

First note that the {{ block.super }} will include a definition of submain block, and since we are redefining the block "main" here, so the previous definition of block "submain" loses its meaning as a place holder (it is location-invalid as its definition context is invalidated), but its content is still meaningful. In this quite interesting case, the newly included definition becomes the new place holder definition. Then the new definition of "submain" is the second one in this same file, and according to our extended rule, it will only override the content of the first definition, but will not change the location of the block, nor shall it repeat the content at its own location. Then the logical output of this template naturally follows. One more subtle issue here: what should the {{ block.super }} in the "submain" block be? Is it the content from the outer {{ block.super }}, or is it the one from the parent (note that the content there remains meaningful)? Well, it is probably not important here, as they are identical, but this is still an ambiguity that might cause trouble in other cases. One simple rule is: block.super always gives the content of the definition immediately precedes the current one, as the current one is overriding the former one. This is the rule I prefer, but people may go with the other way: that block.super always use the contents from the parent template. A problem with the latter approach is that we need new ways to access the contents of the same block defined earlier. Both ways yields the same result if no multiple definitions occur in a single template. I think multiple definitions in the same template might be useful as a content-holder, so that its contents may be used later through the block.super call as defined in the preferred way. It is then interesting to consider yet a slightly different situation:

{% extends "new/test.html" %}
{% block main %}
    Top-level main block
    {% block submain %}
        my new sub-content
        {{ block.super }}
{% comment %} request the content of a previous definition (in the parent template), 
it is OK as the former definition can still be used as a content-holder. this one
is the place-holder, as the former becomes place-invalid. {% endcomment %}

    {% endblock %}

    {{ block.super }}
{% comment %} request the content of a previous definition (in the parent template), 
which includes another definition of the "submain" block, which shall only
override the contents of the former definition. The net result is that the new content
of "submain" defined above is lost, replaced by the old content! {% endcomment %}

{% endblock %}

In summary, a block definition has two properties: place and content. We should consider them
in terms of place-holders and content-holders, and they could be place-valid, content-valid,
place-invalid and/or content-invalid. The first definition will be place-holders unless it
becomes place-invalid when its context is redefined. A later definition will make the former
ones content-invalid. But it only becomes place-holders if all former ones become place-invalid.

Please also refer to ticket #13399 for more discussions.

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.