1. 程式人生 > >django 1.8 官方文件翻譯:7-2 管理操作

django 1.8 官方文件翻譯:7-2 管理操作

管理操作

簡而言之,Django管理後臺的基本流程是,“選擇一個物件並改變它”。在大多數情況下,這是非常適合的。然而當你一次性要對多個物件做相同的改變,這個流程是非常的單調乏味的。

在這些例子中,Django管理後臺可以讓你實現和註冊“操作” —— 僅僅只是一個以已選中物件集合為引數的回撥函式。

在Django自帶的管理頁面中都能看到這樣的例子。Django在所有的模型中自帶了一個“刪除所選物件”操作。例如,下面是 django.contrib.auth app 在Django’s建立的使用者模型:

../../../_images/user_actions.png

警告

“刪除所選物件”的操作由於效能因素使用了QuerySet.delete()

,這裡有個附加說明:它不會呼叫你模型的delete()方法。

如果你想覆寫這一行為,編寫自定義操作,以你的方式實現刪除就可以了 – 例如,對每個已選擇的元素呼叫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): # __unicode__ on Python 2 return self.title

我們可能在模型上執行的一個普遍任務是,將文章狀態從“草稿”更新為“已釋出”。我們在後臺一次處理一篇文章非常輕鬆,但是如果我們想要批量釋出一些文章,會非常麻煩。所以讓我們編寫一個操作,可以讓我們將一篇文章的狀態修改為“已釋出”。

編寫操作 函式

首先,我們需要定義一個函式,當後臺操作被點選觸發的時候呼叫。操作函式,跟普通的函式一樣,需要接收三個引數:

我們用於釋出這些文章的函式並不需要ModelAdmin或者請求物件,但是我們會用到查詢集:

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')

注意

為了效能最優,我們使用查詢集的update 方法。其它型別的操作可能需要分別處理每個物件;這種情況下我們需要對查詢集進行遍歷:

for obj in queryset:
    do_something_with(obj)

編寫操作的全部內容實際上就這麼多了。但是,我們要進行一個可選但是有用的步驟,在後臺給操作起一個“非常棒”的標題。通常,操作以“Make published”的方式出現在操作列表中 – 所有空格被下劃線替換後的函式名稱。這樣就很好了,但是我們可以提供一個更好、更人性化的名稱,通過向make_published函式新增short_description 屬性:

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"

注意

這看起來可能會有點熟悉;admin的list_display選項使用同樣的技巧,為這裡註冊的回掉函式來提供人類可讀的描述。

接下來,我們需要把操作告訴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的更改列表,看起來像這樣:

../../../_images/article_actions.png

這就是全部內容了。如果你想編寫自己的操作,你現在應該知道怎麼開始了。這篇文件的剩餘部分會介紹更多高階技巧。

在操作中處理錯誤

如果你預見到,執行你的操作時可能出現一些錯誤,你應該以優雅的方式向用戶通知這些錯誤。也就是說,異常處理和使用django.contrib.admin.ModelAdmin.message_user()可以在響應中展示使用者友好的問題描述。

操作的高階技巧

對於進一步的選擇,你可以使用一些額外的選項。

上面的例子展示了定義為一個簡單函式的make_published操作。這真是極好的,但是以檢視的程式碼設計角度來看,它並不完美:由於操作與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,其次,我們現在將'make_published'字串放進了actions,而不是一個直接的函式引用。這樣會讓 ModelAdmin將這個操作視為方法。

將操作定義為方法,可以使操作以更加直接、符合語言習慣的方式來訪問ModelAdmin,呼叫任何admin提供的方法。

例如,我們可以使用self來向用戶傳送訊息,告訴她操作成功了:

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)

這會使動作與後臺在成功執行動作後做的事情相匹配:

../../../_images/article_actions_message.png

提供中間頁面的操作

通常,在執行操作之後,使用者會簡單地通過重定向返回到之前的修改列表頁面中。然而,一些操作,尤其是更加複雜的操作,需要返回一箇中間頁面。例如,內建的刪除操作,在刪除選中物件之前需要向用戶詢問來確認。

要提供中間頁面,只要從你的操作返回HttpResponse(或其子類)就可以了。例如,你可能編寫了一個簡單的匯出函式,它使用了Django的序列化函式來將一些選中的物件轉換為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

通常,上面的程式碼的實現方式並不是很好。大多數情況下,最佳實踐是返回 HttpResponseRedirect,並且使使用者重定向到你編寫的檢視中,向GET查詢字串傳遞選中物件的列表。這需要你在中間介面上提供複雜的互動邏輯。例如,如果你打算提供一個更加複雜的匯出函式,你會希望讓使用者選擇一種格式,以及可能在匯出中包含一個含有欄位的列表。最佳方式是編寫一個小型的操作,簡單重定向到你的自定義匯出檢視中:

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()傳遞第二個引數:

admin.site.add_action(export_selected_objects, 'export_selected')

禁用操作

有時你需要禁用特定的操作 – 尤其是註冊的站點級操作 – 對於特定的物件。你可以使用一些方法來禁用操作:

禁用整個站點的操作

AdminSite.``disable_action(name)

例如,你可以使用這個方法來移除內建的“刪除選中的物件”操作:

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

class MyModelAdmin(admin.ModelAdmin):
    actions = None

這樣會告訴ModelAdmin,不要展示或者允許任何操作,包括站點級操作

按需啟用或禁用操作

ModelAdmin.``get_actions(request)

這個函式返回包含允許操作的字典。字典的鍵是操作的名稱,值是 (function, name, short_description)元組。

多數情況下,你會按需使用這一方法,來從超類中的列表移除操作。例如,如果我只希望名稱以’J’開頭的使用者可以批量刪除物件,我可以執行下面的程式碼:

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