Skip to content

Django Template Architecture

This guide provides a comprehensive overview of how Django templates are organized and used in Arctyk ITSM.


What Are Django Templates?

Django templates are text files (usually HTML) that define the structure and layout of web pages. They use the Django Template Language (DTL) to insert dynamic content, implement logic, and create reusable components.

Key Features

  • Variable substitution: {{ variable }}
  • Template tags: {% for item in items %}...{% endfor %}
  • Filters: {{ value|date:"Y-m-d" }}
  • Template inheritance: {% extends "base.html" %}
  • Template inclusion: {% include "partials/sidebar.html" %}

Template Configuration

Templates are configured in config/settings.py:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            BASE_DIR / 'templates',  # Project-level templates
        ],
        'APP_DIRS': True,  # Look for templates in each app's templates/ dir
        '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',
                # Custom context processors (if any)
            ],
        },
    },
]

Template Resolution Order

Django looks for templates in this order:

  1. Project templates directory: templates/
  2. App templates directories: tickets/templates/, projects/templates/, etc.

Tip

App templates should be namespaced: tickets/templates/tickets/ticket_list.html


Template Hierarchy

Arctyk ITSM uses a multi-level template inheritance pattern:

base.html                           ← Base template (site-wide)
  └── layouts/_layout_wrapper.html  ← Standard layout (sidebar + topbar)
      └── tickets/ticket_list.html  ← Page-specific template

Level 1: Base Template (base.html)

The foundation for ALL pages. Defines: - HTML structure (<html>, <head>, <body>) - Meta tags and SEO - Global CSS and JavaScript - Common blocks for overriding

Example:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Arctyk ITSM{% endblock %}</title>

    {% load static %}
    <link rel="stylesheet" href="{% static 'css/main.css' %}">

    {% block extra_css %}{% endblock %}
</head>
<body>
    {% block body %}
    <div class="app-container">
        {% block content %}{% endblock %}
    </div>
    {% endblock body %}

    <script src="{% static 'js/main.js' %}"></script>
    {% block extra_js %}{% endblock %}
</body>
</html>

Level 2: Layout Wrapper (layouts/_layout_wrapper.html)

Standard page layout with sidebar and topbar. Most authenticated pages extend this.

Example:

{% extends "base.html" %}

{% block body %}
<div class="app-layout">
    {% include "partials/sidebar.html" %}

    <div class="main-content">
        {% include "partials/topbar.html" %}

        <div class="content-area">
            {% include "partials/breadcrumbs.html" %}

            <main class="page-content">
                {% block page_content %}{% endblock %}
            </main>
        </div>
    </div>
</div>
{% endblock body %}

Level 3: Page Templates (e.g., tickets/ticket_list.html)

Specific pages that provide actual content:

{% extends "layouts/_layout_wrapper.html" %}

{% block title %}Tickets - Arctyk ITSM{% endblock %}

{% block page_content %}
<div class="ticket-list">
    <h1>Tickets</h1>

    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Title</th>
                <th>Status</th>
            </tr>
        </thead>
        <tbody>
            {% for ticket in tickets %}
            <tr>
                <td>{{ ticket.id }}</td>
                <td>{{ ticket.title }}</td>
                <td>{{ ticket.status }}</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
</div>
{% endblock %}

Template Blocks

Blocks are placeholders that child templates can override.

Common Blocks in Arctyk ITSM

Block Name Purpose Defined In
title Page title (shown in browser tab) base.html
extra_css Page-specific CSS base.html
extra_js Page-specific JavaScript base.html
body Entire body content base.html
content Main content area base.html
page_content Page-specific content _layout_wrapper.html

Overriding Blocks

{% extends "layouts/_layout_wrapper.html" %}

{% block title %}Create Ticket - Arctyk ITSM{% endblock %}

{% block extra_css %}
<style>
    .ticket-form { max-width: 800px; }
</style>
{% endblock %}

{% block page_content %}
<h1>Create New Ticket</h1>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Create</button>
</form>
{% endblock %}

Template Inheritance: {% extends %} vs {% include %}

{% extends %} - Template Inheritance

Use when creating a child template that builds upon a parent:

{% extends "base.html" %}

{% block content %}
    <p>This replaces the content block</p>
{% endblock %}

Characteristics: - Can only extend ONE parent template - Must be the first tag in the file (except comments) - Overrides specific blocks - Used for page hierarchy

{% include %} - Template Inclusion

Use for reusable fragments (partials):

{% include "partials/sidebar.html" %}
{% include "partials/topbar.html" %}
{% include "partials/breadcrumbs.html" %}

Characteristics: - Can include multiple templates - Can be used anywhere in the template - Inherits parent context - Can pass additional context: {% include "partial.html" with user=user %}


Context Processors

Context processors automatically add variables to ALL template contexts.

Built-in Context Processors

Configured in settings.py:

'context_processors': [
    'django.template.context_processors.debug',      # Adds 'debug' variable
    'django.template.context_processors.request',    # Adds 'request' object
    'django.contrib.auth.context_processors.auth',   # Adds 'user', 'perms'
    'django.contrib.messages.context_processors.messages',  # Adds 'messages'
],

Available in All Templates

{# User information #}
{% if user.is_authenticated %}
    <p>Welcome, {{ user.username }}!</p>
{% endif %}

{# Request object #}
<p>Current path: {{ request.path }}</p>

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

Custom Context Processor Example

Create core/context_processors.py:

def site_settings(request):
    """Add site-wide settings to all templates."""
    return {
        'SITE_NAME': 'Arctyk ITSM',
        'VERSION': '0.4.4',
        'SUPPORT_EMAIL': 'support@arctyk.dev',
    }

Register in settings.py:

'context_processors': [
    # ... other processors
    'core.context_processors.site_settings',
],

Use in templates:

<footer>
    {{ SITE_NAME }} v{{ VERSION }} - Contact: {{ SUPPORT_EMAIL }}
</footer>

Template Tags and Filters

Common Template Tags

{# Conditional logic #}
{% if ticket.is_open %}
    <span class="badge badge-open">Open</span>
{% elif ticket.is_closed %}
    <span class="badge badge-closed">Closed</span>
{% else %}
    <span class="badge badge-pending">Pending</span>
{% endif %}

{# Loops #}
{% for ticket in tickets %}
    <div>{{ ticket.title }}</div>
{% empty %}
    <p>No tickets found.</p>
{% endfor %}

{# URL generation #}
<a href="{% url 'ticket_detail' ticket.id %}">View Ticket</a>

{# CSRF token (required for POST forms) #}
<form method="post">
    {% csrf_token %}
    <!-- form fields -->
</form>

{# Comments #}
{# This is a template comment - won't appear in HTML #}

Common Filters

{# Date formatting #}
{{ ticket.created_at|date:"Y-m-d H:i" }}

{# String manipulation #}
{{ ticket.title|title }}           {# Title Case #}
{{ ticket.description|truncatewords:20 }}

{# Default values #}
{{ ticket.assignee|default:"Unassigned" }}

{# Safe HTML rendering #}
{{ ticket.description|safe }}

{# Length #}
{{ tickets|length }} tickets found

{# Chaining filters #}
{{ ticket.title|lower|truncatewords:5 }}

Static Files Management

Loading Static Files

{% load static %}

<link rel="stylesheet" href="{% static 'css/main.css' %}">
<script src="{% static 'js/ticket_form.js' %}"></script>
<img src="{% static 'images/logo.png' %}" alt="Arctyk Logo">

Static File Organization

static/
├── css/
│   ├── main.css           # Compiled from SCSS
│   └── components/
├── js/
│   ├── main.js
│   └── views/
│       └── ticket_form.js
└── images/
    └── logo.png

Collecting Static Files

In production, run:

python manage.py collectstatic

This copies all static files to STATIC_ROOT for serving by Nginx.


Example Templates from Arctyk ITSM

Ticket List Template

{% extends "layouts/_layout_wrapper.html" %}
{% load static %}

{% block title %}Tickets - Arctyk ITSM{% endblock %}

{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/views/ticket_list.css' %}">
{% endblock %}

{% block page_content %}
<div class="ticket-list-container">
    <div class="page-header">
        <h1>Tickets</h1>
        <a href="{% url 'ticket_create' %}" class="btn btn-primary">
            Create Ticket
        </a>
    </div>

    {# Filters #}
    <div class="filters">
        <form method="get">
            <input type="text" name="q" placeholder="Search tickets...">
            <select name="status">
                <option value="">All Statuses</option>
                <option value="open">Open</option>
                <option value="closed">Closed</option>
            </select>
            <button type="submit">Filter</button>
        </form>
    </div>

    {# Ticket table #}
    <table class="table table-hover">
        <thead>
            <tr>
                {% include "partials/sort_header.html" with field="id" label="ID" %}
                {% include "partials/sort_header.html" with field="title" label="Title" %}
                {% include "partials/sort_header.html" with field="status" label="Status" %}
                {% include "partials/sort_header.html" with field="created_at" label="Created" %}
            </tr>
        </thead>
        <tbody>
            {% for ticket in tickets %}
            <tr>
                <td><a href="{% url 'ticket_detail' ticket.id %}">#{{ ticket.id }}</a></td>
                <td>{{ ticket.title }}</td>
                <td><span class="badge badge-{{ ticket.status }}">{{ ticket.get_status_display }}</span></td>
                <td>{{ ticket.created_at|date:"Y-m-d H:i" }}</td>
            </tr>
            {% empty %}
            <tr>
                <td colspan="4" class="text-center">No tickets found.</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>

    {# Pagination #}
    {% include "partials/pagination.html" %}
</div>
{% endblock %}

{% block extra_js %}
<script src="{% static 'js/views/ticket_list.js' %}"></script>
{% endblock %}

Ticket Detail Template

{% extends "layouts/_layout_wrapper.html" %}

{% block title %}{{ ticket.title }} - Arctyk ITSM{% endblock %}

{% block page_content %}
<div class="ticket-detail">
    <div class="ticket-header">
        <h1>{{ ticket.title }}</h1>
        <span class="ticket-id">#{{ ticket.id }}</span>
    </div>

    <div class="ticket-meta">
        <div class="meta-item">
            <label>Status:</label>
            <span class="badge badge-{{ ticket.status }}">
                {{ ticket.get_status_display }}
            </span>
        </div>
        <div class="meta-item">
            <label>Assignee:</label>
            <span>{{ ticket.assignee|default:"Unassigned" }}</span>
        </div>
        <div class="meta-item">
            <label>Created:</label>
            <span>{{ ticket.created_at|date:"Y-m-d H:i" }}</span>
        </div>
    </div>

    <div class="ticket-description">
        <h2>Description</h2>
        {{ ticket.description|safe }}
    </div>

    <div class="ticket-actions">
        <a href="{% url 'ticket_edit' ticket.id %}" class="btn btn-secondary">
            Edit
        </a>
        {% if ticket.can_close %}
        <form method="post" action="{% url 'ticket_close' ticket.id %}" style="display: inline;">
            {% csrf_token %}
            <button type="submit" class="btn btn-primary">Close Ticket</button>
        </form>
        {% endif %}
    </div>
</div>
{% endblock %}

Best Practices

1. DRY (Don't Repeat Yourself)

  • Use template inheritance for common layouts
  • Extract reusable components into partials
  • Create custom template tags for complex logic

2. Keep Templates Simple

  • Minimize logic in templates
  • Move complex operations to views or template tags
  • Use context processors for common data

3. Organize Templates by App

templates/
├── base.html
├── layouts/
│   └── _layout_wrapper.html
├── partials/
│   ├── sidebar.html
│   ├── topbar.html
│   └── breadcrumbs.html
└── tickets/
    ├── ticket_list.html
    ├── ticket_detail.html
    └── ticket_form.html

4. Use Meaningful Block Names

{% block page_title %}{% endblock %}        # ✅ Clear purpose
{% block content_main %}{% endblock %}      # ✅ Descriptive
{% block stuff %}{% endblock %}             # ❌ Too vague

5. Always Use {% load static %}

{% load static %}  {# At top of template #}
<link rel="stylesheet" href="{% static 'css/main.css' %}">

6. Escape User Input

{{ user_input }}              {# ✅ Auto-escaped #}
{{ user_input|safe }}         {# ⚠️ Only if you trust the content #}
{{ user_input|escape }}       {# ✅ Explicit escaping #}


Resources