Skip to content

Template Partials Guide

This guide documents all reusable template partials (fragments) used throughout Arctyk ITSM.


What Are Partials?

Partials are small, reusable template fragments that encapsulate specific UI components. They help maintain the DRY (Don't Repeat Yourself) principle and ensure consistency across the application.

Benefits of Using Partials

Consistency: Same component looks and behaves identically everywhere
Maintainability: Update in one place, changes reflect everywhere
Readability: Keep parent templates clean and focused
Reusability: Use the same component in multiple pages
Testing: Easier to test isolated components


How to Include Partials

Use the {% include %} template tag:

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

Passing Context to Partials

{# Pass specific variables #}
{% include "partials/breadcrumbs.html" with page_title="Tickets" %}

{# Pass multiple variables #}
{% include "partials/pagination.html" with page_obj=tickets page_name="ticket_list" %}

{# Use only passed context (isolate from parent) #}
{% include "partials/card.html" with title="Summary" only %}

Available Partials

Layout Components

Data Display Components

Filter Components


Sidebar.html

Location: templates/partials/sidebar.html
Purpose: Main navigation sidebar with app sections and menu items

Usage

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

Features

  • Branding: Arctyk logo and site name
  • Navigation sections: Tickets, Projects, Assets, Reports
  • Active state highlighting: Current page is highlighted
  • Collapsible sections: Expandable menu groups
  • User preferences: Density settings (compact/comfortable)

Code Example

<aside class="sidebar" id="sidebar">
    {# Logo and branding #}
    <div class="sidebar-header">
        <img src="{% static 'images/arctyk-logo.svg' %}" alt="Arctyk ITSM" class="sidebar-logo">
        <h1 class="sidebar-title">Arctyk ITSM</h1>
    </div>

    {# Navigation menu #}
    <nav class="sidebar-nav">
        {# Tickets section #}
        <div class="nav-section">
            <h2 class="nav-section-title">Tickets</h2>
            <ul class="nav-list">
                <li class="nav-item {% if request.resolver_match.url_name == 'ticket_list' %}active{% endif %}">
                    <a href="{% url 'ticket_list' %}" class="nav-link">
                        <i class="icon icon-list"></i>
                        <span>All Tickets</span>
                    </a>
                </li>
                <li class="nav-item {% if request.resolver_match.url_name == 'ticket_create' %}active{% endif %}">
                    <a href="{% url 'ticket_create' %}" class="nav-link">
                        <i class="icon icon-plus"></i>
                        <span>Create Ticket</span>
                    </a>
                </li>
            </ul>
        </div>

        {# Projects section #}
        <div class="nav-section">
            <h2 class="nav-section-title">Projects</h2>
            <ul class="nav-list">
                <li class="nav-item {% if request.resolver_match.url_name == 'project_list' %}active{% endif %}">
                    <a href="{% url 'project_list' %}" class="nav-link">
                        <i class="icon icon-folder"></i>
                        <span>All Projects</span>
                    </a>
                </li>
            </ul>
        </div>

        {# Assets section #}
        <div class="nav-section">
            <h2 class="nav-section-title">Assets</h2>
            <ul class="nav-list">
                <li class="nav-item {% if request.resolver_match.url_name == 'asset_list' %}active{% endif %}">
                    <a href="{% url 'asset_list' %}" class="nav-link">
                        <i class="icon icon-server"></i>
                        <span>Inventory</span>
                    </a>
                </li>
            </ul>
        </div>
    </nav>

    {# User section #}
    <div class="sidebar-footer">
        <div class="user-info">
            <span class="user-avatar">{{ user.username|slice:":1"|upper }}</span>
            <span class="user-name">{{ user.username }}</span>
        </div>
    </div>
</aside>

Styling

Styled in static/scss/components/_sidebar.scss: - Fixed left position - Scrollable content - Hover states - Active link highlighting

JavaScript

Interactive features in static/js/components/sidebar.js: - Toggle sidebar collapse - Persist user density preference - Highlight active section


Topbar.html

Location: templates/partials/topbar.html
Purpose: Top navigation bar with global actions and user menu

Usage

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

Features

  • Quick create: Fast ticket creation button
  • Search: Global search across tickets, projects, assets
  • Notifications: Notification bell with count badge
  • User menu: Account settings, profile, logout

Code Example

<header class="topbar" id="topbar">
    <div class="topbar-content">
        {# Left side: Create button #}
        <div class="topbar-left">
            <button class="btn btn-primary" data-action="create-ticket">
                <i class="icon icon-plus"></i>
                <span>Create</span>
            </button>
        </div>

        {# Center: Search #}
        <div class="topbar-center">
            <form class="search-form" method="get" action="{% url 'search' %}">
                <input 
                    type="search" 
                    name="q" 
                    placeholder="Search tickets, projects, assets..." 
                    class="search-input"
                    autocomplete="off"
                >
                <button type="submit" class="search-submit">
                    <i class="icon icon-search"></i>
                </button>
            </form>
        </div>

        {# Right side: Notifications and user menu #}
        <div class="topbar-right">
            {# Notifications #}
            <div class="topbar-item">
                <button class="notifications-trigger" data-toggle="dropdown">
                    <i class="icon icon-bell"></i>
                    {% if unread_notifications_count > 0 %}
                    <span class="badge badge-count">{{ unread_notifications_count }}</span>
                    {% endif %}
                </button>
                <div class="dropdown-menu notifications-menu">
                    <h3>Notifications</h3>
                    {% for notification in recent_notifications %}
                    <div class="notification-item">
                        <p>{{ notification.message }}</p>
                        <span class="notification-time">{{ notification.created_at|timesince }} ago</span>
                    </div>
                    {% empty %}
                    <p class="empty-state">No new notifications</p>
                    {% endfor %}
                </div>
            </div>

            {# User menu #}
            <div class="topbar-item">
                <button class="user-menu-trigger" data-toggle="dropdown">
                    <span class="user-avatar">{{ user.username|slice:":1"|upper }}</span>
                    <span class="user-name">{{ user.username }}</span>
                    <i class="icon icon-chevron-down"></i>
                </button>
                <div class="dropdown-menu user-menu">
                    <a href="{% url 'profile' %}" class="dropdown-item">
                        <i class="icon icon-user"></i> Profile
                    </a>
                    <a href="{% url 'settings' %}" class="dropdown-item">
                        <i class="icon icon-settings"></i> Settings
                    </a>
                    <hr class="dropdown-divider">
                    <a href="{% url 'logout' %}" class="dropdown-item">
                        <i class="icon icon-logout"></i> Logout
                    </a>
                </div>
            </div>
        </div>
    </div>
</header>

Styling

Styled in static/scss/components/_topbar.scss: - Fixed top position - Flexbox layout - Dropdown menus - Responsive breakpoints


Location: templates/partials/breadcrumbs.html
Purpose: Hierarchical navigation showing current page location

Usage

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

Features

  • Hierarchical navigation: Shows path to current page
  • Clickable ancestors: Navigate back to parent pages
  • Current page indicator: Last item is not clickable
  • Automatic generation: Built from URL structure

Code Example

<nav class="breadcrumbs" aria-label="Breadcrumb">
    <ol class="breadcrumb-list">
        <li class="breadcrumb-item">
            <a href="{% url 'dashboard' %}">
                <i class="icon icon-home"></i>
                Home
            </a>
        </li>

        {% block breadcrumbs %}
        {# Override this block in child templates #}
        {% endblock %}
    </ol>
</nav>

Usage in Page Template

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

{% block breadcrumbs %}
<li class="breadcrumb-item">
    <a href="{% url 'ticket_list' %}">Tickets</a>
</li>
<li class="breadcrumb-item active" aria-current="page">
    {{ ticket.title }}
</li>
{% endblock %}

Result

Home > Tickets > Fix login bug

Styling

Styled in static/scss/components/_breadcrumbs.scss: - Separator arrows - Hover states - Active item styling


Pagination.html

Location: templates/partials/pagination.html
Purpose: Pagination controls for paginated lists

Usage

{% include "partials/pagination.html" with page_obj=tickets %}

Features

  • Page numbers: Direct page navigation
  • Previous/Next: Navigate sequentially
  • First/Last: Jump to endpoints
  • Page info: "Showing X to Y of Z results"
  • Responsive: Adapts to screen size

Code Example

{% if page_obj.has_other_pages %}
<nav class="pagination" aria-label="Pagination">
    <div class="pagination-info">
        Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ page_obj.paginator.count }} results
    </div>

    <ul class="pagination-list">
        {# Previous button #}
        {% if page_obj.has_previous %}
        <li class="pagination-item">
            <a href="?page={{ page_obj.previous_page_number }}" class="pagination-link">
                <i class="icon icon-chevron-left"></i>
                Previous
            </a>
        </li>
        {% else %}
        <li class="pagination-item disabled">
            <span class="pagination-link">
                <i class="icon icon-chevron-left"></i>
                Previous
            </span>
        </li>
        {% endif %}

        {# Page numbers #}
        {% for num in page_obj.paginator.page_range %}
            {% if page_obj.number == num %}
            <li class="pagination-item active">
                <span class="pagination-link">{{ num }}</span>
            </li>
            {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
            <li class="pagination-item">
                <a href="?page={{ num }}" class="pagination-link">{{ num }}</a>
            </li>
            {% endif %}
        {% endfor %}

        {# Next button #}
        {% if page_obj.has_next %}
        <li class="pagination-item">
            <a href="?page={{ page_obj.next_page_number }}" class="pagination-link">
                Next
                <i class="icon icon-chevron-right"></i>
            </a>
        </li>
        {% else %}
        <li class="pagination-item disabled">
            <span class="pagination-link">
                Next
                <i class="icon icon-chevron-right"></i>
            </span>
        </li>
        {% endif %}
    </ul>
</nav>
{% endif %}

Required Context

  • page_obj: Django Paginator page object

View Setup

from django.core.paginator import Paginator

def ticket_list(request):
    tickets = Ticket.objects.all()
    paginator = Paginator(tickets, 25)  # 25 per page
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)

    return render(request, 'tickets/ticket_list.html', {
        'tickets': page_obj,
        'page_obj': page_obj,
    })

Sort_header.html

Location: templates/partials/sort_header.html
Purpose: Sortable table column headers

Usage

<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="created_at" label="Created" %}
    </tr>
</thead>

Features

  • Click to sort: Toggle ascending/descending
  • Visual indicators: Arrows show sort direction
  • URL preservation: Maintains other query parameters
  • Accessible: ARIA labels for screen readers

Code Example

<th class="sortable {% if request.GET.sort == field %}sorted{% endif %}">
    <a href="?sort={{ field }}{% if request.GET.sort == field and request.GET.order != 'desc' %}&order=desc{% endif %}" 
       class="sort-link">
        {{ label }}
        {% if request.GET.sort == field %}
            {% if request.GET.order == 'desc' %}
                <i class="icon icon-arrow-down"></i>
            {% else %}
                <i class="icon icon-arrow-up"></i>
            {% endif %}
        {% else %}
            <i class="icon icon-arrow-updown"></i>
        {% endif %}
    </a>
</th>

Required Context

  • field: Database field name to sort by
  • label: Display label for the column

View Implementation

def ticket_list(request):
    tickets = Ticket.objects.all()

    # Handle sorting
    sort_field = request.GET.get('sort', 'created_at')
    sort_order = request.GET.get('order', 'asc')

    if sort_order == 'desc':
        sort_field = '-' + sort_field

    tickets = tickets.order_by(sort_field)

    return render(request, 'tickets/ticket_list.html', {
        'tickets': tickets,
    })

Log_filters.html

Location: templates/partials/log_filters.html
Purpose: Filters for change logs and audit trails

Usage

{% include "partials/log_filters.html" %}

Features

  • Date range: Filter by date
  • User filter: Filter by who made changes
  • Action filter: Filter by change type (create, update, delete)
  • Reset filters: Clear all filters

Code Example

<div class="log-filters">
    <form method="get" class="filter-form">
        {# Date range #}
        <div class="filter-group">
            <label for="date_from">From:</label>
            <input type="date" id="date_from" name="date_from" value="{{ request.GET.date_from }}">
        </div>

        <div class="filter-group">
            <label for="date_to">To:</label>
            <input type="date" id="date_to" name="date_to" value="{{ request.GET.date_to }}">
        </div>

        {# User filter #}
        <div class="filter-group">
            <label for="user">User:</label>
            <select id="user" name="user">
                <option value="">All Users</option>
                {% for user in users %}
                <option value="{{ user.id }}" {% if request.GET.user == user.id|stringformat:"s" %}selected{% endif %}>
                    {{ user.username }}
                </option>
                {% endfor %}
            </select>
        </div>

        {# Action filter #}
        <div class="filter-group">
            <label for="action">Action:</label>
            <select id="action" name="action">
                <option value="">All Actions</option>
                <option value="create" {% if request.GET.action == 'create' %}selected{% endif %}>Created</option>
                <option value="update" {% if request.GET.action == 'update' %}selected{% endif %}>Updated</option>
                <option value="delete" {% if request.GET.action == 'delete' %}selected{% endif %}>Deleted</option>
            </select>
        </div>

        {# Apply and reset #}
        <div class="filter-actions">
            <button type="submit" class="btn btn-primary">Apply Filters</button>
            <a href="{% url 'changelog' %}" class="btn btn-secondary">Reset</a>
        </div>
    </form>
</div>

_layout_wrapper.html

Location: templates/layouts/_layout_wrapper.html
Purpose: Standard page layout with sidebar and topbar

Usage

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

{% block page_content %}
    <h1>My Page Content</h1>
{% endblock %}

Features

  • Consistent layout: All authenticated pages use this
  • Includes partials: Sidebar, topbar, breadcrumbs
  • Responsive: Adapts to mobile/tablet/desktop
  • Content block: Override with page-specific content

Code Example

{% extends "base.html" %}

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

    {# Main content area #}
    <div class="main-content">
        {# Top navigation #}
        {% include "partials/topbar.html" %}

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

            {# Main page content (override this) #}
            <main class="page-content">
                {% block page_content %}{% endblock %}
            </main>
        </div>
    </div>
</div>
{% endblock body %}

Creating New Partials

When to Create a Partial

Create a partial when:

✅ Component is used in multiple places
✅ Component has complex structure
✅ Component needs to be testable in isolation
✅ Helps keep parent templates readable

Naming Conventions

  • Use descriptive names: sidebar.html, not nav.html
  • Prefix layout partials with underscore: _layout_wrapper.html
  • Group by category: partials/, layouts/, components/

Template Structure

{# partials/my_component.html #}
{# Description of what this component does #}

<div class="my-component">
    {# Component markup #}
    {{ content }}
</div>

Best Practices

  1. Document the partial: Add comments explaining purpose and required context
  2. Keep it focused: One responsibility per partial
  3. Make it flexible: Accept context variables for customization
  4. Style consistently: Follow existing patterns
  5. Test thoroughly: Ensure it works in all use cases


Summary

Partials are essential for maintaining a clean, DRY codebase in Arctyk ITSM. They enable:

  • Code reuse across multiple pages
  • Consistent UI throughout the application
  • Easier maintenance with single source of truth
  • Better organization of template code

All partials follow Django's {% include %} pattern and can accept context variables for flexibility.