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.4Problem
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 fieldsProblem 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' articlesProblem 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 missingProblem 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" errorAnalysis
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 authorFix 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_fieldsfor 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.