Django Admin:has_delete_permission忽略“删除”操作

时间:2021-07-30 20:23:01

Let's say that I have a model where the row that has an ID of 1 is special and should not be able to be deleted, but all the other rows are fine to delete. Here is my attempt at implementing that logic:

假设我有一个模型,其中ID为1的行是特殊的,不应该被删除,但所有其他行都可以删除。这是我尝试实现该逻辑:

models.py

from django.db import models


class Widget(models.Model):
    name = models.CharField(max_length=255)

    class Meta:
        ordering = ('name',)

    def __unicode__(self):
        return self.name

admin.py

from django.contrib import admin

from .models import Widget


class WidgetAdmin(admin.ModelAdmin):
    def has_delete_permission(self, request, obj=None):
        return obj is None or obj.pk != 1

admin.site.register(Widget, WidgetAdmin)

The above code removes the "Delete" button from the change form when obj.pk is 1, which is what I want. However, on the change list, if I check the checkbox for the row with an ID of 1 and then use the "Delete selected widgets" action, I am able to delete that row. I want to prevent that, but still allow all of the other rows to be deleted with the "Delete selected widgets" action. How can I do this?

当obj.pk为1时,上面的代码从更改表单中删除“删除”按钮,这就是我想要的。但是,在更改列表中,如果我选中ID为1的行的复选框,然后使用“删除所选小部件”操作,我可以删除该行。我想阻止它,但仍然允许使用“删除所选小部件”操作删除所有其他行。我怎样才能做到这一点?

2 个解决方案

#1


8  

According to has_delete_permission's docstring:

根据has_delete_permission的docstring:

def has_delete_permission(self, request, obj=None):
    """
    Returns True if the given request has permission to change the given
    Django model instance, ...
    """

This means has_delete_permission is executed per request, not per object. On a bulk action, obj is not set. However you may examine request:

这意味着每个请求执行has_delete_permission,而不是每个对象。在批量操作中,未设置obj。但是,您可以检查请求:

def has_delete_permission(self, request, obj=None):
    if request.POST and request.POST.get('action') == 'delete_selected':
        return '1' not in request.POST.getlist('_selected_action')
    return obj is None or obj.pk != 1

Note that the above works because the delete_selected action takes has_delete_permission into account.

请注意,上述方法有效,因为delete_selected操作考虑了has_delete_permission。

You may also want to provide some details about the error:

您可能还想提供有关错误的一些详细信息:

from django.contrib import messages

def has_delete_permission(self, request, obj=None):
    if request.POST and request.POST.get('action') == 'delete_selected':
        if '1' in request.POST.getlist('_selected_action'):
            messages.add_message(request, messages.ERROR, (
                "Widget #1 is protected, please remove it from your selection "
                "and try again."
            ))
            return False
        return True
    return obj is None or obj.pk != 1

I guess has_delete_permission is called per request rather than per object for performance reasons. In the general case, it is useless to make a SELECT query and loop over has_delete_permission (which may be time consuming according to what it does) prior to running the DELETE query. And when it's relevant to do so, it's up to the developer to take the necessary steps.

我猜想,出于性能原因,每个请求而不是每个对象都会调用has_delete_permission。在一般情况下,在运行DELETE查询之前,进行SELECT查询并循环遍历has_delete_permission(根据其工作可能会耗费时间)是没用的。当与此相关时,由开发人员采取必要的步骤。

#2


7  

You can replace the admin's implementation of the delete_selected action with your own. Something like:

您可以用自己的admin替换admin的delete_selected操作实现。就像是:

from django.contrib.admin import actions

class WidgetAdmin(admin.ModelAdmin):
    actions = [delete_selected]

    def delete_selected(self, request, queryset):
        # Handle this however you like. You could raise PermissionDenied,
        # or just remove it, and / or use the messages framework...
        queryset = queryset.exclude(pk=1)

        actions.delete_selected(self, request, queryset)
    delete_selected.short_description = "Delete stuff"

See the documentation for more details.

有关详细信息,请参阅文档。

#1


8  

According to has_delete_permission's docstring:

根据has_delete_permission的docstring:

def has_delete_permission(self, request, obj=None):
    """
    Returns True if the given request has permission to change the given
    Django model instance, ...
    """

This means has_delete_permission is executed per request, not per object. On a bulk action, obj is not set. However you may examine request:

这意味着每个请求执行has_delete_permission,而不是每个对象。在批量操作中,未设置obj。但是,您可以检查请求:

def has_delete_permission(self, request, obj=None):
    if request.POST and request.POST.get('action') == 'delete_selected':
        return '1' not in request.POST.getlist('_selected_action')
    return obj is None or obj.pk != 1

Note that the above works because the delete_selected action takes has_delete_permission into account.

请注意,上述方法有效,因为delete_selected操作考虑了has_delete_permission。

You may also want to provide some details about the error:

您可能还想提供有关错误的一些详细信息:

from django.contrib import messages

def has_delete_permission(self, request, obj=None):
    if request.POST and request.POST.get('action') == 'delete_selected':
        if '1' in request.POST.getlist('_selected_action'):
            messages.add_message(request, messages.ERROR, (
                "Widget #1 is protected, please remove it from your selection "
                "and try again."
            ))
            return False
        return True
    return obj is None or obj.pk != 1

I guess has_delete_permission is called per request rather than per object for performance reasons. In the general case, it is useless to make a SELECT query and loop over has_delete_permission (which may be time consuming according to what it does) prior to running the DELETE query. And when it's relevant to do so, it's up to the developer to take the necessary steps.

我猜想,出于性能原因,每个请求而不是每个对象都会调用has_delete_permission。在一般情况下,在运行DELETE查询之前,进行SELECT查询并循环遍历has_delete_permission(根据其工作可能会耗费时间)是没用的。当与此相关时,由开发人员采取必要的步骤。

#2


7  

You can replace the admin's implementation of the delete_selected action with your own. Something like:

您可以用自己的admin替换admin的delete_selected操作实现。就像是:

from django.contrib.admin import actions

class WidgetAdmin(admin.ModelAdmin):
    actions = [delete_selected]

    def delete_selected(self, request, queryset):
        # Handle this however you like. You could raise PermissionDenied,
        # or just remove it, and / or use the messages framework...
        queryset = queryset.exclude(pk=1)

        actions.delete_selected(self, request, queryset)
    delete_selected.short_description = "Delete stuff"

See the documentation for more details.

有关详细信息,请参阅文档。