Django Admin 進階:Inline 編輯、自訂 Actions 與權限控制 | Django 教學
當你已經能在 Django Admin 中註冊 Model 並進行基本的 CRUD 操作後,下一步就是深入掌握 Admin 的進階功能。本篇將帶你學會 Inline 編輯 在同一頁面管理關聯資料、透過自訂 Actions 實現批次操作、使用 fieldsets 分組整理欄位,以及透過 權限控制(Permission Control) 精確管理使用者的存取範圍,最後再介紹幾個實用的 效能優化(Performance Optimization) 技巧。
Inline 模型編輯
在 Django Admin 中,Inline(內嵌編輯) 讓你可以在父模型的編輯頁面中,直接新增、修改或刪除關聯的子模型資料,不需要跳轉到另一個頁面。例如:在編輯一篇 Post 時,同時管理它底下的所有 Comment。
Django 提供兩種 Inline 類型:TabularInline(表格式)與 StackedInline(堆疊式)。
TabularInline(表格式)
TabularInline 以表格形式呈現,每筆關聯資料佔一列,畫面較為精簡,適合欄位數量較少的子模型。
from django.contrib import admin
from .models import Post, Comment
# 表格式 Inline:適合欄位少的關聯模型
class CommentTabularInline(admin.TabularInline):
model = Comment
extra = 1 # 預設顯示 1 個空白列供新增
max_num = 20 # 最多允許 20 筆留言
min_num = 0 # 最少 0 筆(非必填)
fields = ['author', 'content', 'is_approved', 'created_at']
readonly_fields = ['created_at']
StackedInline(堆疊式)
StackedInline 以堆疊方式呈現,每筆資料的欄位獨立成一個區塊,適合欄位較多或需要較大編輯空間的情境。
# 堆疊式 Inline:適合欄位多、需要較大編輯空間的關聯模型
class CommentStackedInline(admin.StackedInline):
model = Comment
extra = 0 # 不顯示額外空白列
max_num = 50 # 最多允許 50 筆
show_change_link = True # 顯示前往子模型編輯頁的連結
fields = ['author', 'content', 'is_approved', 'created_at']
readonly_fields = ['created_at', 'author']
將 Inline 掛載到 ModelAdmin
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'is_published', 'created_at']
# 在 Post 編輯頁面內嵌 Comment
inlines = [CommentTabularInline]
設定完成後,在 Admin 中編輯任何一篇 Post 時,頁面下方就會出現 Comment 的內嵌表格,可以直接新增、修改或刪除留言,全部在同一個頁面完成。
extra、max_num、min_num 這三個參數用來控制 Inline 的數量行為:
| 參數 | 說明 | 預設值 |
|---|---|---|
extra | 額外顯示的空白列數 | 3 |
max_num | 最多允許的物件總數 | 無限制 |
min_num | 最少需要的物件數量 | 0 |
自訂 Admin Actions
Actions(動作) 是 Django Admin 列表頁上方的下拉選單功能,讓你對勾選的多筆資料執行批次操作。Django 預設提供「刪除選取的項目」這個 Action,但你可以自訂任意數量的 Action。
定義 Action 函數與 @admin.action 裝飾器
from django.contrib import admin
from django.contrib import messages
from django.utils import timezone
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'is_published', 'created_at']
actions = ['make_published', 'make_draft']
# 使用 @admin.action 裝飾器定義動作
@admin.action(description='將選取的文章標記為已發布')
def make_published(self, request, queryset):
# queryset 包含使用者勾選的所有物件
count = queryset.update(
is_published=True,
published_at=timezone.now()
)
self.message_user(
request,
f'已成功發布 {count} 篇文章。',
messages.SUCCESS
)
@admin.action(description='將選取的文章退回草稿')
def make_draft(self, request, queryset):
count = queryset.update(
is_published=False,
published_at=None
)
self.message_user(
request,
f'已將 {count} 篇文章退回草稿。',
messages.WARNING
)
批次匯出 CSV 範例
自訂 Action 不限於修改資料,也可以產生檔案下載。以下示範匯出 CSV 的實作:
import csv
from django.http import HttpResponse
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
actions = ['export_as_csv']
@admin.action(description='匯出選取的文章為 CSV')
def export_as_csv(self, request, queryset):
# 建立 CSV 回應
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="posts.csv"'
writer = csv.writer(response)
# 寫入標題列
writer.writerow(['ID', '標題', '作者', '發布狀態', '建立時間'])
# 寫入資料列
for post in queryset:
writer.writerow([
post.id,
post.title,
post.author.username,
'已發布' if post.is_published else '草稿',
post.created_at.strftime('%Y-%m-%d %H:%M'),
])
self.message_user(
request,
f'已匯出 {queryset.count()} 篇文章。',
messages.SUCCESS
)
return response
fieldsets 分組顯示欄位
當 Model 的欄位數量較多時,fieldsets(欄位分組)能讓編輯頁面更有組織性。每個分組可以設定標題、包含的欄位,以及 CSS 類別(如 collapse 預設摺疊)。
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
fieldsets = [
('基本資訊', {
'fields': ['title', 'slug', 'author', 'category'],
}),
('文章內容', {
'fields': ['content', 'cover_image'],
}),
('發布設定', {
'fields': ['is_published', 'published_at', 'tags'],
'classes': ['collapse'], # 預設摺疊,點擊才展開
'description': '控制文章的發布狀態與分類標籤',
}),
('系統資訊(唯讀)', {
'fields': ['view_count', 'created_at', 'updated_at'],
'classes': ['collapse'],
}),
]
readonly_fields 與 get_readonly_fields()
readonly_fields 讓你將特定欄位設定為唯讀(Read-only),在 Admin 編輯頁面中只能檢視、不能修改。
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
# 靜態唯讀欄位
readonly_fields = ['created_at', 'updated_at', 'view_count']
若需要根據使用者身份動態決定哪些欄位為唯讀,可以覆寫 get_readonly_fields() 方法:
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
readonly_fields = ['created_at', 'updated_at']
def get_readonly_fields(self, request, obj=None):
# 取得基本唯讀欄位
readonly = list(super().get_readonly_fields(request, obj))
# 非超級使用者不能修改作者欄位
if not request.user.is_superuser:
readonly.append('author')
# 已發布的文章,標題不可修改
if obj and obj.is_published:
readonly.append('title')
return readonly
Admin 權限控制
Django Admin 內建了完整的 權限系統(Permission System),透過覆寫四個 has_*_permission 方法,可以精確控制每位使用者的新增、修改、刪除與檢視權限。
has_add_permission / has_change_permission / has_delete_permission / has_view_permission
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
"""控制誰可以新增文章"""
# 只有 Editor 群組的成員可以新增
return request.user.groups.filter(name='Editor').exists()
def has_change_permission(self, request, obj=None):
"""控制誰可以編輯文章"""
if obj is None:
# obj 為 None 表示列表頁的整體權限
return True
# 只有作者本人或超級使用者可以編輯
return obj.author == request.user or request.user.is_superuser
def has_delete_permission(self, request, obj=None):
"""控制誰可以刪除文章"""
# 只有超級使用者可以刪除
return request.user.is_superuser
def has_view_permission(self, request, obj=None):
"""控制誰可以檢視文章(Django 2.1+)"""
# 所有已登入的管理員都可以檢視
return True
get_queryset() 過濾使用者可見資料
除了權限控制,有時你還需要根據使用者身份過濾可見的資料範圍。覆寫 get_queryset() 是最直接的做法:
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super().get_queryset(request)
# 超級使用者可以看到所有文章
if request.user.is_superuser:
return qs
# 一般管理員只能看到自己建立的文章
return qs.filter(author=request.user)
這種做法確保使用者連列表頁都只會看到自己有權限存取的資料,搭配 has_change_permission 和 has_delete_permission 可以建立完整的資料隔離機制。
自訂 Admin 模板
Django Admin 基於標準的 Django 模板系統,可以透過覆寫模板來客製化頁面外觀與行為。
change_list_template 與 change_form_template
在 ModelAdmin 中指定自訂模板路徑,即可覆寫特定 Model 的列表頁或編輯頁:
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
# 自訂列表頁模板
change_list_template = 'admin/myapp/post/change_list.html'
# 自訂編輯頁模板
change_form_template = 'admin/myapp/post/change_form.html'
自訂模板通常繼承原始模板,只覆寫需要修改的區塊:
{# templates/admin/myapp/post/change_list.html #}
{% extends "admin/change_list.html" %}
{% block content %}
<div style="padding: 10px; background: #f0f8ff; border-radius: 4px; margin-bottom: 15px;">
<strong>提示:</strong>本頁面顯示所有文章,點擊標題可進入編輯。
</div>
{{ block.super }}
{% endblock %}
確保 settings.py 中的 TEMPLATES 設定包含你的自訂模板目錄:
# settings.py
TEMPLATES = [{
'DIRS': [BASE_DIR / 'templates'], # 自訂模板目錄
'APP_DIRS': True,
# ...
}]
Admin 效能優化
當資料量增長後,Admin 列表頁可能會出現載入緩慢的問題,通常是因為 N+1 查詢問題(N+1 Query Problem) 或一次載入過多關聯資料。以下是三個最實用的優化技巧。
list_select_related
list_select_related 告訴 Django 在載入列表頁時,自動對指定的 ForeignKey 欄位執行 select_related,將多次查詢合併為一次 JOIN 查詢。
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'category', 'created_at']
# 自動 select_related,避免列表頁對每筆資料額外查詢 author 和 category
list_select_related = ['author', 'category']
raw_id_fields(大量 ForeignKey)
當 ForeignKey 關聯的目標資料表有成千上萬筆資料時,預設的下拉選單會載入所有選項,導致頁面極度緩慢。raw_id_fields 將下拉選單替換為一個 ID 輸入框搭配彈出式搜尋視窗。
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
# 將 author 欄位改為 ID 輸入框 + 放大鏡搜尋
raw_id_fields = ['author']
autocomplete_fields
autocomplete_fields 是 raw_id_fields 的現代替代方案(Django 2.0+),提供自動完成(Autocomplete)的搜尋體驗。使用前,關聯模型的 ModelAdmin 必須設定 search_fields。
from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin
# 確保關聯模型有設定 search_fields
class CustomUserAdmin(UserAdmin):
search_fields = ['username', 'email', 'first_name']
# 取消原有註冊,重新註冊
admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
# 自動完成搜尋,體驗比 raw_id_fields 更好
autocomplete_fields = ['author', 'category']
三種方式的比較:
| 方式 | 適用場景 | 使用體驗 |
|---|---|---|
| 預設下拉選單 | 選項少於 100 筆 | 最直覺,但資料多時會卡頓 |
raw_id_fields | 大量資料、不需搜尋 | ID 輸入框 + 彈出視窗 |
autocomplete_fields | 大量資料、需要搜尋 | 輸入關鍵字即時搜尋,推薦使用 |
總結
Django Admin 的進階功能讓你可以打造出高效且符合業務需求的後台管理介面。以下整理本篇的核心要點:
- Inline 編輯 讓你在同一頁面管理父子關聯資料,
TabularInline適合欄位少的情境,StackedInline適合欄位多的情境,善用extra、max_num、min_num控制數量 - 自訂 Actions 能實現批次發布、匯出 CSV 等常見操作,透過
@admin.action裝飾器定義,搭配self.message_user()回饋操作結果 - fieldsets 與 readonly_fields 讓編輯頁面更有組織性,
get_readonly_fields()可根據使用者身份或物件狀態動態調整唯讀欄位 - 權限控制 透過四個
has_*_permission方法精確管理存取權限,搭配get_queryset()過濾可見資料,建立完整的資料隔離機制 - 效能優化 使用
list_select_related解決 N+1 查詢問題,大量 ForeignKey 資料改用autocomplete_fields取代預設下拉選單
掌握這些進階技巧後,你的 Django Admin 後台將不再只是「能用」的基本工具,而是一套「好用」的管理系統。下一篇我們將探討 Django 的 靜態檔案管理(Static Files) 與 媒體檔案處理(Media Files),學習如何正確管理 CSS、JavaScript 與使用者上傳的檔案。