Templates and Template Tags

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.html and extend it in every page template.

Next: Forms and Validation