Reputation: 1273
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
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
Reputation: 7344
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
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
Reputation: 42188
Do it like this:
<h2>{% block heading %}{% endblock %}</h2>
{% 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