Skip to content

Column Header Icons

Added in: Milestone 2 (Phase 2)

Column header icons provide a flexible system for adding interactive elements to table column headers. While commonly used for sorting indicators, the system is extensible - any plugin can add custom icons for any purpose.

Overview

The column header icon system allows plugins to: - Add clickable icons to column headers - Provide dynamic behavior based on current state - Control icon display with conditional logic - Order multiple icons per column

ColumnHeaderIcon Dataclass

The ColumnHeaderIcon dataclass defines a column header icon:

from dataclasses import dataclass
from typing import Optional, Callable

@dataclass
class ColumnHeaderIcon:
    """Configuration for a column header icon."""

    icon_template: Callable[[object, str], str]  # Icon template path
    url: Callable[[object, str], str]  # Icon URL
    title: Callable[[object, str], str]  # Tooltip text
    css_class: str = ''  # CSS classes for icon
    order: int = 100  # Display order
    condition: Optional[Callable[[object, str], bool]] = None  # Display condition
    identifier: Optional[str] = None  # Unique ID

Callable Attributes

All main attributes are callables that receive (view, column_name):

icon_template(view, column_name) → str - Returns path to icon template - Example: 'djadmin/icons/sort-up.html'

url(view, column_name) → str - Returns URL for icon link/button - Example: '/djadmin/products/?ordering=name'

title(view, column_name) → str - Returns tooltip text - Example: 'Sort by name (ascending)'

condition(view, column_name) → bool (optional) - Returns whether icon should display - Example: Check if column is orderable

Other Attributes

css_class (optional) - CSS classes applied to icon link - Example: 'sort-icon active'

order (optional, default: 100) - Controls icon display order when multiple icons exist - Lower values appear first

identifier (optional) - Unique string identifier for the icon - Useful for debugging

Plugin Hook

Plugins register column header icons via the djadmin_get_column_header_icons hook:

# myapp/djadmin_hooks.py
from djadmin.plugins import hookimpl
from djadmin.dataclasses import ColumnHeaderIcon
from djadmin.actions.list_view import ListAction

@hookimpl
def djadmin_get_column_header_icons(action):
    """Register column header icons for ListView."""

    def get_icon_template(view, column_name):
        # Dynamic icon selection based on state
        if is_active(view, column_name):
            return 'myapp/icons/active.html'
        return 'myapp/icons/inactive.html'

    def get_icon_url(view, column_name):
        # Generate URL for icon action
        return view.model_admin.get_action_url(column_name)

    def get_icon_title(view, column_name):
        return f"Toggle {column_name}"

    def should_display(view, column_name):
        # Only show for certain columns
        return hasattr(view.model_admin, 'toggleable_columns') and \
               column_name in view.model_admin.toggleable_columns

    return {
        ListAction: [
            ColumnHeaderIcon(
                icon_template=get_icon_template,
                url=get_icon_url,
                title=get_icon_title,
                condition=should_display,
                order=50,
                identifier='toggle_icon',
            )
        ]
    }

Built-in Icons: Sort Indicators (djadmin-filters)

The djadmin-filters plugin uses column header icons for sorting:

# djadmin_filters/djadmin_hooks.py (simplified)

def get_sort_icon_template(view, column_name):
    """Determine which icon to show based on current sort state."""
    current_ordering = view.request.GET.get('ordering', '')

    if current_ordering == column_name:
        return 'djadmin/icons/sort-up.html'  # Ascending
    elif current_ordering == f'-{column_name}':
        return 'djadmin/icons/sort-down.html'  # Descending
    else:
        return 'djadmin/icons/sort.html'  # Neutral

def get_sort_url(view, column_name):
    """Generate URL that toggles sort state."""
    from django.template import Template, Context
    current_ordering = view.request.GET.get('ordering', '')

    # Toggle logic: none → asc → desc → none
    if current_ordering == column_name:
        new_ordering = f'-{column_name}'  # Switch to descending
    elif current_ordering == f'-{column_name}':
        new_ordering = None  # Clear ordering
    else:
        new_ordering = column_name  # Set ascending

    # Use querystring tag to preserve other params
    template = Template("{% load djadmin_tags %}{% querystring ordering=new_ordering %}")
    return template.render(Context({
        'request': view.request,
        'new_ordering': new_ordering,
    }))

def get_sort_title(view, column_name):
    """Generate tooltip text."""
    current_ordering = view.request.GET.get('ordering', '')

    if current_ordering == column_name:
        return f"Sort by {column_name} (descending)"
    elif current_ordering == f'-{column_name}':
        return f"Clear sorting"
    else:
        return f"Sort by {column_name}"

def should_display_sort_icon(view, column_name):
    """Only show for columns with order=True."""
    column = view.get_column_by_name(column_name)
    return column and column.order and column.order.enabled

Template Rendering

Icons are rendered in column headers via the render_column_header_icons template tag:

{# djadmin/templates/djadmin/{app}/{model}_list.html or actions/list.html #}
{% load djadmin_tags %}

<thead>
    <tr>
        {% for column in columns %}
            <th>
                {{ column.label }}
                {% render_column_header_icons column.field %}
            </th>
        {% endfor %}
    </tr>
</thead>

The template tag renders the icons using this template:

{# djadmin/templates/djadmin/includes/_column_header_icons.html #}
{% if icons %}
    <span class="column-header-icons">
        {% for icon in icons %}
            <a href="{{ icon.url }}"
               title="{{ icon.title }}"
               class="column-header-icon {{ icon.css_class }}">
                {% include icon.icon_template %}
            </a>
        {% endfor %}
    </span>
{% endif %}

Styling

Column header icons use the theme's styles:

/* Icon container */
.column-header-icons {
    display: inline-flex;
    gap: 0.25rem;
    margin-left: 0.5rem;
    align-items: center;
}

/* Individual icon link */
.column-header-icon {
    display: inline-flex;
    align-items: center;
    color: #6b7280;
    transition: color 0.2s;
}

.column-header-icon:hover {
    color: #111827;
}

/* Dark mode */
.dark .column-header-icon {
    color: #9ca3af;
}

.dark .column-header-icon:hover {
    color: #f9fafb;
}

/* Icon SVG sizing */
.column-header-icon svg {
    width: 1rem;
    height: 1rem;
}

Example: Help Icon

Add help icons to column headers with documentation links:

# myapp/djadmin_hooks.py
from djadmin.plugins import hookimpl
from djadmin.dataclasses import ColumnHeaderIcon
from djadmin.actions.list_view import ListAction

@hookimpl
def djadmin_get_column_header_icons(action):
    """Add help icons to columns with documentation."""

    def get_help_url(view, column_name):
        # Get help URL from model admin configuration
        help_urls = getattr(view.model_admin, 'column_help_urls', {})
        return help_urls.get(column_name, '#')

    def get_help_title(view, column_name):
        return f"Help for {column_name}"

    def has_help(view, column_name):
        help_urls = getattr(view.model_admin, 'column_help_urls', {})
        return column_name in help_urls

    return {
        ListAction: [
            ColumnHeaderIcon(
                icon_template=lambda v, c: 'myapp/icons/help.html',
                url=get_help_url,
                title=get_help_title,
                condition=has_help,
                order=200,  # After sort icons
                css_class='help-icon',
                identifier='column_help',
            )
        ]
    }

Usage in ModelAdmin:

from djadmin import ModelAdmin, register

@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = ['name', 'sku', 'price']

    # Map column names to help URLs
    column_help_urls = {
        'sku': 'https://docs.example.com/sku',
        'price': 'https://docs.example.com/pricing',
    }

Icon Template Example

Icon templates should be simple SVG includes:

{# myapp/templates/myapp/icons/help.html #}
<svg xmlns="http://www.w3.org/2000/svg"
     fill="none"
     viewBox="0 0 24 24"
     stroke-width="1.5"
     stroke="currentColor">
    <path stroke-linecap="round"
          stroke-linejoin="round"
          d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" />
</svg>

See Also