簡而言之,Django管理后臺的基本流程是,“選擇一個對象并改變它”。在大多數(shù)情況下,這是非常適合的。然而當你一次性要對多個對象做相同的改變,這個流程是非常的單調(diào)乏味的。
在這些例子中,Django管理后臺可以讓你實現(xiàn)和注冊“操作” —— 僅僅只是一個以已選中對象集合為參數(shù)的回調(diào)函數(shù)。
在Django自帶的管理頁面中都能看到這樣的例子。Django在所有的模型中自帶了一個“刪除所選對象”操作。例如,下面是 django.contrib.auth
app 在Django's創(chuàng)建的用戶模型:
警告
“刪除所選對象”的操作由于性能因素使用了QuerySet.delete()
,這里有個附加說明:它不會調(diào)用你模型的delete()
方法。
如果你想覆寫這一行為,編寫自定義操作,以你的方式實現(xiàn)刪除就可以了 -- 例如,對每個已選擇的元素調(diào)用Model.delete()
。
關(guān)于整體刪除的更多信息,參見對象刪除的文檔。
繼續(xù)閱讀,來弄清楚如何向列表添加你自己的操作。
通過示例來解釋操作最為簡單,讓我們開始吧。
操作的一個最為普遍的用例是模型的整體更新??紤]帶有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): # __unicode__ on Python 2
return self.title
我們可能在模型上執(zhí)行的一個普遍任務是,將文章狀態(tài)從“草稿”更新為“已發(fā)布”。我們在后臺一次處理一篇文章非常輕松,但是如果我們想要批量發(fā)布一些文章,會非常麻煩。所以讓我們編寫一個操作,可以讓我們將一篇文章的狀態(tài)修改為“已發(fā)布”。
首先,我們需要定義一個函數(shù),當后臺操作被點擊觸發(fā)的時候調(diào)用。操作函數(shù),跟普通的函數(shù)一樣,需要接收三個參數(shù):
ModelAdmin
HttpRequest
QuerySet
我們用于發(fā)布這些文章的函數(shù)并不需要ModelAdmin
或者請求對象,但是我們會用到查詢集:
def make_published(modeladmin, request, queryset):
queryset.update(status='p')
注意
為了性能最優(yōu),我們使用查詢集的update 方法。其它類型的操作可能需要分別處理每個對象;這種情況下我們需要對查詢集進行遍歷:
for obj in queryset:
do_something_with(obj)
編寫操作的全部內(nèi)容實際上就這么多了。但是,我們要進行一個可選但是有用的步驟,在后臺給操作起一個“非常棒”的標題。通常,操作以“Make published”的方式出現(xiàn)在操作列表中 -- 所有空格被下劃線替換后的函數(shù)名稱。這樣就很好了,但是我們可以提供一個更好、更人性化的名稱,通過向make_published
函數(shù)添加short_description
屬性:
def make_published(modeladmin, request, queryset):
queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"
注意
這看起來可能會有點熟悉;admin的list_display
選項使用同樣的技巧,為這里注冊的回掉函數(shù)來提供人類可讀的描述。
ModelAdmin
接下來,我們需要把操作告訴ModelAdmin
。它和其他配置項的工作方式相同。所以,帶有操作及其注冊的完整的admin.py
看起來像這樣:
from django.contrib import admin
from myapp.models import Article
def make_published(modeladmin, request, queryset):
queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"
class ArticleAdmin(admin.ModelAdmin):
list_display = ['title', 'status']
ordering = ['title']
actions = [make_published]
admin.site.register(Article, ArticleAdmin)
這段代碼會向我們提供admin的更改列表,看起來像這樣:
這就是全部內(nèi)容了。如果你想編寫自己的操作,你現(xiàn)在應該知道怎么開始了。這篇文檔的剩余部分會介紹更多高級技巧。
如果你預見到,運行你的操作時可能出現(xiàn)一些錯誤,你應該以優(yōu)雅的方式向用戶通知這些錯誤。也就是說,異常處理和使用django.contrib.admin.ModelAdmin.message_user()
可以在響應中展示用戶友好的問題描述。
對于進一步的選擇,你可以使用一些額外的選項。
ModelAdmin
上面的例子展示了定義為一個簡單函數(shù)的make_published
操作。這真是極好的,但是以視圖的代碼設(shè)計角度來看,它并不完美:由于操作與Article
緊密耦合,不如將操作直接綁定到ArticleAdmin
對象上更有意義。
這樣做十分簡單:
class ArticleAdmin(admin.ModelAdmin):
...
actions = ['make_published']
def make_published(self, request, queryset):
queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"
首先注意,我們將make_published
放到一個方法中,并重命名 modeladmin
為self
,其次,我們現(xiàn)在將'make_published'
字符串放進了actions
,而不是一個直接的函數(shù)引用。這樣會讓 ModelAdmin
將這個操作視為方法。
將操作定義為方法,可以使操作以更加直接、符合語言習慣的方式來訪問ModelAdmin
,調(diào)用任何admin提供的方法。
例如,我們可以使用self
來向用戶發(fā)送消息,告訴她操作成功了:
class ArticleAdmin(admin.ModelAdmin):
...
def make_published(self, request, queryset):
rows_updated = queryset.update(status='p')
if rows_updated == 1:
message_bit = "1 story was"
else:
message_bit = "%s stories were" % rows_updated
self.message_user(request, "%s successfully marked as published." % message_bit)
這會使動作與后臺在成功執(zhí)行動作后做的事情相匹配:
通常,在執(zhí)行操作之后,用戶會簡單地通過重定向返回到之前的修改列表頁面中。然而,一些操作,尤其是更加復雜的操作,需要返回一個中間頁面。例如,內(nèi)建的刪除操作,在刪除選中對象之前需要向用戶詢問來確認。
要提供中間頁面,只要從你的操作返回HttpResponse
(或其子類)就可以了。例如,你可能編寫了一個簡單的導出函數(shù),它使用了Django的序列化函數(shù)來將一些選中的對象轉(zhuǎn)換為JSON:
from django.http import HttpResponse
from django.core import serializers
def export_as_json(modeladmin, request, queryset):
response = HttpResponse(content_type="application/json")
serializers.serialize("json", queryset, stream=response)
return response
通常,上面的代碼的實現(xiàn)方式并不是很好。大多數(shù)情況下,最佳實踐是返回 HttpResponseRedirect
,并且使用戶重定向到你編寫的視圖中,向GET查詢字符串傳遞選中對象的列表。這需要你在中間界面上提供復雜的交互邏輯。例如,如果你打算提供一個更加復雜的導出函數(shù),你會希望讓用戶選擇一種格式,以及可能在導出中包含一個含有字段的列表。最佳方式是編寫一個小型的操作,簡單重定向到你的自定義導出視圖中:
from django.contrib import admin
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect
def export_selected_objects(modeladmin, request, queryset):
selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
ct = ContentType.objects.get_for_model(queryset.model)
return HttpResponseRedirect("/export/?ct=%s&ids=%s" % (ct.pk, ",".join(selected)))
就像你看到的那樣,這個操作是最簡單的部分;所有復雜的邏輯都在你的導出視圖里面。這需要處理任何類型的對象,所以需要處理ContentType
。
這個視圖的編寫作為一個練習留給讀者。
AdminSite.``add_action
(action[, name])
如果一些操作對管理站點的_任何_對象都可用的話,是非常不錯的 -- 上面所定義的導出操作是個不錯的備選方案。你可以使用AdminSite.add_action()
讓一個操作在全局都可以使用。例如:
from django.contrib import admin
admin.site.add_action(export_selected_objects)
這樣,export_selected_objects
操作可以在全局使用,名稱為“export_selected_objects”。你也可以顯式指定操作的名稱 – 如果你想以編程的方式移除這個操作 – 通過向AdminSite.add_action()
傳遞第二個參數(shù):
admin.site.add_action(export_selected_objects, 'export_selected')
有時你需要禁用特定的操作 -- 尤其是注冊的站點級操作 -- 對于特定的對象。你可以使用一些方法來禁用操作:
AdminSite.``disable_action
(name)
如果你需要禁用站點級操作 ,你可以調(diào)用 AdminSite.disable_action()
。
例如,你可以使用這個方法來移除內(nèi)建的“刪除選中的對象”操作:
admin.site.disable_action('delete_selected')
一旦你執(zhí)行了上面的代碼,這個操作不再對整個站點中可用。
然而,如果你需要為特定的模型重新啟動在全局禁用的對象,把它顯式放在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
設(shè)置為None
:
class MyModelAdmin(admin.ModelAdmin):
actions = None
這樣會告訴[ModelAdmin
](index.html#django.contrib.admin.ModelAdmin ),不要展示或者允許任何操作,包括站點級操作。
ModelAdmin.``get_actions
(request)
最后,你可以通過覆寫ModelAdmin.get_actions()
,對每個請求(每個用戶)按需開啟或禁用操作。
這個函數(shù)返回包含允許操作的字典。字典的鍵是操作的名稱,值是 (function, name, short_description)
元組。
多數(shù)情況下,你會按需使用這一方法,來從超類中的列表移除操作。例如,如果我只希望名稱以'J'開頭的用戶可以批量刪除對象,我可以執(zhí)行下面的代碼:
class MyModelAdmin(admin.ModelAdmin):
...
def get_actions(self, request):
actions = super(MyModelAdmin, self).get_actions(request)
if request.user.username[0].upper() != 'J':
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
譯者:Django 文檔協(xié)作翻譯小組,原文:Admin actions。
本文以 CC BY-NC-SA 3.0 協(xié)議發(fā)布,轉(zhuǎn)載請保留作者署名和文章出處。
Django 文檔協(xié)作翻譯小組人手緊缺,有興趣的朋友可以加入我們,完全公益性質(zhì)。交流群:467338606。