jjt
jjt

Reputation: 1273

Test for existence of template block in a template

I have a structure where there's normally a page heading in (% block heading %} in my base template:

base.html

<h2>{% block heading %}{% endblock %}</h2>

Most of the time, I'll pass in a heading like this through templates that extend base:

extends-base.html

{% block heading %}Super Cool Page!{% endblock %}

However, for a special page, I don't want to have a page heading:

extends-base-special.html

{% block heading %}{% endblock %}

Ideally, this should exclude the <h2> tags. Now, I could just make the all of the extending templates include the <h2> tags, but that violates DRY, as every page should have the same element for the page-level heading. What I'd prefer to do is this (which doesn't appear to work):

base-prefered.html

{% if heading %}
<h2>{% block heading %}{% endblock %}</h2>
{% endif %}

Is this doable somehow on the template level, or do I have to tuck into views for this?

Upvotes: 25

Views: 8891

Answers (4)

TokeyChan
TokeyChan

Reputation: 11

I know I am late to the party but for anyone who (like me) is still looking for a solution for that problem, here is mine:

I created a custom TemplateBackend which puts the name of every block in the given template in a context variable called "FILLED_BLOCKS".

Here is the code: (note: it probably won't work if you extend your templates multiple times but I guess you can easily fix this by adding a little recursion or something. It just wasn't something I needed)

from django.template.backends.django import DjangoTemplates, Template, reraise, TemplateDoesNotExist
from django.template.context import make_context
from django.template.engine import Engine
from django.template.loader_tags import ExtendsNode

class CustomBackend(DjangoTemplates):
    def from_string(self, template_code):
        return CustomTemplate(self.engine.from_string(template_code))

    def get_template(self, template_name):
        try:
            return CustomTemplate(self.engine.get_template(template_name), self)
        except TemplateDoesNotExist as exc:
            reraise(exc, self)


class CustomTemplate(Template):
    def get_filled_blocks(self):
        nodes = self.template.nodelist.get_nodes_by_type(ExtendsNode)
        if len(nodes) == 0:
            return []
        blocks = []
        for node in nodes:
            for block in node.blocks.values():
                if len(block.nodelist) != 0:
                    blocks.append(block.name)
        return blocks
    
    def render(self, context=None, request=None):
            context['FILLED_BLOCKS'] = self.get_filled_blocks()
            context = make_context(context, request, autoescape=self.backend.engine.autoescape)
            try:
                return self.template.render(context)
            except TemplateDoesNotExist as exc:
                reraise(exc, self.backend)

Just put the path to this file in your settings.py at TEMPLATES['BACKEND']

Use it in your Template like this:

{% if 'heading' in FILLED_BLOCKS %}
   <h2>{% block heading %}{% endblock %}</h2>
{% endif %}

Upvotes: 1

Afaik there is no really good and simple solution yet. Besides the option offered by czarchaic you could also write your own template tag as described in Jarret Hardie's answer to "How to test for use of a django template block?". However, imho the best and most elegant way will be the {% capture as ... %} template tag - unfortunately it is not implemented yet: https://code.djangoproject.com/ticket/6378

Upvotes: 1

czarchaic
czarchaic

Reputation: 6318

You could double wrap it

{% block noheader %}
  <h2>{% block header %}Super Cool Page!{% endblock header %}</h2>
{% endblock noheader %}

On pages without header

{% block noheader %}{% endblock %}

Upvotes: 39

gruszczy
gruszczy

Reputation: 42188

Do it like this:

  • base.html - whole structure <h2>{% block heading %}{% endblock %}</h2>
  • base-without-heading.html - extend base with this {% block heading %}{% endblock %}

And then either extend the first or the second template. I believe this should be the easiest way.

And by the way. By writing:

{% if heading %}

you are actuallly asking for boolean value of element in the context with name 'heading'. Elements of django markup language arent held in the context, so you can't ask for them. You could write a tag, that adds something to the context, I once needed such thing and used it, but I don't believe that's the way to go here. Above solution should work (I don't have machine to check this) and it's the best way IMNSHO.

Upvotes: 6

Related Questions