管理員動作¶
Django 管理介面的基本工作流程簡而言之是「選擇一個物件,然後變更它」。這對於大多數的使用情境來說都運作良好。然而,如果您需要一次對多個物件進行相同的變更,此工作流程可能會非常繁瑣。
在這些情況下,Django 管理介面可讓您撰寫並註冊「動作」——這些函數會在變更列表頁面上選取物件列表時被呼叫。
如果您查看管理介面中的任何變更列表,您就會看到此功能的實際作用;Django 隨附了「刪除選取的物件」動作,可供所有模型使用。例如,以下是 Django 內建的 django.contrib.auth
應用程式中的使用者模組

警告
「刪除選取的物件」動作基於效率考量,使用 QuerySet.delete()
,這有一個重要的注意事項:您模型的 delete()
方法將不會被呼叫。
如果您想要覆寫此行為,您可以覆寫 ModelAdmin.delete_queryset()
或撰寫一個自訂動作,以您偏好的方式執行刪除 - 例如,為每個選取的項目呼叫 Model.delete()
。
有關批次刪除的更多背景資訊,請參閱有關 物件刪除的文件。
請繼續閱讀以了解如何將您自己的動作新增至此列表。
撰寫動作¶
解釋動作最簡單的方式就是透過範例,所以讓我們開始吧。
管理員動作的常見使用情境是模型的批次更新。想像一個具有 Article
模型的新聞應用程式
from django.db import models
STATUS_CHOICES = {
"d": "Draft",
"p": "Published",
"w": "Withdrawn",
}
class Article(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
status = models.CharField(max_length=1, choices=STATUS_CHOICES)
def __str__(self):
return self.title
我們可能會對這種模型執行的常見任務是將文章的狀態從「草稿」更新為「已發佈」。我們可以在管理介面中一次輕鬆完成一篇文章,但如果我們要批次發佈一群文章,這將會很繁瑣。所以,讓我們撰寫一個動作,讓我們將文章的狀態變更為「已發佈」。
撰寫動作函式¶
首先,我們需要撰寫一個函數,當從管理介面觸發動作時會呼叫該函數。動作函數是接受三個參數的常規函數
目前的
ModelAdmin
代表目前請求的
HttpRequest
,包含使用者選取的一組物件的
QuerySet
。
我們的發佈這些文章函數不需要 ModelAdmin
或請求物件,但我們將使用查詢集
def make_published(modeladmin, request, queryset):
queryset.update(status="p")
注意
為了獲得最佳效能,我們使用查詢集的 更新方法。其他類型的動作可能需要單獨處理每個物件;在這些情況下,我們會疊代查詢集
for obj in queryset:
do_something_with(obj)
這實際上就是撰寫動作的全部內容!然而,我們將採取另一個可選但有用的步驟,並在管理介面中為動作提供一個「漂亮」的標題。預設情況下,此動作將在動作列表中顯示為「Make published」——函數名稱,底線由空格取代。這很好,但我們可以透過在 make_published
函數上使用 action()
修飾符,來提供更好、更人性化的名稱
from django.contrib import admin
...
@admin.action(description="Mark selected stories as published")
def make_published(modeladmin, request, queryset):
queryset.update(status="p")
注意
這可能看起來很熟悉;管理介面的 list_display
選項也使用類似的技巧,使用 display()
修飾符來為那裡註冊的回呼函數提供人類可讀的描述。
將動作新增至 ModelAdmin
¶
接下來,我們需要通知我們的 ModelAdmin
動作。這與任何其他設定選項的工作方式相同。因此,包含動作及其註冊的完整 admin.py
看起來會像這樣
from django.contrib import admin
from myapp.models import Article
@admin.action(description="Mark selected stories as published")
def make_published(modeladmin, request, queryset):
queryset.update(status="p")
class ArticleAdmin(admin.ModelAdmin):
list_display = ["title", "status"]
ordering = ["title"]
actions = [make_published]
admin.site.register(Article, ArticleAdmin)
該程式碼將為我們提供一個看起來像這樣的管理介面變更列表

這真的就是全部了!如果您渴望撰寫自己的動作,您現在已經了解足夠的知識可以開始了。本文檔的其餘部分涵蓋了更進階的技術。
處理動作中的錯誤¶
如果您的動作在執行時可能會發生可預見的錯誤情況,您應該優雅地通知使用者問題。這表示要處理例外情況,並使用 django.contrib.admin.ModelAdmin.message_user()
在回應中顯示問題的友善使用者描述。
進階動作技術¶
您可以利用一些額外的選項和可能性來獲得更進階的選項。
以 ModelAdmin
方法呈現的動作¶
上面的範例顯示 make_published
動作定義為函數。這完全沒問題,但從程式碼設計的角度來看並不完美:由於動作與 Article
物件緊密耦合,因此將動作掛接到 ArticleAdmin
物件本身是有意義的。
您可以像這樣做
class ArticleAdmin(admin.ModelAdmin):
...
actions = ["make_published"]
@admin.action(description="Mark selected stories as published")
def make_published(self, request, queryset):
queryset.update(status="p")
請注意,首先我們已將 make_published
移至方法中,並將 modeladmin
參數重新命名為 self
,其次我們現在已將字串 'make_published'
放在 actions
中,而不是直接的函數參考。這會告知 ModelAdmin
將動作查找為方法。
將動作定義為方法可讓動作更慣用地存取 ModelAdmin
本身,讓動作可以呼叫管理介面提供的任何方法。
例如,我們可以使用 self
向使用者快顯訊息,通知他們動作已成功執行
from django.contrib import messages
from django.utils.translation import ngettext
class ArticleAdmin(admin.ModelAdmin):
...
def make_published(self, request, queryset):
updated = queryset.update(status="p")
self.message_user(
request,
ngettext(
"%d story was successfully marked as published.",
"%d stories were successfully marked as published.",
updated,
)
% updated,
messages.SUCCESS,
)
這使動作與管理介面本身在成功執行動作後所做的事情相符

提供中間頁面的動作¶
預設情況下,執行動作後,使用者會被重新導向回原始的變更列表頁面。然而,某些動作,尤其是更複雜的動作,將需要傳回中間頁面。例如,內建的刪除動作會在刪除選取的物件之前要求確認。
若要提供中間頁面,請從您的動作傳回 HttpResponse
(或子類別)。例如,您可以撰寫一個匯出函數,該函數使用 Django 的 序列化函數 將一些選取的物件轉儲為 JSON
from django.core import serializers
from django.http import HttpResponse
def export_as_json(modeladmin, request, queryset):
response = HttpResponse(content_type="application/json")
serializers.serialize("json", queryset, stream=response)
return response
一般來說,上述做法並不是一個好主意。大多數時候,最佳實務是傳回 HttpResponseRedirect
,並將使用者重新導向到您撰寫的視圖,並在 GET 查詢字串中傳遞選取的物件列表。這可讓您在中間頁面上提供複雜的互動邏輯。例如,如果您想要提供更完整的匯出函數,您會想要讓使用者選擇格式,以及可能要包含在匯出中的欄位列表。最好的做法是撰寫一個小型動作,將重新導向到您的自訂匯出視圖
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect
def export_selected_objects(modeladmin, request, queryset):
selected = queryset.values_list("pk", flat=True)
ct = ContentType.objects.get_for_model(queryset.model)
return HttpResponseRedirect(
"/export/?ct=%s&ids=%s"
% (
ct.pk,
",".join(str(pk) for pk in selected),
)
)
如您所見,動作相當短;所有複雜的邏輯都將屬於您的匯出視圖。這將需要處理任何類型的物件,因此會與 ContentType
相關。
撰寫此視圖作為讀者的練習。
使動作在整個網站中可用¶
- AdminSite.add_action(action, name=None)[原始碼]¶
有些動作最好能在管理網站中的任何物件上都可用 — 上面定義的匯出動作會是一個很好的候選。您可以使用
AdminSite.add_action()
,將動作設為全域可用。例如:from django.contrib import admin admin.site.add_action(export_selected_objects)
這會使
export_selected_objects
動作以名稱「export_selected_objects」全域可用。您可以明確地給予動作一個名稱 — 如果您之後想以程式化的方式移除此動作,這會很有用 — 方法是傳遞第二個引數給AdminSite.add_action()
admin.site.add_action(export_selected_objects, "export_selected")
停用動作¶
有時候您需要停用某些動作 — 特別是那些全網站註冊的動作 — 對於特定的物件。有幾種方法可以停用動作:
停用網站範圍的動作¶
- AdminSite.disable_action(name)[原始碼]¶
如果您需要停用一個網站範圍的動作,您可以呼叫
AdminSite.disable_action()
。例如,您可以使用這個方法來移除內建的「刪除選取的物件」動作:
admin.site.disable_action("delete_selected")
一旦您完成以上步驟,該動作將不再於全網站可用。
然而,如果您需要為特定模型重新啟用一個全域停用的動作,請在您的
ModelAdmin.actions
清單中明確列出它:# Globally disable delete selected admin.site.disable_action("delete_selected") # This ModelAdmin will not have delete_selected available class SomeModelAdmin(admin.ModelAdmin): actions = ["some_other_action"] ... # This one will class AnotherModelAdmin(admin.ModelAdmin): actions = ["delete_selected", "a_third_action"] ...
為特定的 ModelAdmin
停用所有動作¶
如果您希望針對指定的 ModelAdmin
沒有可用的批次動作,請將 ModelAdmin.actions
設定為 None
:
class MyModelAdmin(admin.ModelAdmin):
actions = None
這會告訴 ModelAdmin
不要顯示或允許任何動作,包括任何網站範圍的動作。
有條件地啟用或停用動作¶
- ModelAdmin.get_actions(request)[原始碼]¶
最後,您可以藉由覆寫
ModelAdmin.get_actions()
,根據每個請求(因此也根據每個使用者)有條件地啟用或停用動作。這會回傳一個允許的動作字典。鍵是動作名稱,而值是
(函數, 名稱, 簡短描述)
元組。例如,如果您只希望名稱以「J」開頭的使用者能夠批次刪除物件:
class MyModelAdmin(admin.ModelAdmin): ... def get_actions(self, request): actions = super().get_actions(request) if request.user.username[0].upper() != "J": if "delete_selected" in actions: del actions["delete_selected"] return actions
設定動作的權限¶
動作可能會透過使用 action()
修飾器包裹動作函數,並傳遞 permissions
引數,來限制它們對具有特定權限的使用者的可用性:
@admin.action(permissions=["change"])
def make_published(modeladmin, request, queryset):
queryset.update(status="p")
make_published()
動作只會對通過 ModelAdmin.has_change_permission()
檢查的使用者可用。
如果 permissions
具有多個權限,只要使用者通過至少一個檢查,該動作就會可用。
permissions
的可用值和相應的方法檢查如下:
'change'
:ModelAdmin.has_change_permission()
'delete'
:ModelAdmin.has_delete_permission()
您可以指定任何其他值,只要您在 ModelAdmin
上實作相應的 has_<value>_permission(self, request)
方法即可。
例如:
from django.contrib import admin
from django.contrib.auth import get_permission_codename
class ArticleAdmin(admin.ModelAdmin):
actions = ["make_published"]
@admin.action(permissions=["publish"])
def make_published(self, request, queryset):
queryset.update(status="p")
def has_publish_permission(self, request):
"""Does the user have the publish permission?"""
opts = self.opts
codename = get_permission_codename("publish", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
action
修飾器¶
- action(*, permissions=None, description=None)[原始碼]¶
這個修飾器可以用於在可以與
actions
一起使用的自訂動作函數上設定特定的屬性。@admin.action( permissions=["publish"], description="Mark selected stories as published", ) def make_published(self, request, queryset): queryset.update(status="p")
這等同於直接在函數上設定一些屬性(使用原始的、較長的名稱):
def make_published(self, request, queryset): queryset.update(status="p") make_published.allowed_permissions = ["publish"] make_published.short_description = "Mark selected stories as published"
使用此修飾器並非建立動作函數的必要條件,但它可以作為來源程式碼中的標記,用於識別函數的目的,即使不使用引數也很有用。
@admin.action def make_inactive(self, request, queryset): queryset.update(is_active=False)
在這種情況下,它不會為函數添加任何屬性。
動作描述是經過 % 格式化的,可能包含
'%(verbose_name)s'
和'%(verbose_name_plural)s'
佔位符,它們會分別被模型的verbose_name
和verbose_name_plural
取代。