管理员操作 — Django 文档

来自菜鸟教程
Django/docs/3.2.x/ref/contrib/admin/actions
跳转至:导航、​搜索

管理员操作

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

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

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

[[../File:../../../_images/admin-actions|../../../_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() 装饰器类似的技术,也为在那里注册的回调函数提供人类可读的描述。


3.2 版本更改: action() 装饰器的 description 参数相当于在前面直接设置动作函数上的 short_description 属性版本。 为了向后兼容,仍然支持直接设置属性。


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

该代码将为我们提供一个管理员更改列表,如下所示:

[[../File:../../../_images/adding-actions-to-the-modeladmin|../../../_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)

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

[[../File:../../../_images/actions-as-modeladmin-methods|../../../_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)

如果某些操作可用于管理站点中的 任何 对象,则它们是最好的——上面定义的导出操作将是一个很好的候选者。 您可以使用 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()

例如,您可以使用此方法删除内置的“删除选定对象”操作:

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.actions 设置为 None

class MyModelAdmin(admin.ModelAdmin):
    actions = None

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


有条件地启用或禁用操作

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 的可用值和相应的方法检查是:

您可以指定任何其他值,只要您在 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))

3.2 版本更改: action() 装饰器的 permissions 参数相当于在前面直接设置动作函数上的 allowed_permissions 属性版本。 为了向后兼容,仍然支持直接设置属性。


action 装饰器

action(*, permissions=None, description=None)

3.2 版中的新功能。

此装饰器可用于设置可与 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)

在这种情况下,它不会向函数添加任何属性。