Django's template engine lets you create HTML with dynamic content. It uses {{ variables }} for output, {% tags %} for logic, and supports template inheritance.
Template Inheritance
The key concept is a base template that child templates extend:
HTML
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Blog{% endblock %}</title>
<style>
body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 2rem; }
.post { border-bottom: 1px solid #ddd; padding: 1rem 0; }
.meta { color: #666; font-size: 0.9rem; }
</style>
</head>
<body>
<nav>
<a href="{% url 'posts:list' %}">Home</a>
{% if user.is_authenticated %}
<a href="{% url 'posts:create' %}">New Post</a>
<a href="{% url 'logout' %}">Logout ({{ user.username }})</a>
{% else %}
<a href="{% url 'login' %}">Login</a>
{% endif %}
</nav>
{% if messages %}
<div class="messages">
{% for message in messages %}
<div class="alert {{ message.tags }}">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>HTML
<!-- templates/posts/post_list.html -->
{% extends "base.html" %}
{% block title %}All Posts — My Blog{% endblock %}
{% block content %}
<h1>Blog Posts</h1>
{% for post in posts %}
<article class="post">
<h2><a href="{% url 'posts:detail' slug=post.slug %}">{{ post.title }}</a></h2>
<p class="meta">
By {{ post.author.username }} · {{ post.created|date:"F j, Y" }}
{% if post.tags.all %}
· Tags: {% for tag in post.tags.all %}{{ tag.name }}{% if not forloop.last %}, {% endif %}{% endfor %}
{% endif %}
</p>
<p>{{ post.body|truncatewords:30 }}</p>
</article>
{% empty %}
<p>No posts yet. {% if user.is_authenticated %}<a href="{% url 'posts:create' %}">Write one!</a>{% endif %}</p>
{% endfor %}
{% endblock %}Template Language Reference
Variables
HTML
{{ post.title }} <!-- Object attribute -->
{{ post.author.username }} <!-- Nested attribute -->
{{ items.0 }} <!-- List index -->
{{ dict.key }} <!-- Dictionary key -->Filters
HTML
{{ value|lower }} <!-- lowercase -->
{{ value|upper }} <!-- UPPERCASE -->
{{ value|title }} <!-- Title Case -->
{{ value|truncatewords:20 }} <!-- First 20 words -->
{{ value|length }} <!-- Length of string/list -->
{{ value|default:"N/A" }} <!-- Fallback if empty -->
{{ value|date:"Y-m-d" }} <!-- Date formatting -->
{{ value|safe }} <!-- Mark as safe HTML -->
{{ value|linebreaksbr }} <!-- Convert newlines to <br> -->
{{ list|join:", " }} <!-- Join list items -->
{{ value|add:"5" }} <!-- Add 5 to value -->Tags
HTML
{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}!</p>
{% elif user.is_staff %}
<p>Welcome, staff!</p>
{% else %}
<p>Please log in.</p>
{% endif %}
{% for item in items %}
<li>{{ item }}</li>
{% if forloop.first %}<li>--- First ---</li>{% endif %}
{% if forloop.last %}<li>--- Last ---</li>{% endif %}
<li>Loop {{ forloop.counter }} of {{ forloop.length }}</li>
{% empty %}
<li>No items found.</li>
{% endfor %}
{% with total=items|length %}
<p>Total items: {{ total }}</p>
{% endwith %}
{% url 'posts:detail' slug=post.slug as post_url %}
<a href="{{ post_url }}">{{ post.title }}</a>Include and Block
HTML
<!-- Reusable component -->
{% include "posts/_comment.html" with comment=comment %}
<!-- Extending parent template -->
{% extends "base.html" %}
{% block title %}Page Title{% endblock %}
{% block content %}Page content here{% endblock %}Template Custom Tags and Filters
Python
# posts/templatetags/blog_tags.py
from django import template
from django.db.models import Count
register = template.Library()
@register.filter(name='reading_time')
def reading_time(text):
words = len(text.split())
minutes = max(1, words // 200)
return f'{minutes} min read'
@register.simple_tag
def total_posts():
from posts.models import Post
return Post.objects.filter(published=True).count()
@register.inclusion_tag('posts/_sidebar.html')
def show_popular_posts(count=5):
from posts.models import Post
popular = Post.objects.filter(published=True).annotate(
num_comments=Count('comments')
).order_by('-num_comments')[:count]
return {'popular_posts': popular}Usage in templates:
HTML
{% load blog_tags %}
<p>{{ post.body|reading_time }}</p>
{% total_posts as count %}
<p>We have published {{ count }} posts!</p>
{% show_popular_posts 3 %}💡 Tip: Always use template inheritance. Put common elements (nav, footer, CSS) in
base.htmland extend it in every page template.
Next: Forms and Validation