Skip to main content
Sortable inlines allow users to manually order related objects using drag-and-drop functionality, providing an intuitive way to manage the display order of related items.

Overview

When you have related objects that need to maintain a specific order (like slides in a slideshow, steps in a tutorial, or menu items), sortable inlines provide a user-friendly way to manage that order directly in the Django admin interface.
Drag-and-drop sortable inline

Basic Setup

To make an inline sortable, you need:
  1. An ordering field in your model (typically an IntegerField or PositiveIntegerField)
  2. Set the ordering_field attribute on your inline class
models.py
from django.db import models

class Gallery(models.Model):
    name = models.CharField(max_length=100)
    
class Image(models.Model):
    gallery = models.ForeignKey(Gallery, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    file = models.ImageField(upload_to='gallery/')
    order = models.PositiveIntegerField(default=0)  # Ordering field
    
    class Meta:
        ordering = ['order']  # Default ordering by order field
admin.py
from django.contrib import admin
from unfold.admin import ModelAdmin, TabularInline
from .models import Gallery, Image

class ImageInline(TabularInline):
    model = Image
    extra = 0
    ordering_field = 'order'  # Enable drag-and-drop sorting
    fields = ['title', 'file', 'order']

@admin.register(Gallery)
class GalleryAdmin(ModelAdmin):
    inlines = [ImageInline]
The ordering_field tells Unfold which field to update when items are reordered via drag-and-drop.

Hiding the Ordering Field

You can hide the ordering field from the inline display while still maintaining sortable functionality:
admin.py
class ImageInline(TabularInline):
    model = Image
    extra = 0
    ordering_field = 'order'
    hide_ordering_field = True  # Hide the order field column
    fields = ['title', 'file']  # Don't include 'order' in fields
Setting hide_ordering_field = True provides a cleaner interface by hiding the numeric order field while keeping the drag-and-drop functionality.

Stacked Inline Sorting

Sortable functionality works with both tabular and stacked inlines:
admin.py
from unfold.admin import StackedInline

class ChapterInline(StackedInline):
    model = Chapter
    extra = 0
    ordering_field = 'position'
    hide_ordering_field = True
    
    fieldsets = [
        ('Chapter Information', {
            'fields': ['title', 'content']
        }),
        ('Settings', {
            'fields': ['is_published', 'publish_date']
        })
    ]

@admin.register(Book)
class BookAdmin(ModelAdmin):
    inlines = [ChapterInline]

Model-Level Ordering

You can also enable ordering at the ModelAdmin level for the changelist view:
admin.py
from unfold.admin import ModelAdmin

@admin.register(MenuItem)
class MenuItemAdmin(ModelAdmin):
    list_display = ['name', 'parent', 'order']
    list_editable = ['order']  # Allow inline editing
    ordering_field = 'order'  # Enable drag-and-drop in changelist
    hide_ordering_field = False  # Show order column
When ordering_field is set on ModelAdmin, the field is automatically added to list_display and list_editable if not already present.

Automatic Order Assignment

Implement automatic order assignment for new objects:
models.py
from django.db import models

class PlaylistItem(models.Model):
    playlist = models.ForeignKey('Playlist', on_delete=models.CASCADE)
    song = models.ForeignKey('Song', on_delete=models.CASCADE)
    order = models.PositiveIntegerField(default=0)
    
    def save(self, *args, **kwargs):
        if not self.order:
            # Set order to max + 1
            max_order = PlaylistItem.objects.filter(
                playlist=self.playlist
            ).aggregate(models.Max('order'))['order__max']
            self.order = (max_order or 0) + 1
        super().save(*args, **kwargs)
    
    class Meta:
        ordering = ['order']
        unique_together = ['playlist', 'order']
admin.py
class PlaylistItemInline(TabularInline):
    model = PlaylistItem
    extra = 1
    ordering_field = 'order'
    hide_ordering_field = True
    fields = ['song']

Combining with Tabs

Sortable inlines work seamlessly with inline tabs:
admin.py
class ImageInline(TabularInline):
    model = Image
    extra = 0
    tab = True  # Display in a tab
    ordering_field = 'order'
    hide_ordering_field = True
    fields = ['title', 'file', 'caption']

class VideoInline(TabularInline):
    model = Video
    extra = 0
    tab = True
    ordering_field = 'position'
    hide_ordering_field = True
    fields = ['title', 'url']

@admin.register(MediaGallery)
class MediaGalleryAdmin(ModelAdmin):
    inlines = [ImageInline, VideoInline]
Learn more about inline tabs in Inline Tabs.

Combining with Pagination

Sortable inlines can be used with pagination, though sorting is limited to items on the current page:
admin.py
class ImageInline(TabularInline):
    model = Image
    extra = 0
    ordering_field = 'order'
    hide_ordering_field = True
    per_page = 20  # Enable pagination
    fields = ['title', 'file']
When using pagination with sortable inlines, users can only reorder items within the current page. Consider the UX implications when enabling both features.

Custom Ordering Logic

Implement custom ordering logic in your model:
models.py
class Task(models.Model):
    project = models.ForeignKey('Project', on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    order = models.PositiveIntegerField(default=0)
    
    class Meta:
        ordering = ['order', '-created_at']  # Secondary sort by creation date
        
    def move_up(self):
        """Move task up in the order"""
        previous = Task.objects.filter(
            project=self.project,
            order__lt=self.order
        ).order_by('-order').first()
        
        if previous:
            self.order, previous.order = previous.order, self.order
            self.save()
            previous.save()
    
    def move_down(self):
        """Move task down in the order"""
        next_task = Task.objects.filter(
            project=self.project,
            order__gt=self.order
        ).order_by('order').first()
        
        if next_task:
            self.order, next_task.order = next_task.order, self.order
            self.save()
            next_task.save()

Best Practices

Always use PositiveIntegerField or PositiveSmallIntegerField for order fields:
order = models.PositiveIntegerField(default=0, db_index=True)
Add a database index for better performance on large datasets.
Always specify ordering in your model’s Meta class:
class Meta:
    ordering = ['order', 'created_at']
This ensures consistent ordering throughout your application.
It’s okay to have gaps in order values. Focus on relative ordering, not sequential numbers:
# Acceptable: [10, 20, 30, 40]
# Also fine: [1, 2, 5, 100]
# What matters is the order, not the specific values
Order items within a specific scope (parent object):
class Meta:
    ordering = ['order']
    unique_together = [['parent', 'order']]  # Unique within parent

JavaScript Integration

The sortable functionality uses JavaScript to handle drag-and-drop. The order field is automatically updated when items are reordered:
admin.py
class ImageInline(TabularInline):
    model = Image
    ordering_field = 'order'
    
    class Media:
        js = [
            # Unfold automatically includes the necessary JavaScript
            # No additional JS needed for basic sorting
        ]
Unfold handles all the JavaScript for drag-and-drop functionality automatically. You don’t need to include any additional scripts.

Technical Details

ordering_field
str
The name of the model field that stores the order value. This field must be numeric (IntegerField or similar).
hide_ordering_field
bool
default:"False"
When True, hides the ordering field column from the inline display while maintaining drag-and-drop functionality.
The ordering field is processed in:
  • Changelist: unfold.templatetags.unfold_list.result_headers and result_list
  • Inlines: unfold.templatetags.unfold.inline_fieldsets

Inlines Overview

Learn about inline basics

Inline Tabs

Organize inlines with tabs

Paginated Inlines

Add pagination to inlines

Nonrelated Inlines

Work with non-related objects

Build docs developers (and LLMs) love