自定义Admin actions

阅读: 45758     评论:1

注意新增的action装饰器

通常情况下,admin的工作模式是“选中目标,然后修改目标”,但在同时修改大量目标的时候,这种模式就变得重复、繁琐。

为此,admin提供了自定义功能函数actions的手段,可以批量对数据进行修改。

admin内置了一个批量删除对象的操作,如下图所示:

下面以一个新闻应用的文章模型为例,编写一个批量更新的actions,它将选择的文章由“草稿”状态更新为“发布”状态:

首先是模型的代码:

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

一、编写action

action必须携带三个参数:

  • 当前的ModelAdmin
  • 当前的HttpRequest对象(即request)
  • 被选择的对象(即QuerySet)

在应用中的admin.py文件中写入:

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

注意:这里我们作为例子,简单地使用了queryset自带的update()方法,它能批量操作。但在多数情况下,你要自己遍历queryset的每个元素,并编写具体的操作。也就是:

for obj in queryset:
    do_something_with(obj)

还可以设置一个简单易懂的简短描述(可以使用中文),用于代替生硬的函数名:

from django.contrib import admin

...


@admin.action(description="Mark selected stories as published")
def make_published(modeladmin, request, queryset):
    queryset.update(status="p")

二、将action添加到对应的ModelAdmin中

关键是其中的action装饰器:

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)

然后,页面看起来是下面的样子(注意下拉框):

三、将action定义为ModelAdmin的方法

上面的make_published看起来已经不错了,但是我们一般会将它作为ModelAdmin的方法来使用。

下面我们把它移到ArticleAdmin类中:

class ArticleAdmin(admin.ModelAdmin):
    ...

    actions = ["make_published"]

    @admin.action(description="Mark selected stories as published")
    def make_published(self, request, queryset):  #注意self取代了modeladmin
        queryset.update(status="p")

这样做的好处是自定义方法可以直接访问类本身。

例如下面使用self为方法添加提示信息的功能:

from django.contrib import messages
from django.utils.translation import ngettext


class ArticleAdmin(admin.ModelAdmin):

    @admin.action
    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,
        )

回到浏览器,再试试,你会看到如下图所示(注意顶部的绿色提示行):

下面是message_user方法的定义:

def message_user(self, request, message, level=messages.INFO, extra_tags='',
                     fail_silently=False):

下面是ngettext的定义:

def ngettext(singular, plural, number):  # 单数、复数、数量
    return _trans.ngettext(singular, plural, number)

四、跳转到中间页面

默认情况下,执行完actions后,浏览器会返回先前的修改列表页面。

但有时候,一些复杂的action需要先跳转到中间页面。例如内置的删除方法,在执行删除动作之前,会弹出一个删除确认页面。

要实现这个功能,只需要在action方法中返回一个HttpResponse(或它的子类)。 例如下面是一个利用Django内置的序列化函数将一个对象保存为json格式的范例:

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

@admin.action
def export_as_json(modeladmin, request, queryset):
    response = HttpResponse(content_type="application/json")
    serializers.serialize("json", queryset, stream=response)
    return response

多数情况下,我们会使用HttpResponseRedirect跳转到一个中间页面,并在GET方法的url中携带所选择的对象作为参数传递过去,然后在这个新的视图中接收这个参数,并编写具体的更加复杂的业务逻辑,如下面的代码所示:

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

具体的业务views这里没有给出,作为练习,留给大家。

五、编写可用于整个站点的action

前面创建的actions只能应用于绑定的模型。实际上有时候,我们还需要可以对admin站点内所有模型都有效的acitons。上面写的export_selected_objects函数可以是一个很好的例子。要实现这一功能,你需要使用内置的AdminSite.add_action方法:

AdminSite.add_action(action, name=None)

事实上admin.site就是一个AdminSite的实例,所以可以如下面的方式调用:

from django.contrib import admin

admin.site.add_action(export_selected_objects)

也可以为动作指定新的名字:

admin.site.add_action(export_selected_objects, 'export_selected')

六、禁用acitons

有时候,对于某些actions,我们想全局禁用或者局部禁用它。需要使用AdminSite.disable_action(name)方法。

  • 禁用全站级别的acitons:

例如,禁用内置的删除方法:

admin.site.disable_action('delete_selected')
  • 全站禁用,但个别可用:在ModelAdmin.actions中显式地引用。

例如:

# 全站禁用删除功能
admin.site.disable_action('delete_selected')

# 这个老老实实的被禁了
class SomeModelAdmin(admin.ModelAdmin):
    actions = ['some_other_action']
    ...

# 这个声明:我还是要用,你别管我
class AnotherModelAdmin(admin.ModelAdmin):
    actions = ['delete_selected', 'a_third_action']
    ...
  • 在指定模型中禁用所有actions:设置ModelAdmin.actions为None。(这会连带全局actions一起禁用了。)
    class MyModelAdmin(admin.ModelAdmin):
        actions = None
  • 根据条件自动启用或禁用:

还可以根据条件自动选择性的启动或禁用某些acitons,你只需要改写ModelAdmin.get_actions()方法。

该方法将返回一个包含actions的字典。字典的键是aciton的名字(也就是前面的'delete_selected', 'a_third_action'之类),值是一个元组,包含(函数、名字、别名)

例如,只允许管理员批量删除对象,但其它用户不行:

class MyModelAdmin(admin.ModelAdmin):
    ...

    def get_actions(self, request):
        actions = super().get_actions(request)
        if request.user.username != "admin":
            if "delete_selected" in actions:
                del actions["delete_selected"]
        return actions

七、为actions设置权限

通过使用 action() 装饰器包装动作函数,并传递 permissions 参数,可以限制其权限。

@admin.action(permissions=["change"])
def make_published(modeladmin, request, queryset):
    queryset.update(status="p")

这样一来,只有能通过ModelAdmin.has_change_permission()方法进行权限检查的用户才可以执行make_published()动作。

如果permissions 属性有多个权限设定,那么只需要具备其中一个权限就可以执行动作。

可设置的权限短语和对应的检查方法如下:

  • 'add': ModelAdmin.has_add_permission()
  • 'change': ModelAdmin.has_change_permission()
  • 'delete': ModelAdmin.has_delete_permission()
  • 'view': ModelAdmin.has_view_permission()

当然,你也可以自定义权限,如下所示:

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

 自定制Admin Admin文档生成器 

评论总数: 1


点击登录后方可评论

有用