Templates and Presentation Layer

Introduction to Django Templates

Django templates are text files that define the structure and layout of your web pages. They combine static HTML with dynamic content using Django's template language, providing a clean separation between presentation and business logic.

Introduction to Django Templates

Django templates are text files that define the structure and layout of your web pages. They combine static HTML with dynamic content using Django's template language, providing a clean separation between presentation and business logic.

Template Fundamentals

What Are Templates?

Templates are files containing static content mixed with special syntax for dynamic content:

<!-- blog/templates/blog/post_detail.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ post.title }} - My Blog</title>
</head>
<body>
    <h1>{{ post.title }}</h1>
    <p>By {{ post.author.username }} on {{ post.created_at|date:"F d, Y" }}</p>
    <div class="content">
        {{ post.content|linebreaks }}
    </div>
</body>
</html>

Template Syntax Elements

Variables - Display dynamic content

{{ variable_name }}
{{ object.attribute }}
{{ dictionary.key }}

Tags - Control logic and flow

{% if condition %}
    <p>Content when true</p>
{% endif %}

{% for item in items %}
    <li>{{ item }}</li>
{% endfor %}

Filters - Transform variable output

{{ text|lower }}
{{ date|date:"Y-m-d" }}
{{ content|truncatewords:50 }}

Comments - Documentation that won't appear in output

{# This is a comment #}
{% comment %}
    Multi-line comment
    for documentation
{% endcomment %}

Template Configuration

Settings Configuration

# settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            BASE_DIR / 'templates',  # Project-level templates
        ],
        'APP_DIRS': True,  # Look 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',
            ],
        },
    },
]

Template Directory Structure

myproject/
├── templates/                 # Project-level templates
│   ├── base.html             # Base template
│   ├── 404.html              # Error pages
│   ├── 500.html
│   └── includes/             # Reusable components
│       ├── header.html
│       ├── footer.html
│       └── navigation.html
├── blog/
│   └── templates/
│       └── blog/             # App-specific templates
│           ├── post_list.html
│           ├── post_detail.html
│           └── post_form.html
└── accounts/
    └── templates/
        └── registration/     # Authentication templates
            ├── login.html
            └── signup.html

Creating Your First Template

Basic Template Example

<!-- templates/blog/post_list.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Blog Posts</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        .post { border-bottom: 1px solid #eee; padding: 20px 0; }
        .post h2 { margin: 0 0 10px 0; }
        .meta { color: #666; font-size: 0.9em; }
    </style>
</head>
<body>
    <h1>Latest Blog Posts</h1>
    
    {% for post in posts %}
        <article class="post">
            <h2>{{ post.title }}</h2>
            <p class="meta">
                By {{ post.author.get_full_name|default:post.author.username }}
                on {{ post.created_at|date:"F d, Y" }}
            </p>
            <p>{{ post.excerpt|default:post.content|truncatewords:30 }}</p>
            <a href="{% url 'blog:post_detail' post.pk %}">Read more</a>
        </article>
    {% empty %}
        <p>No posts available yet.</p>
    {% endfor %}
</body>
</html>

Connecting Templates to Views

Function-Based View:

# blog/views.py
from django.shortcuts import render
from .models import Post

def post_list(request):
    posts = Post.objects.filter(published=True).order_by('-created_at')
    context = {
        'posts': posts,
        'page_title': 'Latest Posts',
    }
    return render(request, 'blog/post_list.html', context)

Class-Based View:

# blog/views.py
from django.views.generic import ListView
from .models import Post

class PostListView(ListView):
    model = Post
    template_name = 'blog/post_list.html'
    context_object_name = 'posts'
    paginate_by = 10
    
    def get_queryset(self):
        return Post.objects.filter(published=True).order_by('-created_at')
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['page_title'] = 'Latest Posts'
        return context

Template Variables and Context

Context Data Types

Simple Variables:

# views.py
context = {
    'title': 'My Blog',
    'user_count': 150,
    'is_featured': True,
    'tags': ['django', 'python', 'web'],
}
<!-- template.html -->
<h1>{{ title }}</h1>
<p>We have {{ user_count }} users!</p>
{% if is_featured %}
    <span class="featured">Featured Content</span>
{% endif %}

<ul>
{% for tag in tags %}
    <li>{{ tag }}</li>
{% endfor %}
</ul>

Object Attributes:

# models.py
class Post(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    
    def get_absolute_url(self):
        return reverse('blog:post_detail', kwargs={'pk': self.pk})
    
    @property
    def word_count(self):
        return len(self.content.split())
<!-- template.html -->
<h2>{{ post.title }}</h2>
<p>By {{ post.author.username }}</p>
<p>Created: {{ post.created_at }}</p>
<p>Word count: {{ post.word_count }}</p>
<a href="{{ post.get_absolute_url }}">Read more</a>

Dictionary Access:

# views.py
context = {
    'user_stats': {
        'total': 1000,
        'active': 750,
        'new_today': 25,
    },
    'settings': {
        'site_name': 'My Blog',
        'maintenance_mode': False,
    }
}
<!-- template.html -->
<h1>{{ settings.site_name }}</h1>
<p>Total users: {{ user_stats.total }}</p>
<p>Active users: {{ user_stats.active }}</p>
{% if not settings.maintenance_mode %}
    <p>Site is operational</p>
{% endif %}

Template Loading and Resolution

Template Loader Order

Django searches for templates in this order:

  1. Filesystem Loader - Directories in TEMPLATES['DIRS']
  2. App Directories Loader - templates/ folders in installed apps
  3. Cached Loader - Cached templates (production)

Template Name Resolution

# When you call render(request, 'blog/post_list.html', context)
# Django looks for:

# 1. /path/to/project/templates/blog/post_list.html
# 2. /path/to/blog/templates/blog/post_list.html
# 3. /path/to/other_app/templates/blog/post_list.html

Avoiding Template Name Conflicts

Good Practice - Namespace Templates:

blog/templates/blog/post_list.html     # ✓ Namespaced
accounts/templates/accounts/login.html  # ✓ Namespaced

Bad Practice - No Namespace:

blog/templates/post_list.html          # ✗ Could conflict
accounts/templates/login.html          # ✗ Could conflict

Context Processors

Built-in Context Processors

Context processors add variables to every template context:

# settings.py
TEMPLATES = [{
    'OPTIONS': {
        'context_processors': [
            'django.template.context_processors.debug',      # {{ debug }}
            'django.template.context_processors.request',    # {{ request }}
            'django.contrib.auth.context_processors.auth',   # {{ user }}, {{ perms }}
            'django.contrib.messages.context_processors.messages',  # {{ messages }}
        ],
    },
}]

Using Built-in Context Variables:

<!-- Available in all templates -->
<p>Hello, {{ user.username }}!</p>
<p>Current path: {{ request.path }}</p>

{% if messages %}
    {% for message in messages %}
        <div class="alert alert-{{ message.tags }}">
            {{ message }}
        </div>
    {% endfor %}
{% endif %}

{% if debug %}
    <div class="debug-info">Debug mode is on</div>
{% endif %}

Custom Context Processors

# blog/context_processors.py
from .models import Category

def blog_context(request):
    """Add blog-specific context to all templates."""
    return {
        'site_name': 'My Awesome Blog',
        'categories': Category.objects.all(),
        'recent_posts': Post.objects.filter(published=True)[:5],
    }
# settings.py
TEMPLATES = [{
    'OPTIONS': {
        'context_processors': [
            # ... built-in processors
            'blog.context_processors.blog_context',
        ],
    },
}]
<!-- Now available in all templates -->
<h1>{{ site_name }}</h1>

<nav>
    {% for category in categories %}
        <a href="{% url 'blog:category' category.slug %}">{{ category.name }}</a>
    {% endfor %}
</nav>

<aside>
    <h3>Recent Posts</h3>
    {% for post in recent_posts %}
        <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
    {% endfor %}
</aside>

Template Security

Automatic HTML Escaping

Django automatically escapes variables to prevent XSS attacks:

# views.py
context = {
    'user_input': '<script>alert("XSS")</script>',
    'safe_html': '<strong>Bold text</strong>',
}
<!-- template.html -->
{{ user_input }}  <!-- Outputs: &lt;script&gt;alert("XSS")&lt;/script&gt; -->
{{ safe_html }}   <!-- Outputs: &lt;strong&gt;Bold text&lt;/strong&gt; -->

<!-- To output unescaped HTML (use carefully!) -->
{{ safe_html|safe }}  <!-- Outputs: <strong>Bold text</strong> -->

Safe vs Unsafe Content

# views.py
from django.utils.safestring import mark_safe

def my_view(request):
    # Unsafe - will be escaped
    unsafe_content = '<p>This will be escaped</p>'
    
    # Safe - marked as safe HTML
    safe_content = mark_safe('<p>This is safe HTML</p>')
    
    context = {
        'unsafe_content': unsafe_content,
        'safe_content': safe_content,
    }
    return render(request, 'template.html', context)
<!-- template.html -->
{{ unsafe_content }}  <!-- Escaped -->
{{ safe_content }}    <!-- Not escaped -->

<!-- Alternative: use the safe filter -->
{{ unsafe_content|safe }}  <!-- Not escaped - be careful! -->

Error Handling

Template Error Types

TemplateDoesNotExist:

# Common causes:
# 1. Wrong template path
return render(request, 'blog/nonexistent.html', context)

# 2. Missing app in INSTALLED_APPS
# 3. Incorrect TEMPLATES configuration

TemplateSyntaxError:

<!-- Common syntax errors -->
{% if condition %  <!-- Missing closing % -->
{{ variable.missing_closing_brace
{% for item in items %}  <!-- Missing {% endfor %} -->

VariableDoesNotExist:

<!-- Accessing undefined variables -->
{{ undefined_variable }}  <!-- Returns empty string in production -->
{{ object.nonexistent_attribute }}  <!-- Returns empty string -->

Debug Mode vs Production

Debug Mode (DEBUG=True):

  • Detailed error pages
  • Template source highlighting
  • Variable inspection
  • Helpful error messages

Production Mode (DEBUG=False):

  • Generic error pages
  • Undefined variables return empty strings
  • Template errors logged
  • Custom error templates (404.html, 500.html)

Template Organization Patterns

Single App Structure

blog/
└── templates/
    └── blog/
        ├── base.html
        ├── post_list.html
        ├── post_detail.html
        ├── post_form.html
        └── includes/
            ├── post_card.html
            └── pagination.html

Multi-App Structure

templates/                    # Project-level templates
├── base.html                # Site-wide base
├── includes/
│   ├── header.html
│   ├── footer.html
│   └── navigation.html
└── errors/
    ├── 404.html
    └── 500.html

blog/templates/blog/         # Blog-specific templates
├── post_list.html
└── post_detail.html

accounts/templates/registration/  # Auth templates
├── login.html
└── signup.html

Template Naming Conventions

Consistent Naming:

# List views
post_list.html
category_list.html
user_list.html

# Detail views
post_detail.html
category_detail.html
user_detail.html

# Form views
post_form.html
post_create.html
post_update.html

# Partial templates
_post_card.html
_pagination.html
_form_errors.html

Performance Considerations

Template Caching

# settings.py - Enable template caching in production
TEMPLATES = [{
    'BACKEND': 'django.template.backends.django.DjangoTemplates',
    'DIRS': [BASE_DIR / 'templates'],
    'OPTIONS': {
        'loaders': [
            ('django.template.loaders.cached.Loader', [
                'django.template.loaders.filesystem.Loader',
                'django.template.loaders.app_directories.Loader',
            ]),
        ],
        'context_processors': [
            # ... context processors
        ],
    },
}]

Efficient Context Passing

# Efficient - only pass what's needed
def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    context = {
        'post': post,
        'related_posts': post.get_related_posts()[:3],  # Limit results
    }
    return render(request, 'blog/post_detail.html', context)

# Inefficient - passing unnecessary data
def post_detail_bad(request, pk):
    context = {
        'all_posts': Post.objects.all(),  # Too much data
        'all_users': User.objects.all(),  # Unnecessary
        'post': get_object_or_404(Post, pk=pk),
    }
    return render(request, 'blog/post_detail.html', context)

Django templates provide a powerful, secure, and flexible way to generate dynamic content. Understanding these fundamentals prepares you for more advanced template features and optimization techniques.