Django Admin 系統¶
開始之前¶
任務目標
在這個章節中,我們會完成:
- 了解 Django Admin 的功能
- 建立超級使用者
- 註冊 Model 到 Admin
- 自訂 Admin 介面
什麼是 Django Admin?¶
Django Admin 是 Django 內建的後台管理系統,讓你可以透過網頁介面管理資料,不需要寫任何前端程式碼。
特色¶
| 特色 | 說明 |
|---|---|
| 自動生成 | 根據 Model 自動產生管理介面 |
| 功能完整 | 新增、編輯、刪除、搜尋、篩選 |
| 權限管理 | 內建使用者權限系統 |
| 可自訂 | 可以自訂顯示欄位、篩選器、動作 |
適用場景
Django Admin 非常適合:
- 內部管理系統
- 快速原型開發
- 資料維護工具
但不適合用作面向使用者的介面,因為它是為管理員設計的。
建立超級使用者¶
在使用 Admin 之前,需要先建立一個管理員帳號。
執行指令¶
輸入資訊¶
系統會詢問你以下資訊:
Username (leave blank to use 'user'): admin
Email address: [email protected]
Password:
Password (again):
This password is too short. It must contain at least 8 characters.
This password is too common.
This password is entirely numeric.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
密碼規則
Django 預設會檢查密碼強度:
- 不能太短(至少 8 個字元)
- 不能太常見
- 不能全是數字
如果密碼太簡單,Django 會警告你,但仍然可以選擇繼續使用(開發環境)。
啟動伺服器並登入¶
開啟瀏覽器訪問 http://127.0.0.1:8000/admin/,輸入剛才建立的帳號密碼。
登入成功
登入後你會看到 Django Admin 的首頁,預設已經有 Users 和 Groups 兩個管理項目。
註冊 Model 到 Admin¶
要讓 Model 出現在 Admin 中,需要在 admin.py 中註冊。
基本註冊¶
在 blog/admin.py 中:
from django.contrib import admin
from blog.models import Article, Author, Tag
admin.site.register(Article)
admin.site.register(Author)
admin.site.register(Tag)
重新整理 Admin 頁面,現在你可以看到 Articles、Authors、Tags 三個管理項目。
預設行為
使用 admin.site.register() 註冊後,Django 會:
- 顯示 Model 的所有欄位
- 提供新增、編輯、刪除功能
- 顯示
__str__()方法的回傳值
自訂 Admin 介面¶
雖然預設的 Admin 已經很好用,但我們可以透過 ModelAdmin 來自訂更多功能。
ModelAdmin 基本用法¶
建立一個繼承自 ModelAdmin 的 class,並在註冊時指定:
from django.contrib import admin
from blog.models import Article, Author, Tag
@admin.register(Author) # (1)!
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at"] # (2)!
admin.site.register(Article)
admin.site.register(Tag)
- 使用 decorator 註冊,等同於
admin.site.register(Author, AuthorAdmin) - 設定列表頁要顯示的欄位
重新整理 Admin 頁面,點擊 Authors,現在列表會顯示 name、email、created_at 三個欄位。
常用的 ModelAdmin 屬性¶
list_display - 列表顯示欄位¶
from django.contrib import admin
from blog.models import Article, Author, Tag
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ["title", "author", "is_published", "created_at"]
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at"]
admin.site.register(Tag)
list_filter - 側邊篩選器¶
from django.contrib import admin
from blog.models import Article, Author, Tag
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ["title", "author", "is_published", "created_at"]
list_filter = ["is_published", "created_at", "author"] # (1)!
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at"]
admin.site.register(Tag)
- 右側會出現篩選器,可以按照這些欄位篩選
篩選器類型
Django 會根據欄位類型自動選擇適合的篩選器:
- BooleanField → 是/否
- DateTimeField → 日期範圍
- ForeignKey → 關聯物件列表
search_fields - 搜尋欄位¶
from django.contrib import admin
from blog.models import Article, Author, Tag
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ["title", "author", "is_published", "created_at"]
list_filter = ["is_published", "created_at", "author"]
search_fields = ["title", "content"] # (1)!
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at"]
admin.site.register(Tag)
- 上方會出現搜尋框,可以搜尋 title 或 content 欄位
ordering - 預設排序¶
from django.contrib import admin
from blog.models import Article, Author, Tag
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ["title", "author", "is_published", "created_at"]
list_filter = ["is_published", "created_at", "author"]
search_fields = ["title", "content"]
ordering = ["-created_at"] # (1)!
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at"]
admin.site.register(Tag)
- 預設按 created_at 降冪排序(最新的在最上面)
list_per_page - 每頁顯示數量¶
from django.contrib import admin
from blog.models import Article, Author, Tag
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ["title", "author", "is_published", "created_at"]
list_filter = ["is_published", "created_at", "author"]
search_fields = ["title", "content"]
ordering = ["-created_at"]
list_per_page = 20 # (1)!
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at"]
admin.site.register(Tag)
- 預設是 100,這裡改成每頁顯示 20 筆
Inline 編輯¶
對於有關聯的 Model,可以使用 Inline 在同一個頁面編輯相關資料。
TabularInline - 表格式編輯¶
適合欄位較少的情況:
from django.contrib import admin
from blog.models import Article, Author, Tag
class ArticleInline(admin.TabularInline): # (1)!
model = Article
extra = 1 # (2)!
fields = ["title", "is_published"] # (3)!
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ["title", "author", "is_published", "created_at"]
list_filter = ["is_published", "created_at", "author"]
search_fields = ["title", "content"]
ordering = ["-created_at"]
list_per_page = 20
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at"]
inlines = [ArticleInline] # (4)!
admin.site.register(Tag)
- 建立一個 Inline class 繼承自
TabularInline - 預設顯示幾個空白表單(用於新增)
- 指定要顯示的欄位
- 在 AuthorAdmin 中使用這個 Inline
現在編輯作者時,可以直接在同一頁面新增/編輯他的文章。
StackedInline - 堆疊式編輯¶
適合欄位較多的情況:
from django.contrib import admin
from blog.models import Article, Author, Tag
class ArticleInline(admin.StackedInline): # (1)!
model = Article
extra = 1
fields = ["title", "content", "is_published"]
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ["title", "author", "is_published", "created_at"]
list_filter = ["is_published", "created_at", "author"]
search_fields = ["title", "content"]
ordering = ["-created_at"]
list_per_page = 20
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at"]
inlines = [ArticleInline]
admin.site.register(Tag)
- 改用
StackedInline,欄位會垂直排列
TabularInline vs StackedInline
| 類型 | 適用場景 |
|---|---|
TabularInline |
欄位少、需要一次看多筆 |
StackedInline |
欄位多、需要詳細編輯 |
自訂顯示欄位¶
除了顯示 Model 的欄位,還可以自訂顯示內容。
使用方法顯示自訂內容¶
from django.contrib import admin
from blog.models import Article, Author, Tag
class ArticleInline(admin.StackedInline):
model = Article
extra = 1
fields = ["title", "content", "is_published"]
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = [
"title",
"author",
"is_published",
"created_at",
"tag_count", # (1)!
]
list_filter = ["is_published", "created_at", "author"]
search_fields = ["title", "content"]
ordering = ["-created_at"]
list_per_page = 20
@admin.display(description="標籤數量") # (2)!
def tag_count(self, obj): # (3)!
return obj.tags.count()
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at"]
inlines = [ArticleInline]
admin.site.register(Tag)
- 在 list_display 中使用自訂方法
- 使用 decorator 設定欄位標題
- obj 是當前的 Article 物件
顯示布林值圖示¶
from django.contrib import admin
from blog.models import Article, Author, Tag
class ArticleInline(admin.StackedInline):
model = Article
extra = 1
fields = ["title", "content", "is_published"]
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = [
"title",
"author",
"is_published",
"created_at",
"tag_count",
]
list_filter = ["is_published", "created_at", "author"]
search_fields = ["title", "content"]
ordering = ["-created_at"]
list_per_page = 20
@admin.display(description="標籤數量")
def tag_count(self, obj):
return obj.tags.count()
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at", "has_published_articles"] # (1)!
inlines = [ArticleInline]
@admin.display(description="有已發布的文章", boolean=True) # (2)!
def has_published_articles(self, obj): # (3)!
return obj.articles.filter(is_published=True).exists()
admin.site.register(Tag)
- 在 list_display 中加入自訂方法
boolean=True會顯示 ✓ 或 ✗ 圖示- 檢查作者是否有已發布的文章
自訂 Actions¶
Admin 提供批次操作功能,可以一次對多筆資料執行動作。
建立自訂 Action¶
from django.contrib import admin
from blog.models import Article, Author, Tag
class ArticleInline(admin.StackedInline):
model = Article
extra = 1
fields = ["title", "content", "is_published"]
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = [
"title",
"author",
"is_published",
"created_at",
"tag_count",
]
list_filter = ["is_published", "created_at", "author"]
search_fields = ["title", "content"]
ordering = ["-created_at"]
list_per_page = 20
actions = ["publish_articles", "unpublish_articles"] # (1)!
@admin.display(description="標籤數量")
def tag_count(self, obj):
return obj.tags.count()
@admin.action(description="發布選中的文章") # (2)!
def publish_articles(self, request, queryset): # (3)!
count = queryset.update(is_published=True) # (4)!
self.message_user(request, f"成功發布 {count} 篇文章") # (5)!
@admin.action(description="取消發布選中的文章")
def unpublish_articles(self, request, queryset):
count = queryset.update(is_published=False)
self.message_user(request, f"成功取消發布 {count} 篇文章")
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at", "has_published_articles"]
inlines = [ArticleInline]
@admin.display(description="有已發布的文章", boolean=True)
def has_published_articles(self, obj):
return obj.articles.filter(is_published=True).exists()
admin.site.register(Tag)
- 註冊自訂 actions
- 使用 decorator 設定動作顯示名稱
- request 是當前請求,queryset 是選中的物件
- 批次更新選中的文章
- 顯示成功訊息給使用者
現在在文章列表頁:
- 勾選要操作的文章
- 在上方的 Action 下拉選單選擇「發布選中的文章」
- 點擊「Go」按鈕
Action 的用途
自訂 Action 適合用於:
- 批次更新資料
- 批次匯出資料
- 批次發送通知
- 批次刪除(可以加上確認步驟)
ManyToMany 欄位處理¶
對於 ManyToManyField,Admin 預設使用多選框,但資料多時不太好用。
使用 filter_horizontal¶
from django.contrib import admin
from blog.models import Article, Author, Tag
class ArticleInline(admin.StackedInline):
model = Article
extra = 1
fields = ["title", "content", "is_published"]
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = [
"title",
"author",
"is_published",
"created_at",
"tag_count",
]
list_filter = ["is_published", "created_at", "author"]
search_fields = ["title", "content"]
ordering = ["-created_at"]
list_per_page = 20
actions = ["publish_articles", "unpublish_articles"]
filter_horizontal = ["tags"] # (1)!
@admin.display(description="標籤數量")
def tag_count(self, obj):
return obj.tags.count()
@admin.action(description="發布選中的文章")
def publish_articles(self, request, queryset):
count = queryset.update(is_published=True)
self.message_user(request, f"成功發布 {count} 篇文章")
@admin.action(description="取消發布選中的文章")
def unpublish_articles(self, request, queryset):
count = queryset.update(is_published=False)
self.message_user(request, f"成功取消發布 {count} 篇文章")
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at", "has_published_articles"]
inlines = [ArticleInline]
@admin.display(description="有已發布的文章", boolean=True)
def has_published_articles(self, obj):
return obj.articles.filter(is_published=True).exists()
admin.site.register(Tag)
- 使用水平雙欄選擇器,左右欄位可以互相移動
使用 filter_vertical¶
from django.contrib import admin
from blog.models import Article, Author, Tag
class ArticleInline(admin.StackedInline):
model = Article
extra = 1
fields = ["title", "content", "is_published"]
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = [
"title",
"author",
"is_published",
"created_at",
"tag_count",
]
list_filter = ["is_published", "created_at", "author"]
search_fields = ["title", "content"]
ordering = ["-created_at"]
list_per_page = 20
actions = ["publish_articles", "unpublish_articles"]
filter_vertical = ["tags"] # (1)!
@admin.display(description="標籤數量")
def tag_count(self, obj):
return obj.tags.count()
@admin.action(description="發布選中的文章")
def publish_articles(self, request, queryset):
count = queryset.update(is_published=True)
self.message_user(request, f"成功發布 {count} 篇文章")
@admin.action(description="取消發布選中的文章")
def unpublish_articles(self, request, queryset):
count = queryset.update(is_published=False)
self.message_user(request, f"成功取消發布 {count} 篇文章")
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at", "has_published_articles"]
inlines = [ArticleInline]
@admin.display(description="有已發布的文章", boolean=True)
def has_published_articles(self, obj):
return obj.articles.filter(is_published=True).exists()
admin.site.register(Tag)
- 垂直版本的雙欄選擇器
filter_horizontal vs filter_vertical
兩者功能相同,只是排列方向不同:
filter_horizontal:左右排列(適合標籤較少)filter_vertical:上下排列(適合標籤較多)
進階設定¶
隱藏欄位¶
使用 exclude 或 fields 參數:
from django.contrib import admin
from blog.models import Article, Author, Tag
class ArticleInline(admin.StackedInline):
model = Article
extra = 1
fields = ["title", "content", "is_published"]
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = [
"title",
"author",
"is_published",
"created_at",
"tag_count",
]
list_filter = ["is_published", "created_at", "author"]
search_fields = ["title", "content"]
ordering = ["-created_at"]
list_per_page = 20
actions = ["publish_articles", "unpublish_articles"]
filter_vertical = ["tags"]
exclude = ["is_published"] # (1)!
@admin.display(description="標籤數量")
def tag_count(self, obj):
return obj.tags.count()
@admin.action(description="發布選中的文章")
def publish_articles(self, request, queryset):
count = queryset.update(is_published=True)
self.message_user(request, f"成功發布 {count} 篇文章")
@admin.action(description="取消發布選中的文章")
def unpublish_articles(self, request, queryset):
count = queryset.update(is_published=False)
self.message_user(request, f"成功取消發布 {count} 篇文章")
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at", "has_published_articles"]
inlines = [ArticleInline]
@admin.display(description="有已發布的文章", boolean=True)
def has_published_articles(self, obj):
return obj.articles.filter(is_published=True).exists()
admin.site.register(Tag)
- 隱藏
updated_at欄位,在編輯頁面不會顯示
exclude vs fields
兩種方式都可以控制顯示的欄位:
exclude:排除不想顯示的欄位fields:只顯示指定的欄位
建議使用 fields 明確列出要顯示的欄位,比較不容易遺漏重要欄位。
唯讀欄位¶
使用 readonly_fields:
from django.contrib import admin
from blog.models import Article, Author, Tag
class ArticleInline(admin.StackedInline):
model = Article
extra = 1
fields = ["title", "content", "is_published"]
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = [
"title",
"author",
"is_published",
"created_at",
"tag_count",
]
list_filter = ["is_published", "created_at", "author"]
search_fields = ["title", "content"]
ordering = ["-created_at"]
list_per_page = 20
actions = ["publish_articles", "unpublish_articles"]
filter_vertical = ["tags"]
exclude = ["is_published"]
readonly_fields = ["created_at"] # (1)!
@admin.display(description="標籤數量")
def tag_count(self, obj):
return obj.tags.count()
@admin.action(description="發布選中的文章")
def publish_articles(self, request, queryset):
count = queryset.update(is_published=True)
self.message_user(request, f"成功發布 {count} 篇文章")
@admin.action(description="取消發布選中的文章")
def unpublish_articles(self, request, queryset):
count = queryset.update(is_published=False)
self.message_user(request, f"成功取消發布 {count} 篇文章")
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at", "has_published_articles"]
inlines = [ArticleInline]
@admin.display(description="有已發布的文章", boolean=True)
def has_published_articles(self, obj):
return obj.articles.filter(is_published=True).exists()
admin.site.register(Tag)
created_at欄位會顯示但無法編輯
自動時間欄位
如果使用 auto_now_add=True 或 auto_now=True 的欄位,Django 會自動設為唯讀,不需要特別設定 readonly_fields。
Django Admin 預設隱藏了這些欄位,想要顯示它們,就需要同時設定 readonly_fields。
自訂表單佈局¶
使用 fieldsets:
from django.contrib import admin
from blog.models import Article, Author, Tag
class ArticleInline(admin.StackedInline):
model = Article
extra = 1
fields = ["title", "content", "is_published"]
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = [
"title",
"author",
"is_published",
"created_at",
"tag_count",
]
list_filter = ["is_published", "created_at", "author"]
search_fields = ["title", "content"]
ordering = ["-created_at"]
list_per_page = 20
actions = ["publish_articles", "unpublish_articles"]
filter_vertical = ["tags"]
fieldsets = [ # (1)!
("基本資訊", {"fields": ["title", "content"]}),
(
"進階選項",
{
"fields": ["author", "tags", "is_published"],
"classes": ["collapse"], # (2)!
},
),
(
"時間資訊",
{
"fields": ["created_at", "updated_at"],
},
),
]
readonly_fields = ["created_at", "updated_at"] # (3)!
@admin.display(description="標籤數量")
def tag_count(self, obj):
return obj.tags.count()
@admin.action(description="發布選中的文章")
def publish_articles(self, request, queryset):
count = queryset.update(is_published=True)
self.message_user(request, f"成功發布 {count} 篇文章")
@admin.action(description="取消發布選中的文章")
def unpublish_articles(self, request, queryset):
count = queryset.update(is_published=False)
self.message_user(request, f"成功取消發布 {count} 篇文章")
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ["name", "email", "created_at", "has_published_articles"]
inlines = [ArticleInline]
@admin.display(description="有已發布的文章", boolean=True)
def has_published_articles(self, obj):
return obj.articles.filter(is_published=True).exists()
admin.site.register(Tag)
- 使用
fieldsets將欄位分組顯示 "classes": ["collapse"]讓該區塊預設摺疊- 時間欄位設為唯讀
fieldsets 的好處
使用 fieldsets 可以:
- 將相關欄位分組,讓表單更有組織
- 使用
collapse摺疊次要資訊,讓頁面更簡潔 - 使用
wide讓欄位佔用更多空間
注意:不能同時使用 fields 和 fieldsets,兩者擇一使用。
常見問題¶
為什麼我的 Model 沒有出現在 Admin?¶
檢查以下幾點:
- 是否在
admin.py中註冊了? - App 是否在
INSTALLED_APPS中? - 有沒有重啟伺服器?
任務結束¶
完成!
恭喜你完成了這個章節!現在你已經:
- 了解 Django Admin 的功能
- 建立超級使用者
- 註冊 Model 到 Admin
- 自訂 Admin 介面
補充說明
Django Admin 還有很多進階功能,例如:
- 自訂 Admin 樣式(覆寫模板)
- Admin 權限控制(
has_add_permission、has_change_permission等) - 使用第三方套件(如
django-admin-interface、grappelli) - 自訂表單驗證
如果想要了解更多,可以參考 Django Admin 官方文件。