Skip to main content

管理操作

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

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

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

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

警告

“删除所选对象”操作由于效率原因使用 QuerySet.delete(),这有一个重要的警告:您的模型的 delete() 方法不会被调用。

如果您希望覆盖此行为,只需写一个自定义操作,以您的首选方式完成删除 - 例如,通过为每个选定的项调用 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):              # __unicode__ on Python 2
        return self.title

我们可能使用这样的模型执行的常见任务是将文章的状态从“草稿”更新为“已发布”。我们可以在管理员一篇文章中轻松做到这一点,但如果我们想批量发布一组文章,这将是乏味的。所以,让我们写一个动作,让我们将文章的状态更改为“已发布”。

编写动作函数

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

我们的publish-these-articles函数不需要 ModelAdmin 或请求对象,但我们将使用查询集:

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

注解

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

for obj in queryset:
    do_something_with(obj)

这实际上是所有有写的行动!然而,我们将采取一个可选的但有用的步骤,并在管理员中给该操作一个“好的”标题。默认情况下,此操作将在操作列表中显示为“已发布” - 函数名称,下划线替换为空格。这很好,但我们可以通过给 make_published 函数一个 short_description 属性来提供一个更好,更人性化的名称:

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"

注解

这可能看起来很熟悉;管理员的 list_display 选项使用相同的技术来为在那里注册的回调函数提供人类可读的描述。

ModelAdmin 添加操作

接下来,我们需要通知我们的 ModelAdmin 的行动。这工作就像任何其他配置选项。所以,具有动作和注册的完整 admin.py 看起来像:

from django.contrib import admin
from myapp.models import Article

def make_published(modeladmin, request, queryset):
    queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"

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']

    def make_published(self, request, queryset):
        queryset.update(status='p')
    make_published.short_description = "Mark selected stories as published"

首先注意,我们已将 make_published 移动到一个方法中,并将 modeladmin 参数重命名为 self,其次,我们现在将字符串 'make_published' 放在 actions 中,而不是直接函数引用。这告诉 ModelAdmin 将该操作查找为方法。

将动作定义为方法使动作更直接,惯用地访问 ModelAdmin 本身,允许动作调用由管理员提供的任何方法。

例如,我们可以使用 self 向用户发送消息,通知她操作成功:

class ArticleAdmin(admin.ModelAdmin):
    ...

    def make_published(self, request, queryset):
        rows_updated = queryset.update(status='p')
        if rows_updated == 1:
            message_bit = "1 story was"
        else:
            message_bit = "%s stories were" % rows_updated
        self.message_user(request, "%s successfully marked as published." % message_bit)

这使行动匹配成功执行操作后管理员本身做什么:

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

提供中间页的操作

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

要提供中介页面,只需从操作中返回 HttpResponse (或子类)即可。例如,您可以编写一个简单的导出函数,它使用Django的 序列化函数 将一些选定的对象转储为JSON:

from django.http import HttpResponse
from django.core import serializers

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 import admin
from django.contrib.contenttypes.models import ContentType
from django.http import HttpResponseRedirect

def export_selected_objects(modeladmin, request, queryset):
    selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
    ct = ContentType.objects.get_for_model(queryset.model)
    return HttpResponseRedirect("/export/?ct=%s&ids=%s" % (ct.pk, ",".join(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 操作全局可用作名为“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(MyModelAdmin, self).get_actions(request)
        if request.user.username[0].upper() != 'J':
            if 'delete_selected' in actions:
                del actions['delete_selected']
        return actions