管理动作

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

在这些情况下,Django的管理员允许您编写和注册“动作”--通过更改列表页面上选择的对象列表调用的函数。

如果您查看管理员中的任何更改列表,您将看到此功能正在运行;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

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

编写动作函数

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

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

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

备注

为了获得最佳性能,我们使用查询集的 update method .其他类型的操作可能需要单独处理每个对象;在这些情况下,我们会迭代查询集::

for obj in queryset:
    do_something_with(obj)

这实际上就是写动作的全部内容!然而,我们将采取另一个可选但有用的步骤,并在管理员中为该操作赋予一个“漂亮”的标题。默认情况下,此操作将在操作列表中显示为“Make published”--函数名称,虚线被空白替换。没关系,但我们可以通过使用 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 (or亚类)从您的行动中。例如,您可能会编写一个使用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