管理员操作

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

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

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

../../../../_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

我们可能对这样的模型执行的一项常见任务是将文章的状态从“草稿”更新为“已发布”。我们可以轻松地在管理员中一次更新一篇文章,但是如果我们想批量发布一组文章,那将非常繁琐。因此,让我们编写一个操作,让我们能够将文章的状态更改为“已发布”。

编写操作函数

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

我们的发布这些文章函数不需要 ModelAdmin 或请求对象,但我们将使用查询集

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

注意

为了获得最佳性能,我们使用查询集的 更新方法。其他类型的操作可能需要分别处理每个对象;在这些情况下,我们将遍历查询集

for obj in queryset:
    do_something_with(obj)

这实际上就是编写操作的全部内容!但是,我们将采取一个可选但有用的步骤,并在管理员中为操作提供一个“漂亮”的标题。默认情况下,此操作将显示在操作列表中,显示为“设为已发布”——函数名称,下划线替换为空格。这很好,但我们可以使用 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")

注意

这可能看起来很熟悉;管理员的 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 的 序列化函数 将一些选定的对象转储为 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)[source]

某些操作最适合在管理站点中的任何对象上使用 - 上面定义的导出操作将是一个很好的候选者。您可以使用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)[source]

如果您需要禁用站点范围操作,您可以调用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禁用所有操作

如果您希望为给定的ModelAdmin提供任何批量操作,请将ModelAdmin.actions设置为None

class MyModelAdmin(admin.ModelAdmin):
    actions = None

这告诉ModelAdmin不要显示或允许任何操作,包括任何站点范围操作

有条件地启用或禁用操作

ModelAdmin.get_actions(request)[source]

最后,您可以通过覆盖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的可用值以及相应的检查方法如下所示:

只要您在ModelAdmin上实现相应的has_<value>_permission(self, request)方法,您就可以指定任何其他值。

例如

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)[source]

此装饰器可用于设置自定义操作函数上的特定属性,这些属性可与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替换。

返回顶部