$ cat /posts/advanced-django-admin-inlines-actions-and-custom-admin-views.md

Advanced Django Admin: Inlines, Actions, and Custom Admin Views

drwxr-xr-x2026-01-225 min0 views
Advanced Django Admin: Inlines, Actions, and Custom Admin Views

Advanced Django admin features extend basic administration capabilities with inline editing for related models, custom actions for bulk operations, custom views for specialized workflows, and admin filters for complex queries. Inline editing enables managing related objects within parent object's admin page streamlining workflows. Custom actions perform bulk operations on multiple selected objects. Custom admin views add specialized functionality beyond standard CRUD operations. Mastering advanced admin features transforms the admin interface into a powerful application management tool supporting complex workflows, batch operations, and specialized administrative tasks from content approval workflows to report generation and data import/export capabilities providing comprehensive administrative functionality.

Inline Editing

Inline editing allows managing related objects directly within parent object's admin page using TabularInline for compact table layouts or StackedInline for full field display. Inlines streamline editing workflows eliminating navigation between separate admin pages. Understanding inlines enables efficient management of parent-child relationships.

pythoninlines.py
# Inline Editing
from django.contrib import admin
from .models import Post, Comment, Image

# Tabular inline (table format)
class CommentInline(admin.TabularInline):
    model = Comment
    extra = 1  # Number of empty forms
    fields = ['author', 'text', 'approved']
    readonly_fields = ['created_at']

# Stacked inline (full form format)
class ImageInline(admin.StackedInline):
    model = Image
    extra = 0
    max_num = 5  # Maximum number of inlines
    min_num = 1  # Minimum number required
    fields = ['image', 'caption', 'order']

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    inlines = [CommentInline, ImageInline]
    list_display = ['title', 'author', 'published_date']

# Advanced inline configuration
class CommentInline(admin.TabularInline):
    model = Comment
    extra = 1
    can_delete = True  # Allow deletion
    show_change_link = True  # Link to full edit page
    
    # Customize queryset
    def get_queryset(self, request):
        qs = super().get_queryset(request)
        return qs.filter(approved=True)
    
    # Conditional readonly fields
    def get_readonly_fields(self, request, obj=None):
        if obj and obj.is_published:
            return ['author', 'text']
        return []

# Inline with custom form
from django import forms

class CommentInlineForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = '__all__'
    
    def clean_text(self):
        text = self.cleaned_data['text']
        if len(text) < 10:
            raise forms.ValidationError('Comment too short')
        return text

class CommentInline(admin.TabularInline):
    model = Comment
    form = CommentInlineForm

# Generic inline (for generic relations)
from django.contrib.contenttypes.admin import GenericTabularInline
from .models import Tag

class TagInline(GenericTabularInline):
    model = Tag
    extra = 1

Custom Admin Actions

Custom admin actions perform operations on multiple selected objects through dropdown menu in list view. Actions enable bulk operations like publishing posts, deleting records, or exporting data. Understanding actions enables efficient batch processing and workflow automation.

pythonactions.py
# Custom Admin Actions
from django.contrib import admin
from django.utils import timezone

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ['title', 'status', 'published_date']
    actions = ['make_published', 'make_draft', 'export_as_csv']
    
    # Action method
    @admin.action(description='Publish selected posts')
    def make_published(self, request, queryset):
        updated = queryset.update(
            status='published',
            published_date=timezone.now()
        )
        self.message_user(request, f'{updated} posts published successfully.')
    
    @admin.action(description='Move to draft')
    def make_draft(self, request, queryset):
        queryset.update(status='draft')
        self.message_user(request, 'Selected posts moved to draft.')
    
    # Action with confirmation
    @admin.action(description='Delete with confirmation')
    def delete_with_warning(self, request, queryset):
        count = queryset.count()
        if count > 10:
            self.message_user(
                request,
                f'Cannot delete more than 10 posts at once',
                level='error'
            )
            return
        queryset.delete()
        self.message_user(request, f'{count} posts deleted')
    
    # Export action
    import csv
    from django.http import HttpResponse
    
    @admin.action(description='Export to CSV')
    def export_as_csv(self, request, queryset):
        response = HttpResponse(content_type='text/csv')
        response['Content-Disposition'] = 'attachment; filename="posts.csv"'
        
        writer = csv.writer(response)
        writer.writerow(['ID', 'Title', 'Status', 'Author', 'Views'])
        
        for post in queryset:
            writer.writerow([
                post.id,
                post.title,
                post.status,
                post.author.username,
                post.views
            ])
        
        return response

# Action with intermediate page
from django.shortcuts import render

@admin.action(description='Send notification')
def send_notification(modeladmin, request, queryset):
    if 'apply' in request.POST:
        message = request.POST.get('message')
        for post in queryset:
            # Send notification
            send_email(post.author.email, message)
        modeladmin.message_user(request, f'Sent to {queryset.count()} authors')
        return
    
    return render(request, 'admin/send_notification.html', {
        'posts': queryset,
        'action': 'send_notification'
    })

# Disable default delete action
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    def get_actions(self, request):
        actions = super().get_actions(request)
        if 'delete_selected' in actions:
            del actions['delete_selected']
        return actions

Custom Admin Views

Custom admin views add specialized functionality beyond standard CRUD operations enabling reports, dashboards, import/export interfaces, and custom workflows. Custom views integrate with admin interface providing consistent user experience. Understanding custom views enables extending admin with application-specific functionality.

pythoncustom_views.py
# Custom Admin Views
from django.contrib import admin
from django.urls import path
from django.shortcuts import render
from django.http import HttpResponse

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    change_list_template = 'admin/post_changelist.html'
    
    def get_urls(self):
        urls = super().get_urls()
        custom_urls = [
            path('statistics/', self.admin_site.admin_view(self.statistics_view), name='post_statistics'),
            path('import/', self.admin_site.admin_view(self.import_view), name='post_import'),
            path('export/', self.admin_site.admin_view(self.export_view), name='post_export'),
        ]
        return custom_urls + urls
    
    def statistics_view(self, request):
        from django.db.models import Count, Avg, Sum
        
        stats = {
            'total_posts': Post.objects.count(),
            'published': Post.objects.filter(status='published').count(),
            'drafts': Post.objects.filter(status='draft').count(),
            'avg_views': Post.objects.aggregate(Avg('views'))['views__avg'],
            'total_views': Post.objects.aggregate(Sum('views'))['views__sum'],
            'by_author': Post.objects.values('author__username').annotate(count=Count('id')),
        }
        
        context = {
            **self.admin_site.each_context(request),
            'title': 'Post Statistics',
            'stats': stats,
        }
        return render(request, 'admin/post_statistics.html', context)
    
    def import_view(self, request):
        if request.method == 'POST':
            csv_file = request.FILES['file']
            # Process CSV import
            import csv
            reader = csv.reader(csv_file.read().decode('utf-8').splitlines())
            next(reader)  # Skip header
            
            imported = 0
            for row in reader:
                Post.objects.create(
                    title=row[0],
                    content=row[1],
                    author_id=row[2]
                )
                imported += 1
            
            self.message_user(request, f'Imported {imported} posts')
            return redirect('..')
        
        context = {
            **self.admin_site.each_context(request),
            'title': 'Import Posts',
        }
        return render(request, 'admin/post_import.html', context)
    
    def export_view(self, request):
        import csv
        response = HttpResponse(content_type='text/csv')
        response['Content-Disposition'] = 'attachment; filename="posts_export.csv"'
        
        writer = csv.writer(response)
        writer.writerow(['ID', 'Title', 'Content', 'Author', 'Status', 'Views'])
        
        for post in Post.objects.all():
            writer.writerow([
                post.id,
                post.title,
                post.content,
                post.author.username,
                post.status,
                post.views
            ])
        
        return response

# Custom dashboard
class MyAdminSite(admin.AdminSite):
    site_header = 'My Admin'
    
    def get_urls(self):
        urls = super().get_urls()
        custom_urls = [
            path('dashboard/', self.admin_view(self.dashboard_view), name='dashboard'),
        ]
        return custom_urls + urls
    
    def dashboard_view(self, request):
        context = {
            **self.each_context(request),
            'title': 'Dashboard',
            'recent_posts': Post.objects.order_by('-created_at')[:10],
        }
        return render(request, 'admin/dashboard.html', context)

# Template: admin/post_changelist.html
# {% extends "admin/change_list.html" %}
# {% block object-tools-items %}
#   {{ block.super }}
#   <li>
#     <a href="{% url 'admin:post_statistics' %}" class="button">Statistics</a>
#   </li>
#   <li>
#     <a href="{% url 'admin:post_import' %}" class="button">Import</a>
#   </li>
#   <li>
#     <a href="{% url 'admin:post_export' %}" class="button">Export</a>
#   </li>
# {% endblock %}

Advanced Admin Best Practices

Effective advanced admin usage follows patterns ensuring robust functionality. Use inlines for tightly related objects keeping workflows efficient. Implement custom actions for common bulk operations with proper validation. Add custom views for specialized functionality maintaining admin interface consistency. Optimize inline queries preventing performance issues. Provide clear action descriptions and confirmations. Handle errors gracefully with informative messages. Test custom functionality thoroughly ensuring proper permissions. Document complex customizations for maintainers. These practices ensure professional admin interfaces supporting complex workflows and batch operations efficiently.

$ cat /comments/ (0)

new_comment.sh

// Email hidden from public

>_

$ cat /comments/

// No comments found. Be the first!

[session] guest@{codershandbook}[timestamp] 2026

Navigation

Connect

Subscribe

// 2026 {Coders Handbook}. EOF.