#Django Template System
This chapter details Django's template system, including template syntax, template inheritance, filters, tags, context processors, and other core concepts, helping you create dynamic and maintainable HTML pages.
#Template System Overview
#What are Django Templates
Django templates are text files that can generate any text-based format (HTML, XML, CSV, etc.). Templates contain variables and tags that are replaced with actual values when rendered.
<!-- Basic template example -->
<!DOCTYPE html>
<html>
<head>
<title>{{ page_title }}</title>
</head>
<body>
<h1>Welcome, {{ user.username }}!</h1>
{% if articles %}
<ul>
{% for article in articles %}
<li><a href="{{ article.get_absolute_url }}">{{ article.title }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No articles yet</p>
{% endif %}
</body>
</html>#Template Configuration
# settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
BASE_DIR / 'templates', # Project-level template directory
],
'APP_DIRS': True, # Search for templates in app directories
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'myapp.context_processors.custom_context', # Custom context processor
],
},
},
]#Template Directory Structure
myproject/
├── templates/ # Project-level templates
│ ├── base.html # Base template
│ ├── 404.html # Error pages
│ ├── 500.html
│ └── registration/ # Authentication-related templates
│ ├── login.html
│ └── signup.html
├── blog/
│ └── templates/
│ └── blog/ # Application-level templates
│ ├── article_list.html
│ ├── article_detail.html
│ └── category.html
└── accounts/
└── templates/
└── accounts/
├── profile.html
└── settings.html#Template Syntax Basics
#Variables
<!-- Basic variables -->
{{ variable }}
{{ user.username }}
{{ article.title }}
<!-- Dictionary access -->
{{ data.key }}
{{ user.profile.bio }}
<!-- List access -->
{{ items.0 }}
{{ articles.first.title }}
<!-- Method calls (no parameters) -->
{{ article.get_absolute_url }}
{{ user.get_full_name }}
<!-- Default values -->
{{ variable|default:"default value" }}
{{ user.email|default:"email not set" }}#Tags
<!-- Conditional statements -->
{% if user.is_authenticated %}
<p>Welcome, {{ user.username }}!</p>
{% elif user.is_anonymous %}
<p>Please log in first</p>
{% else %}
<p>Unknown user status</p>
{% endif %}
<!-- Loops -->
{% for article in articles %}
<div class="article">
<h3>{{ article.title }}</h3>
<p>{{ article.content|truncatewords:30 }}</p>
<!-- Loop variables -->
<small>Article {{ forloop.counter }}</small>
{% if forloop.first %}
<span class="badge">Latest</span>
{% endif %}
{% if forloop.last %}
<hr>
{% endif %}
</div>
{% empty %}
<p>No articles yet</p>
{% endfor %}
<!-- Loop variables explained -->
{{ forloop.counter }} <!-- Current iteration (1-indexed) -->
{{ forloop.counter0 }} <!-- Current iteration (0-indexed) -->
{{ forloop.revcounter }} <!-- Remaining iterations (to 1) -->
{{ forloop.revcounter0 }} <!-- Remaining iterations (to 0) -->
{{ forloop.first }} <!-- Whether first iteration -->
{{ forloop.last }} <!-- Whether last iteration -->
{{ forloop.parentloop }} <!-- Parent loop object -->
<!-- URL generation -->
<a href="{% url 'blog:article_detail' article.id %}">{{ article.title }}</a>
<a href="{% url 'blog:category' category.slug %}">{{ category.name }}</a>
<!-- Static files -->
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<img src="{% static 'images/logo.png' %}" alt="Logo">
<!-- Include other templates -->
{% include 'partials/header.html' %}
{% include 'partials/sidebar.html' with articles=latest_articles %}
<!-- Comments -->
{# This is a single-line comment #}
{% comment %}
This is a multi-line comment
Can contain multiple lines of content
{% endcomment %}#Filters
<!-- String filters -->
{{ article.title|upper }} <!-- Uppercase -->
{{ article.title|lower }} <!-- Lowercase -->
{{ article.title|title }} <!-- Title case -->
{{ article.title|capfirst }} <!-- Capitalize first letter -->
{{ article.content|truncatewords:30 }} <!-- Truncate to 30 words -->
{{ article.content|truncatechars:100 }} <!-- Truncate to 100 characters -->
{{ article.content|linebreaks }} <!-- Convert newlines to <p> and <br> -->
{{ article.content|striptags }} <!-- Remove HTML tags -->
{{ article.content|escape }} <!-- HTML escape -->
{{ article.content|safe }} <!-- Mark as safe, don't escape -->
<!-- Number filters -->
{{ price|floatformat:2 }} <!-- Format with 2 decimal places -->
{{ number|add:10 }} <!-- Add 10 -->
{{ items|length }} <!-- Get length -->
<!-- Date filters -->
{{ article.created_at|date:"Y-m-d H:i:s" }} <!-- Format date -->
{{ article.created_at|timesince }} <!-- Time since now -->
{{ article.created_at|timeuntil }} <!-- Time until future -->
<!-- List filters -->
{{ articles|first }} <!-- First element -->
{{ articles|last }} <!-- Last element -->
{{ articles|slice:":5" }} <!-- First 5 elements -->
{{ articles|random }} <!-- Random element -->
{{ tags|join:", " }} <!-- Join with comma -->
<!-- Chained filters -->
{{ article.title|lower|capfirst }}
{{ article.content|striptags|truncatewords:20 }}
{{ user.email|default:"not set"|upper }}#Template Inheritance
#Base Template
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Website{% endblock %}</title>
{% load static %}
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- Navigation bar -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{% url 'home' %}">My Website</a>
<div class="navbar-nav">
<a class="nav-link" href="{% url 'blog:article_list' %}">Articles</a>
<a class="nav-link" href="{% url 'about' %}">About</a>
{% if user.is_authenticated %}
<a class="nav-link" href="{% url 'profile' %}">{{ user.username }}</a>
<a class="nav-link" href="{% url 'logout' %}">Logout</a>
{% else %}
<a class="nav-link" href="{% url 'login' %}">Login</a>
<a class="nav-link" href="{% url 'signup' %}">Sign Up</a>
{% endif %}
</div>
</div>
</nav>
<!-- Message alerts -->
{% if messages %}
<div class="container mt-3">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Breadcrumb navigation -->
{% block breadcrumb %}{% endblock %}
<!-- Main content -->
<main class="container my-4">
<div class="row">
<div class="col-md-8">
{% block content %}{% endblock %}
</div>
<div class="col-md-4">
{% block sidebar %}
{% include 'partials/sidebar.html' %}
{% endblock %}
</div>
</div>
</main>
<!-- Footer -->
<footer class="bg-dark text-light py-4 mt-5">
<div class="container">
<div class="row">
<div class="col-md-6">
<p>© 2023 My Website. All rights reserved.</p>
</div>
<div class="col-md-6 text-end">
{% block footer_extra %}{% endblock %}
</div>
</div>
</div>
</footer>
<!-- JavaScript -->
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>#Child Template
<!-- blog/templates/blog/article_list.html -->
{% extends 'base.html' %}
{% load static %}
{% block title %}Article List - {{ block.super }}{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/blog.css' %}">
{% endblock %}
{% block breadcrumb %}
<nav aria-label="breadcrumb" class="container mt-3">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'home' %}">Home</a></li>
<li class="breadcrumb-item active">Article List</li>
</ol>
</nav>
{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Article List</h1>
{% if user.is_authenticated %}
<a href="{% url 'blog:create_article' %}" class="btn btn-primary">Write Article</a>
{% endif %}
</div>
<!-- Search and filtering -->
<div class="row mb-4">
<div class="col-md-8">
<form method="get" class="d-flex">
<input type="text" name="search" class="form-control me-2"
placeholder="Search articles..." value="{{ search_query }}">
<button type="submit" class="btn btn-outline-secondary">Search</button>
</form>
</div>
<div class="col-md-4">
<select class="form-select" onchange="filterByCategory(this.value)">
<option value="">All Categories</option>
{% for category in categories %}
<option value="{{ category.slug }}"
{% if category.slug == current_category %}selected{% endif %}>
{{ category.name }}
</option>
{% endfor %}
</select>
</div>
</div>
<!-- Article list -->
{% for article in page_obj %}
<article class="card mb-4">
{% if article.featured_image %}
<img src="{{ article.featured_image.url }}" class="card-img-top" alt="{{ article.title }}">
{% endif %}
<div class="card-body">
<h5 class="card-title">
<a href="{{ article.get_absolute_url }}" class="text-decoration-none">
{{ article.title }}
</a>
</h5>
<p class="card-text">{{ article.content|striptags|truncatewords:30 }}</p>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
<i class="fas fa-user"></i> {{ article.author.username }}
<i class="fas fa-calendar ms-2"></i> {{ article.created_at|date:"Y-m-d" }}
<i class="fas fa-eye ms-2"></i> {{ article.views }}
</small>
<span class="badge bg-secondary">{{ article.category.name }}</span>
</div>
</div>
</article>
{% empty %}
<div class="text-center py-5">
<h3>No articles yet</h3>
<p class="text-muted">No articles have been published yet</p>
{% if user.is_authenticated %}
<a href="{% url 'blog:create_article' %}" class="btn btn-primary">Write First Article</a>
{% endif %}
</div>
{% endfor %}
<!-- Pagination -->
{% if page_obj.has_other_pages %}
<nav aria-label="Article pagination">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if search_query %}&search={{ search_query }}{% endif %}">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search_query %}&search={{ search_query }}{% endif %}">Previous</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}{% if search_query %}&search={{ search_query }}{% endif %}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search_query %}&search={{ search_query }}{% endif %}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if search_query %}&search={{ search_query }}{% endif %}">Last</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% endblock %}
{% block sidebar %}
<!-- Latest articles -->
<div class="card mb-4">
<div class="card-header">
<h5>Latest Articles</h5>
</div>
<div class="card-body">
{% for article in latest_articles %}
<div class="mb-2">
<a href="{{ article.get_absolute_url }}" class="text-decoration-none">
{{ article.title|truncatechars:30 }}
</a>
<small class="text-muted d-block">{{ article.created_at|date:"m-d" }}</small>
</div>
{% endfor %}
</div>
</div>
<!-- Popular tags -->
<div class="card">
<div class="card-header">
<h5>Popular Tags</h5>
</div>
<div class="card-body">
{% for tag in popular_tags %}
<a href="{% url 'blog:tag' tag.slug %}" class="badge bg-light text-dark me-1 mb-1">
{{ tag.name }}
</a>
{% endfor %}
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
function filterByCategory(categorySlug) {
const url = new URL(window.location);
if (categorySlug) {
url.searchParams.set('category', categorySlug);
} else {
url.searchParams.delete('category');
}
window.location.href = url.toString();
}
</script>
{% endblock %}#Custom Template Tags and Filters
#Creating Template Tag Library
# blog/templatetags/__init__.py
# Empty file to make directory a Python package
# blog/templatetags/blog_extras.py
from django import template
from django.utils.safestring import mark_safe
from django.utils.html import format_html
from ..models import Article, Category
import markdown
register = template.Library()
# Simple tags
@register.simple_tag
def total_articles():
"""Return total number of articles"""
return Article.objects.filter(published=True).count()
@register.simple_tag
def get_categories():
"""Get all categories"""
return Category.objects.all()
@register.simple_tag(takes_context=True)
def current_time(context, format_string):
"""Get current time"""
from datetime import datetime
return datetime.now().strftime(format_string)
# Inclusion tags
@register.inclusion_tag('blog/tags/recent_articles.html')
def show_recent_articles(count=5):
"""Show recent articles"""
articles = Article.objects.filter(published=True)[:count]
return {'articles': articles}
@register.inclusion_tag('blog/tags/category_list.html', takes_context=True)
def show_categories(context):
"""Show category list"""
categories = Category.objects.all()
return {
'categories': categories,
'request': context['request'],
}
# Assignment tags
@register.simple_tag
def get_popular_articles(count=5):
"""Get popular articles"""
return Article.objects.filter(published=True).order_by('-views')[:count]
# Custom filters
@register.filter
def markdown_to_html(text):
"""Convert Markdown to HTML"""
return mark_safe(markdown.markdown(text))
@register.filter
def multiply(value, arg):
"""Multiplication filter"""
try:
return int(value) * int(arg)
except (ValueError, TypeError):
return 0
@register.filter
def get_item(dictionary, key):
"""Get value from dictionary"""
return dictionary.get(key)
@register.filter
def add_class(field, css_class):
"""Add CSS class to form field"""
return field.as_widget(attrs={'class': css_class})
# Conditional tags
@register.simple_tag
def url_replace(request, field, value):
"""Replace URL parameter"""
dict_ = request.GET.copy()
dict_[field] = value
return dict_.urlencode()
# Complex custom tags
@register.tag
def get_articles_by_category(parser, token):
"""Get articles by category"""
try:
tag_name, category_slug, as_var = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError(
f"{token.contents.split()[0]} tag requires exactly two arguments"
)
if not (as_var == 'as'):
raise template.TemplateSyntaxError(
f"{tag_name} tag's second argument should be 'as'"
)
return ArticlesByCategoryNode(category_slug, as_var)
class ArticlesByCategoryNode(template.Node):
def __init__(self, category_slug, var_name):
self.category_slug = template.Variable(category_slug)
self.var_name = var_name
def render(self, context):
try:
category_slug = self.category_slug.resolve(context)
articles = Article.objects.filter(
category__slug=category_slug,
published=True
)
context[self.var_name] = articles
except template.VariableDoesNotExist:
context[self.var_name] = []
return ''#Using Custom Tags and Filters
<!-- Using in templates -->
{% load blog_extras %}
<!-- Simple tags -->
<p>Website has {% total_articles %} articles in total</p>
<p>Current time: {% current_time "%Y-%m-d %H:%M:%S" %}</p>
<!-- Inclusion tags -->
{% show_recent_articles 10 %}
{% show_categories %}
<!-- Custom filters -->
{{ article.content|markdown_to_html }}
{{ price|multiply:quantity }}
{{ form.email|add_class:"form-control" }}
<!-- Complex tags -->
{% get_articles_by_category "technology" as tech_articles %}
{% for article in tech_articles %}
<h3>{{ article.title }}</h3>
{% endfor %}
<!-- URL parameter replacement -->
<a href="?{% url_replace request 'page' page_obj.next_page_number %}">Next Page</a>#Inclusion Tag Templates
<!-- blog/templates/blog/tags/recent_articles.html -->
<div class="recent-articles">
<h4>Latest Articles</h4>
<ul class="list-unstyled">
{% for article in articles %}
<li class="mb-2">
<a href="{{ article.get_absolute_url }}" class="text-decoration-none">
{{ article.title|truncatechars:40 }}
</a>
<small class="text-muted d-block">{{ article.created_at|date:"m-d" }}</small>
</li>
{% endfor %}
</ul>
</div>
<!-- blog/templates/blog/tags/category_list.html -->
<div class="category-list">
<h4>Article Categories</h4>
<ul class="list-unstyled">
{% for category in categories %}
<li>
<a href="{% url 'blog:category' category.slug %}"
class="{% if request.resolver_match.kwargs.slug == category.slug %}active{% endif %}">
{{ category.name }}
<span class="badge bg-secondary">{{ category.article_set.count }}</span>
</a>
</li>
{% endfor %}
</ul>
</div>#Context Processors
#Custom Context Processors
# blog/context_processors.py
from .models import Category, Article
def blog_context(request):
"""Blog-related global context"""
return {
'all_categories': Category.objects.all(),
'recent_articles': Article.objects.filter(published=True)[:5],
'popular_articles': Article.objects.filter(published=True).order_by('-views')[:5],
'site_name': 'My Blog',
'site_description': 'Sharing technology and life',
}
def navigation_context(request):
"""Navigation-related context"""
navigation_items = [
{'name': 'Home', 'url': '/', 'active': request.path == '/'},
{'name': 'Articles', 'url': '/blog/', 'active': request.path.startswith('/blog/')},
{'name': 'About', 'url': '/about/', 'active': request.path == '/about/'},
]
return {
'navigation_items': navigation_items,
'current_path': request.path,
}
def user_context(request):
"""User-related context"""
context = {}
if request.user.is_authenticated:
context.update({
'user_article_count': Article.objects.filter(author=request.user).count(),
'user_notifications': get_user_notifications(request.user),
})
return context
def get_user_notifications(user):
"""Get user notifications (example)"""
# Can implement actual notification logic here
return []#Register Context Processors
# settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'blog.context_processors.blog_context', # Custom context processor
'blog.context_processors.navigation_context', # Navigation context
'blog.context_processors.user_context', # User context
],
},
},
]#Template Optimization and Best Practices
#Template Caching
# settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
}
}
# Enable template caching
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'loaders': [
('django.template.loaders.cached.Loader', [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
]),
],
'context_processors': [
# ... context processors
],
},
},
]#Template Fragment Caching
<!-- Cache sidebar -->
{% load cache %}
{% cache 300 sidebar request.user.id %}
<div class="sidebar">
{% show_recent_articles %}
{% show_categories %}
</div>
{% endcache %}
<!-- Cache article list -->
{% cache 600 article_list page_obj.number %}
{% for article in page_obj %}
<!-- Article content -->
{% endfor %}
{% endcache %}
<!-- Conditional caching -->
{% if user.is_authenticated %}
{% cache 300 user_sidebar user.id %}
<!-- User-specific sidebar -->
{% endcache %}
{% else %}
{% cache 3600 anonymous_sidebar %}
<!-- Anonymous user sidebar -->
{% endcache %}
{% endif %}#Template Organization Best Practices
templates/
├── base.html # Base template
├── partials/ # Reusable template fragments
│ ├── header.html
│ ├── footer.html
│ ├── sidebar.html
│ ├── pagination.html
│ └── messages.html
├── layouts/ # Different layout templates
│ ├── single_column.html
│ ├── two_column.html
│ └── three_column.html
├── components/ # Component templates
│ ├── article_card.html
│ ├── comment_form.html
│ └── search_form.html
├── emails/ # Email templates
│ ├── welcome.html
│ ├── password_reset.html
│ └── notification.html
└── errors/ # Error page templates
├── 404.html
├── 500.html
└── 403.html#Reusable Template Components
<!-- components/article_card.html -->
<div class="card mb-3">
{% if article.featured_image %}
<img src="{{ article.featured_image.url }}" class="card-img-top" alt="{{ article.title }}">
{% endif %}
<div class="card-body">
<h5 class="card-title">
<a href="{{ article.get_absolute_url }}">{{ article.title }}</a>
</h5>
<p class="card-text">{{ article.content|striptags|truncatewords:20 }}</p>
<div class="card-meta">
<small class="text-muted">
{{ article.author.username }} · {{ article.created_at|date:"Y-m-d" }}
</small>
<span class="badge bg-primary">{{ article.category.name }}</span>
</div>
</div>
</div>
<!-- Using components -->
{% for article in articles %}
{% include 'components/article_card.html' with article=article %}
{% endfor %}#Responsive Templates
<!-- Responsive base template -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %}</title>
{% load static %}
<!-- Bootstrap CSS -->
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
<!-- Custom CSS -->
<link href="{% static 'css/style.css' %}" rel="stylesheet">
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- Mobile navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{% url 'home' %}">My Website</a>
<!-- Mobile toggle button -->
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
{% for item in navigation_items %}
<li class="nav-item">
<a class="nav-link {% if item.active %}active{% endif %}"
href="{{ item.url }}">{{ item.name }}</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</nav>
<!-- Main content -->
<main class="container-fluid">
<div class="row">
<!-- Content area -->
<div class="col-lg-8 col-md-12">
{% block content %}{% endblock %}
</div>
<!-- Sidebar - hidden on small screens -->
<div class="col-lg-4 d-none d-lg-block">
{% block sidebar %}{% endblock %}
</div>
</div>
</main>
<!-- Bootstrap JS -->
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>#Chapter Summary
This chapter detailed Django's template system:
Key Points:
- Template Syntax: Using variables, tags, filters
- Template Inheritance: Code reuse through extends and block
- Custom Tags: Creating your own template tags and filters
- Context Processors: Providing global variables for all templates
- Template Optimization: Caching and performance optimization techniques
Important Concepts:
- DRY Principle: Avoid code duplication through inheritance and inclusion
- Separation of Concerns: Templates only handle presentation logic
- Security: Automatic HTML escaping and safe marking
- Maintainability: Good template organization structure
Best Practices:
- Use template inheritance to establish consistent page structure
- Create reusable template components
- Use caching appropriately to improve performance
- Keep template logic simple
- Use semantic HTML structure
In the next chapter, we will learn about Django's static file management to understand how to handle static resources like CSS, JavaScript, and images.