管理アクション

Djangoの管理者の基本的な仕事は、一言で言えば、「オブジェクトを選択し、それを変更する」ことに尽きます。これは大多数のユースケースに当てはまります。しかし、一度に多くのオブジェクトに同じ変更を適用する必要がある場合、この仕事は退屈です。

このような場合、 Django の管理画面では、"アクション" を書いて登録できます。チェンジリストページで選択されたオブジェクトのリストで呼び出される関数です。

Django には、すべてのモデルで利用可能な "delete selected objects" アクションが同梱されています。たとえば、これは Django 組み込みの django.contrib.auth アプリの user モジュールです:

../../../_images/admin-actions.png

警告

"選択したオブジェクトの削除" アクションは効率の観点から 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

このようなモデルで行う一般的なタスクは記事のステータスを "draft "から "published "に更新することです。管理画面で一度に1つの記事を更新するのは簡単ですが、記事のグループを一括で公開するのは面倒です。そこで、記事のステータスを "published " に変更するアクションを書いてみましょう。

アクション関数を書く

最初に、管理画面からアクションがトリガーされたときに呼び出される関数を書く必要があります。アクション関数は3つの引数を取る通常の関数です:

  • 現在の ModelAdmin

  • 現在のリクエストを表す HttpRequest

  • ユーザーが選択したオブジェクトのセットを含む QuerySet

publish-these-articles 関数は ModelAdmin やリクエストオブジェクトを必要としませんが、クエリセットを使用します:

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

注釈

最高のパフォーマンスを得るために、クエリセットの update メソッド を使用しています。他のタイプのアクションでは、各オブジェクトを個別に処理する必要があるかもしれません。このような場合は、クエリセットをイテレートします:

for obj in queryset:
    do_something_with(obj)

アクションの書き方はこれだけです!しかし、もう1つオプションの、しかし便利なステップを踏んで、管理画面でアクションに "素敵な "タイトルをつけましょう。デフォルトでは、このアクションは "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")

注釈

これには見覚えがあるかもしれません。admin の 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)

このコードによって、次のような admin チェンジリストができます:

../../../_images/adding-actions-to-the-modeladmin.png

本当にそれだけです!もしあなたが自分のアクションを書きたくてうずうずしているのであれば、これで始めるのに十分な知識が得られました。このドキュメントの残りの部分では、より高度なテクニックを扱います。

アクション内でエラーをハンドリングする

アクションの実行中に予見可能なエラー状態が発生した場合、ユーザに問題を潔く知らせるべきです。これは、例外のハンドリングと、 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 に変更していることに注意してください。次に、関数を直接参照する代わりに actions'make_published' という文字列を指定していることに注意してください。これは ModelAdmin にアクションをメソッドとして検索するように指示します。

アクションをメソッドとして定義することで、アクションはより自然な形で ModelAdmin 自体にアクセスすることができるようになり、admin が提供する任意のメソッドをアクションが呼び出せるようになります。

例えば、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,
        )

This makes the action match what the admin itself does after successfully performing an action:

../../../_images/actions-as-modeladmin-methods.png

中間ページを提供するアクション

デフォルトでは、アクションが実行された後、ユーザーは元のチェンジリストページにリダイレクトされます。しかし、いくつかのアクション、特により複雑なアクションは中間ページを返す必要があります。例えば、組み込みの削除アクションは、選択したオブジェクトを削除する前に確認を求めます。

中間ページを提供するには、アクションから HttpResponse (またはそのサブクラス) を返します。たとえば、選択したオブジェクトを JSON としてダンプするエクスポート関数を書くには、Django の シリアライズ関数 を使います:

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() を呼び出します。

例えば、このメソッドを使って、組み込みの "delete selected objects" アクションを削除できます:

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.actionsNone に設定します:

class MyModelAdmin(admin.ModelAdmin):
    actions = None

これは ModelAdmin に対して、 サイト全体のアクション を含む全てのアクションを表示しない、または許可しないようにします。

条件付きでアクションを有効/無効にする

ModelAdmin.get_actions(request, action_location=ActionLocation.CHANGE_LIST)[ソース]

最後に、 ModelAdmin.get_actions() をオーバーライドすることで、リクエスト単位で (つまりユーザ単位で) 条件付きでアクションを有効/無効にすることができます。

This returns a dictionary of actions allowed for the specific action_location. The keys are action names, and the values are Action objects.

たとえば、名前が J で始まるユーザーだけがオブジェクトを一括削除できるようにしたい場合、次のようにします。

class MyModelAdmin(admin.ModelAdmin):
    ...

    def get_actions(self, request, action_location=ActionLocation.CHANGE_LIST):
        actions = super().get_actions(request, action_location=action_location)
        if request.user.username[0].upper() != "J":
            if "delete_selected" in actions:
                del actions["delete_selected"]
        return actions
Changed in Django 6.1:

The keyword argument action_location was added. The return type was changed to a dictionary where the keys are action names and the values are Action objects, previously the values were (function, name, description) tuples.

アクションに権限を設定する

特定の権限を持つユーザーに利用を制限するには、アクション関数を action() デコレータでラップし、次のように permissions 引数を渡します。

@admin.action(permissions=["change"])
def make_published(modeladmin, request, queryset):
    queryset.update(status="p")

make_published() アクションは ModelAdmin.has_change_permission() チェックに合格したユーザのみが利用できます。

もし permissions が複数のパーミッションを持っている場合、ユーザが少なくとも1つのチェックに合格していれば、そのアクションを利用できます。

permissions で使用可能な値と対応するメソッドチェックは以下の通りです。

対応する has_<value>_permission(self, request) メソッドを ModelAdmin に実装すれば、他の値も指定できます。

例:

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))

Controlling where actions are available

New in Django 6.1.

By default, admin actions are available on the change list page only. You can control where an action appears using the location argument of the @admin.action decorator.

For example, to make an action available only on the change form, set location to ActionLocation.CHANGE_FORM:

from django.contrib import admin
from django.contrib.admin import ActionLocation


@admin.action(location=ActionLocation.CHANGE_FORM)
def make_published(modeladmin, request, queryset): ...

To make an action available on both the change list and the change form:

@admin.action(
    location=[ActionLocation.CHANGE_FORM, ActionLocation.CHANGE_LIST],
    description="Publish",
    description_plural="Mark selected stories as published",
)
def make_published(modeladmin, request, queryset): ...

Notice that description and description_plural were provided. These are optional but the admin action will be labeled by description in the admin change form and description_plural in the admin change list.

You can customize how actions are rendered by overriding admin templates. The change list page uses admin/actions.html and the change form page uses admin/change_form_actions.html. Note that the change form template inherits from the change list actions template.

action デコレータ

action(*, permissions=None, description=None, description_plural=None, location=ActionLocation.CHANGE_LIST)[ソース]

このデコレータは、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)

この場合、関数に属性は追加されません。

パラメータ:
  • permissions -- A list of permission codenames that restrict the action to users who have at least one of the defined permissions. This sets the allowed_permissions attribute on the function. See アクションに権限を設定する for details.

  • description -- A human-readable description of the action to be rendered in the admin. If description is not provided, Django renders the function's name, converting underscores to spaces and capitalizing the first letter of the first word. This sets the short_description attribute on the function.

  • description_plural -- A human-readable description of the action used in contexts where plural wording is required, such as on the admin change list. If description_plural is not provided, falls back to description. This sets the plural_description attribute on the function.

  • location -- Specifies where the action is available. Accepts either a single ActionLocation value or an iterable of values. If omitted, the action is only available on the admin change list. See Controlling where actions are available for details.

Action description %-formatting support

Action descriptions support %-formatting and may include '%(verbose_name)s' and '%(verbose_name_plural)s' placeholders. These are replaced with the model’s verbose_name and verbose_name_plural.

Changed in Django 6.1:

The keyword arguments description_plural and location were added.

ActionLocation

New in Django 6.1.
class ActionLocation[ソース]

Enum of allowed values for the location parameter of the action() decorator.

CHANGE_FORM

The action is available on the admin change form. When an action is run, any unsaved changes on the admin change form will be lost.

CHANGE_LIST

The action is available on the admin change list.

Action

New in Django 6.1.
class Action[ソース]

Represents an action. Actions should be defined using the action() decorator.

func

The action function. See アクション関数を書く for details.

name

The action function name.

description

A human-readable description of the action to be rendered in the admin.

plural_description

A human-readable description of the action used in contexts where plural wording is required.

locations

A list of ActionLocation values the admin action can be rendered.