59.6 Templates: Template Language and Inheritance
Right, let’s talk about Django templates. Forget what you’ve heard about templating languages being an afterthought. Django’s is a deliberately, almost frustratingly, limited set of tools. And that’s its genius. It’s not trying to be a full programming language. Its entire job is to present data that your views have already chewed up and prepared. This separation is sacred. It keeps your designers from accidentally nuking your database and your developers from writing hideous, unmaintainable HTML soup.
The Syntax: Your New Best Friends (and occasional frenemies)
At its core, the Django Template Language (DTL) is about two things: {{ variables }} and {% tags %}.
Variables are how you inject data. You just plop them in. The DTL tries to be forgiving; if a variable doesn’t exist, it defaults to an empty string. It’s not raising a huge fuss, which is both a blessing (your page doesn’t crash) and a curse (silent failures can be a pain to debug).
<h1>Welcome back, {{ user.first_name }}!</h1>
<p>You have {{ unread_notifications.count }} new notifications.</p>
Tags are the workhorses. They do logic-y things. {% for %}, {% if %}, {% url %}, you get the drill. The critical thing to remember, and this is where people constantly faceplant, is that the DTL is not Python.
{% for item in item_list %}
{# Good: This works. item is the current object in the loop. #}
<div>{{ item.name }}</div>
{# BAD: This will NOT work. You can't call functions with arguments. #}
{# <div>{{ item.get_absolute_url }}</div> #}
{# Less bad, but still clunky: You have to use the {% %} tag syntax for multi-argument stuff. #}
{% url 'item_detail' item.id as item_url %}
<a href="{{ item_url }}">Link</a>
{% endfor %}
See? You can’t just call any method with arguments. You have to pre-calculate that stuff in your view or use a simple property. It feels restrictive because it is restrictive, and it’s designed to keep complex logic out of your templates where it doesn’t belong.
The Almighty Filter
When the variable isn’t quite what you need, you reach for a filter. They’re like little functions that modify variables right in the template. Django comes with a boatload of them.
{# Let's say our blog post date is a full datetime object #}
<p>Published: {{ post.published_on|date:"F j, Y" }}</p> {# Outputs: July 10, 2023 #}
{# Or perhaps a user-generated string is a bit... verbose #}
<p>{{ user.bio|truncatewords:30 }}</p>
{# And my personal favorite for dealing with "what if it's None?" #}
<p>Your total is: ${{ total_amount|default:"0.00" }}</p>
You can even chain them, because sometimes you just need to |lower|capfirst a string to make it presentable. The default filter is a lifesaver. Use it religiously to avoid those weird-looking “Your total is: $” moments.
Template Inheritance: The “Don’t Repeat Yourself” of HTML
This is the killer feature. The one that makes Django templates worth it. You define a base template—a skeleton of your site with all the common HTML boilerplate, navigation, and footers—and then you poke holes in it that child templates can fill.
Your base.html will look something like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}My Awesome Site{% endblock %}</title>
</head>
<body>
<nav>{# your nav here #}</nav>
<main>
{% block content %}
{# This is where the unique stuff for each page goes. #}
{% endblock %}
</main>
<footer>{# your footer here #}</footer>
</body>
</html>
Notice the {% block %} tags. They define editable regions. Now, a child template, say blog_detail.html, can inherit from it and only worry about the unique parts:
{% extends "base.html" %}
{# Pro move: You can use the block name to set the page title from the child template. #}
{% block title %}{{ post.title }} | My Awesome Site{% endblock %}
{% block content %}
<article>
<h1>{{ post.title }}</h1>
<div>{{ post.body|safe }}</div>
</article>
{% endblock %}
The {% extends %} tag is magic. It tells Django, “Go find base.html and everything in this file is meant to replace a block in that parent.” It’s incredibly powerful for maintaining consistency across a site. You change the nav in one file, and it updates everywhere.
The Rough Edges and Best Practices
Let’s be honest. The DTL has its quirks. Its refusal to let you pass arguments to methods is a constant source of grumbling. The solution? Use custom template tags or filters for complex presentation logic. It’s Django’s way of saying, “That’s probably a job for the view, pal.”
Another common pitfall: overloading your views with context data just because the template is a bit dumb. If your view is passing a dozen different variables to the template, it might be a sign you should create a custom template tag to encapsulate that complexity.
And a word on the |safe filter I used above. It tells Django, “I pinky-promise this string is safe HTML and you shouldn’t escape it.” You should treat this filter with the respect you’d give a loaded weapon. Never, ever use it on user-generated content unless you’ve sanitized it yourself first. The default auto-escaping is your best defense against XSS attacks; don’t disable it lightly.
So embrace the constraints. The DTL’s limitations are a feature, guiding you toward a cleaner, more secure, and more maintainable design. It’s not trying to be clever. It’s trying to be correct. And honestly, in web development, I’ll take correct over clever any day of the week.