管理员操作

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

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

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

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

警告

为了提高效率,“删除选定对象”动作使用QuerySet.delete(),这有一个重要的警告:您的模型的delete()方法将不会被调用。

如果您希望覆盖此行为,只需编写一个自定义操作,以您喜欢的方式完成删除操作,例如对每个选定的项目调用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

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

编写动作函数

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

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

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

注意

为了获得最佳性能,我们使用查询集的update method 其他类型的行为可能需要分别处理每个对象;在这些情况下,我们只是遍历查询集:

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"

Notice first that we’ve moved make_published into a method and renamed the modeladmin parameter to self, and second that we’ve now put the string 'make_published' in actions instead of a direct function reference. 这告诉ModelAdmin查找作为方法的操作。

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

For example, we can use self to flash a message to the user informing her that the action was successful:

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的serialization functions将所选对象转储为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_actionactionname = None[source]

Some actions are best if they’re made available to any object in the admin site – the export action defined above would be a good candidate. 您可以使用AdminSite.add_action()使全局可用。 例如:

from django.contrib import admin

admin.site.add_action(export_selected_objects)

这使全局可用的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)[source]

如果您需要禁用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 的所有操作¶

如果您想对ModelAdmin使用no批量操作,只需将ModelAdmin.actions设置为None

class MyModelAdmin(admin.ModelAdmin):
    actions = None

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

有条件地启用或禁用动作

的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