Skip to content

Ordering Guide - djadmin-filters

Plugin: djadmin-filters Version: 1.0.0 Last Updated: October 31, 2025

Overview

The djadmin-filters plugin provides sortable column headers with visual indicators (↕️ ↑ ↓). Ordering is configured directly on Column objects and supports custom labels, multi-field ordering, and related field sorting.

Prerequisites

Basic Ordering

Enable Ordering

Use order=True to make a column sortable:

from djadmin import Column

Column('name', order=True)
# Clicking header cycles: neutral (↕) → ascending (↑) → descending (↓) → neutral

Disable Ordering

Explicitly disable ordering for a column:

Column('description', order=False)
# Header has no sort icon

Simple Example

from djadmin import ModelAdmin, register, Column

@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        Column('name', order=True),        # Sortable
        Column('price', order=True),       # Sortable
        Column('description', order=False), # Not sortable
    ]

Sort Cycle

When a column is sortable, clicking the header cycles through three states:

  1. Neutral (↕) - No sorting applied, default ordering
  2. Ascending (↑) - Sort A-Z, 0-9, oldest-newest
  3. Descending (↓) - Sort Z-A, 9-0, newest-oldest

Clicking again returns to neutral (clears sorting).

Custom Labels

Provide custom labels for ascending and descending states:

from djadmin.dataclasses import Order

Column('price',
       order=Order(
           label='Price (low to high)',
           descending_label='Price (high to low)'
       ))

These labels appear in tooltips or accessibility text, helping users understand what clicking will do.

Multi-Field Ordering

Order by multiple fields using the fields parameter:

Basic Multi-Field

Column('full_name',
       order=Order(fields=['last_name', 'first_name']))
# Clicking "Full Name" orders by last name, then first name
Column('author',
       order=Order(fields=['author__last_name', 'author__first_name']))
# Orders by author's last name, then first name

Complex Ordering

Column('availability',
       order=Order(fields=['in_stock', '-stock_count', 'price']))
# Orders by: in_stock (asc), stock_count (desc), price (asc)

Sort by fields on related models using Django's __ syntax:

Foreign Key

Column('category',
       order=Order(fields=['category__name']))
# Sort products by their category name

Reverse Foreign Key

# In AuthorAdmin
Column('book_count',
       order=Order(fields=['books__count']))
# Sort authors by number of books

Through Multiple Relations

Column('publisher',
       order=Order(fields=['category__publisher__name']))
# Sort through multiple relationships

Computed Field Ordering

Order by annotated/computed fields:

from django.db.models import Count, F

@register(Author)
class AuthorAdmin(ModelAdmin):
    list_display = [
        Column('name', order=True),
        Column('book_count',
               order=Order(fields=['book_count'])),
    ]

    def get_queryset(self, request):
        return super().get_queryset(request).annotate(
            book_count=Count('books')
        )

URL Parameters

Ordering uses the ordering query parameter:

Ascending

?ordering=price

Descending

?ordering=-price

Multiple Fields

?ordering=category,-price
# Order by category (asc), then price (desc)

Combined with Filters

?category=1&ordering=-price&search=laptop
# Filter by category, search, and sort by price descending

Order Indicators

Visual Icons

The plugin displays sort icons in column headers:

  • - Neutral (sortable, not currently sorting)
  • - Ascending (currently sorting A-Z)
  • - Descending (currently sorting Z-A)

Icon Customization

See UI Customization Guide for custom icon templates.

Examples

Basic Sortable Columns

@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        Column('name', order=True),
        Column('price', order=True),
        Column('stock', order=True),
        Column('description', order=False),  # Not sortable
    ]

Custom Sort Labels

@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        Column('name',
               order=Order(
                   label='Name (A-Z)',
                   descending_label='Name (Z-A)'
               )),

        Column('price',
               order=Order(
                   label='Price (low to high)',
                   descending_label='Price (high to low)'
               )),

        Column('created_at',
               order=Order(
                   label='Oldest first',
                   descending_label='Newest first'
               )),
    ]

Multi-Field Ordering

@register(Person)
class PersonAdmin(ModelAdmin):
    list_display = [
        Column('full_name',
               order=Order(fields=['last_name', 'first_name'])),

        Column('address',
               order=Order(fields=['city', 'state', 'zip_code'])),
    ]
@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        Column('name', order=True),

        Column('category',
               order=Order(fields=['category__name'])),

        Column('manufacturer',
               order=Order(fields=['manufacturer__company_name'])),
    ]

    def get_queryset(self, request):
        return super().get_queryset(request).select_related(
            'category', 'manufacturer'
        )

Computed Field Ordering

from django.db.models import Count, Avg

@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        Column('name', order=True),

        Column('review_count',
               label='Reviews',
               order=Order(fields=['review_count'])),

        Column('avg_rating',
               label='Avg. Rating',
               order=Order(fields=['avg_rating'])),
    ]

    def get_queryset(self, request):
        return super().get_queryset(request).annotate(
            review_count=Count('reviews'),
            avg_rating=Avg('reviews__rating')
        )

Default Ordering

@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        Column('name', order=True),
        Column('price', order=True),
        Column('created_at', order=True),
    ]

    # Default ordering when no ?ordering parameter
    ordering = ['-created_at', 'name']

Complete Example

from django.db.models import Count, Avg, F
from djadmin import ModelAdmin, register, Column
from djadmin.dataclasses import Order
from myapp.models import Product

@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        # Simple ordering
        Column('name',
               order=Order(
                   label='Name (A-Z)',
                   descending_label='Name (Z-A)'
               )),

        # Related field ordering
        Column('category',
               order=Order(fields=['category__name'])),

        # Price ordering with custom labels
        Column('price',
               order=Order(
                   label='Price (low to high)',
                   descending_label='Price (high to low)'
               )),

        # Computed field ordering
        Column('review_count',
               label='Reviews',
               order=Order(fields=['review_count'])),

        # Multi-field ordering
        Column('full_name',
               order=Order(fields=['last_name', 'first_name'])),

        # Not sortable
        Column('description', order=False),
    ]

    # Default ordering
    ordering = ['-created_at']

    def get_queryset(self, request):
        return super().get_queryset(request).select_related(
            'category'
        ).annotate(
            review_count=Count('reviews'),
            avg_rating=Avg('reviews__rating')
        )

Best Practices

Use Natural Sort Orders

# Good - intuitive sorting
Column('price',
       order=Order(
           label='Price (low to high)',
           descending_label='Price (high to high)'
       ))

# Avoid - confusing direction
Column('price', order=True)  # Unclear what "ascending" means
# Good - uses select_related to avoid N+1 queries
@register(Product)
class ProductAdmin(ModelAdmin):
    list_display = [
        Column('category', order=Order(fields=['category__name'])),
    ]

    def get_queryset(self, request):
        return super().get_queryset(request).select_related('category')

Annotate Before Ordering Computed Fields

# Good - annotate in get_queryset
def get_queryset(self, request):
    return super().get_queryset(request).annotate(
        review_count=Count('reviews')
    )

list_display = [
    Column('review_count', order=Order(fields=['review_count'])),
]

# Avoid - ordering by unannotated field (will error)

Provide Clear Labels for Multi-Field Ordering

# Good - label explains the sort behavior
Column('full_name',
       label='Full Name',
       order=Order(
           fields=['last_name', 'first_name'],
           label='Sort by last name, then first name'
       ))

# Avoid - unclear what fields are used
Column('full_name', order=Order(fields=['last_name', 'first_name']))

Troubleshooting

Ordering Not Working

Problem: Column header shows sort icon but clicking doesn't sort

Solutions: 1. Check order=True or order=Order(...) is set 2. Verify field exists on model 3. Check ordering URL parameter appears after clicking 4. Inspect queryset with Django Debug Toolbar

N+1 Query Issues

Problem: Sorting by related field causes many database queries

Solutions: 1. Use select_related() for ForeignKey fields:

def get_queryset(self, request):
    return super().get_queryset(request).select_related('category')

  1. Use prefetch_related() for ManyToMany fields:
    def get_queryset(self, request):
        return super().get_queryset(request).prefetch_related('tags')
    

Computed Field Not Sortable

Problem: Ordering by computed field causes error

Solutions: 1. Annotate the field in get_queryset():

def get_queryset(self, request):
    return super().get_queryset(request).annotate(
        review_count=Count('reviews')
    )

  1. Match the annotation name to the order field name:
    Column('review_count', order=Order(fields=['review_count']))
    

Sort Icon Not Showing

Problem: Column should be sortable but no icon appears

Solutions: 1. Verify order=True is set 2. Check CSS is loading correctly 3. Clear browser cache 4. Inspect HTML for sort icon element

See Also