管理动作

简而言之,Django管理员的基本工作流程是“选择一个对象,然后更改它。”这在大多数用例中都很有效。但是,如果您需要同时对许多对象进行相同的更改,则此工作流可能非常繁琐。

在这些情况下,Django的admin允许您编写和注册“actions”--调用这些函数的对象列表是在更改列表页面上选择的。

如果您查看管理员中的任何更改列表,您将看到此功能正在运行;Django附带了一个对所有模型都可用的“删除选定对象”操作。例如,下面是Django内置的用户模块 django.contrib.auth 应用程序:

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

警告

“删除选定对象”操作使用 QuerySet.delete() 出于效率的考虑,这有一个重要的警告:您的模型 delete() 将不调用方法。

如果要重写此行为,可以重写 ModelAdmin.delete_queryset() 或者编写一个以您喜欢的方式进行删除的自定义操作——例如,通过调用 Model.delete() 对于每个选定的项目。

有关批量删除的更多背景信息,请参阅 object deletion .

继续阅读,了解如何将自己的操作添加到此列表中。

写作行为

解释行动的最简单方法是通过例子,所以让我们深入了解一下。

管理操作的一个常见用例是模型的批量更新。设想一个新闻应用程序 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

对于这样的模型,我们可能执行的一个常见任务是将文章的状态从“草稿”更新为“已发布”。我们可以很容易地在管理员中一次只发布一篇文章,但是如果我们想批量发布一组文章,那将是非常乏味的。那么,让我们编写一个操作,让我们将文章的状态更改为“已发布”。

编写动作函数

首先,我们需要编写一个函数,该函数在管理员触发操作时被调用。Action函数是常规函数,有三个参数:

我们的“发布这些文章”功能不需要 ModelAdmin 或者请求对象,但我们将使用queryset::

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

备注

为了获得最佳性能,我们使用的是 update method . 其他类型的操作可能需要单独处理每个对象;在这些情况下,我们将遍历queryset::

for obj in queryset:
    do_something_with(obj)

实际上,这就是编写动作的全部内容!但是,我们将采取另一个可选但有用的步骤,并在管理员中为该操作指定一个“不错”的标题。默认情况下,此操作在操作列表中显示为“Make Publisher”--函数名称,下划线由空格替换。这很好,但是我们可以提供一个更好、更人性化的名称,方法是使用 action() 室内装饰师在 make_published 功能::

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)

该代码将为我们提供一个类似以下内容的管理更改列表:

../../../_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 第二,我们现在把字符串 '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,
        )

这将使操作与成功执行操作后管理员本身的操作相匹配:

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

提供中间页的操作

默认情况下,执行操作后,用户将被重定向回原始更改列表页面。但是,有些操作,尤其是更复杂的操作,需要返回中间页。例如,内置的删除操作要求在删除选定对象之前进行确认。

若要提供中间页,请返回 HttpResponse (或子类)来自您的操作。例如,您可以编写一个使用Django的导出函数 serialization functions 要将某些选定对象转储为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)[源代码]

一些行动是最好的,如果他们可以 any 管理站点中的对象——上面定义的导出操作将是一个很好的候选者。您可以使用 AdminSite.add_action() . 例如::

from django.contrib import admin

admin.site.add_action(export_selected_objects)

这使得 export_selected_objects 作为名为“导出选定对象”的操作全局可用的操作。您可以显式地给这个动作起一个名字——如果以后您想通过编程的方式实现,那就好了。 remove the action --通过将第二个参数传递给 AdminSite.add_action() ::

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

禁用操作

有时你需要禁用某些动作——尤其是那些 registered site-wide --对于特定对象。有几种方法可以禁用操作:

禁用站点范围的操作

AdminSite.disable_action(name)[源代码]

如果需要禁用 site-wide action 你可以调用 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

如果你想要 no 对于给定的 ModelAdmin ,集合 ModelAdmin.actionsNone ::

class MyModelAdmin(admin.ModelAdmin):
    actions = None

这告诉了 ModelAdmin 不显示或允许任何操作,包括 site-wide actions .

有条件地启用或禁用操作

ModelAdmin.get_actions(request)[源代码]

最后,您可以通过重写来有条件地启用或禁用每个请求上的操作(因此也可以基于每个用户)。 ModelAdmin.get_actions() .

这将返回允许的操作字典。键是操作名,值是 (function, name, short_description) 元组。

例如,如果只希望名称以“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 相应的方法检查为:

只要实现相应的 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))

这个 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_nameverbose_name_plural