troubleshooting2024-10-10·8·261/348

Django admin 커스터마이징 문제

Solving common Django admin customization issues — from inline editing failures to custom querysets, widgets, and permission problems.

Django admin 커스터마이징 문제

Introduction

Django's admin interface is remarkably powerful out of the box, but customizing it beyond the basics often leads to unexpected behavior. Whether it is inline editing that silently saves empty records, custom querysets that break pagination, or permission checks that lock you out of your own admin panel, these issues are common and frustrating.

I ran into a significant Django admin issue while building a content management system for a client in Lisbon. Custom admin forms with inline editing were saving parent records without child records, creating orphaned data that corrupted the database.

Environment

Django 4.2.11
Python 3.12.3
PostgreSQL 15.4

Problem

Problem 1: Inline saves empty records

from django.contrib import admin
from .models import Article, Comment

class CommentInline(admin.TabularInline):
    model = Comment
    extra = 3  # Shows 3 empty forms

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    inlines = [CommentInline]

# When saving an article with all 3 extra comments empty,
# Django creates 3 Comment records with empty fields

Problem 2: Custom queryset breaks admin

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    def get_queryset(self, request):
        return Article.objects.filter(author=request.user)
    
# This breaks the "Add" form for superusers
# because the queryset filter prevents seeing other authors' articles

Problem 3: Custom widget not rendering

from django import forms
from django.contrib import admin
from django.forms import Textarea

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        widgets = {
            'content': Textarea(attrs={'rows': 20, 'cols': 80}),
        }
        fields = '__all__'

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    form = ArticleForm
    # Widget might not render correctly if Media class is missing

Problem 4: admin.py import errors

# admin.py
from django.contrib import admin
from .models import Article

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_display = ['title', 'author', 'created_at']

# If models.py has a syntax error, admin.py fails silently
# and you get "No registered apps" error

Analysis

Django admin customization issues usually stem from one of these root causes.

Root Cause 1: TabularInline saves all forms including empty ones. By default, Django creates extra number of empty forms and saves them all. Empty inline forms result in empty database records.

Root Cause 2: Queryset filtering conflicts with admin operations. When you filter the admin queryset, you affect all admin operations — list view, add, change, and delete.

Root Cause 3: Widget configuration conflicts with ModelAdmin. Widget definitions in ModelAdmin and form classes can override each other.

Root Cause 4: Admin site registration order matters. If admin.py imports fail, the admin site may not register your models at all.

Diagnose the issue:

# Check if models are registered
python manage.py shell -c "
from django.contrib import admin
for model, model_admin in admin.site._registry.items():
    print(f'{model.__name__} -> {model_admin.__class__.__name__}')
"

Solution

Fix 1: Prevent empty inline records with has_changed()

from django.contrib import admin
from .models import Article, Comment

class CommentInline(admin.TabularInline):
    model = Comment
    extra = 1
    
    def has_changed(self):
        """Only save if at least one field was changed."""
        return any(
            self.cleaned_data.get(field)
            for field in ['text']  # Your required fields
        )

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    inlines = [CommentInline]

Or use validate_unique:

class CommentInline(admin.TabularInline):
    model = Comment
    extra = 1
    
    def save_model(self, request, obj, form, change):
        """Skip saving if all fields are empty."""
        if not any(form.cleaned_data.get(f) for f in ['text']):
            return
        super().save_model(request, obj, form, change)

Fix 2: Proper queryset filtering for superusers

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    def get_queryset(self, request):
        qs = super().get_queryset(request)
        if request.user.is_superuser:
            return qs  # Superusers see everything
        return qs.filter(author=request.user)
    
    def get_readonly_fields(self, request, obj=None):
        if request.user.is_superuser:
            return []
        return ['author']  # Non-superusers can't change author

Fix 3: Proper widget configuration

from django.contrib import admin
from django import forms
from .models import Article

class ArticleAdminForm(forms.ModelForm):
    class Meta:
        model = Article
        widgets = {
            'content': forms.Textarea(attrs={
                'rows': 20, 
                'cols': 80,
                'class': 'vLargeTextField',
            }),
        }
        fields = '__all__'
    
    class Media:
        css = {
            'all': ('css/custom-admin.css',)
        }
        js = ('js/custom-admin.js',)

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    form = ArticleAdminForm
    fieldsets = [
        ('Basic Info', {
            'fields': ['title', 'slug']
        }),
        ('Content', {
            'fields': ['content'],
            'classes': ['collapse'],
        }),
        ('Metadata', {
            'fields': ['author', 'created_at', 'updated_at'],
            'classes': ['collapse'],
        }),
    ]

Fix 4: Validate admin configuration at startup

# admin.py
from django.contrib import admin
from .models import Article, Comment

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_display = ['title', 'author', 'created_at']
    list_filter = ['created_at', 'author']
    search_fields = ['title', 'content']
    readonly_fields = ['created_at', 'updated_at']

@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
    list_display = ['article', 'author', 'created_at']
    raw_id_fields = ['article', 'author']  # Better than dropdown for large datasets

# Add validation
def validate_admin():
    """Check that all required admin attributes are set."""
    from django.contrib.admin.sites import site
    for model, model_admin in site._registry.items():
        if not hasattr(model_admin, 'list_display'):
            print(f"WARNING: {model.__name__}Admin missing list_display")
        if not hasattr(model_admin, 'list_filter'):
            print(f"WARNING: {model.__name__}Admin missing list_filter")

# Run validation at module load
if __name__ != '__main__':
    validate_admin()

Lessons Learned

  • Always override save_formset() for inline editing to prevent empty records from being saved.
  • Check queryset filtering conflicts with superuser operations by testing add/change/delete as different users.
  • Use raw_id_fields for ForeignKey fields with large datasets instead of dropdown selects.
  • Validate admin configuration at startup to catch missing attributes early.
  • Test admin customizations with different user roles to ensure permissions work correctly.

This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.