52

在 Django 的管理员中,我想禁用“选择要更改的项目”页面上提供的链接,以便用户无法去任何地方编辑该项目。(我将把用户可以用这个列表做的事情限制在一组下拉操作中——没有实际的字段编辑)。

我看到 Django 有能力选择哪些字段显示链接,但是,我看不出我怎么能没有它们。

class HitAdmin(admin.ModelAdmin):
    list_display = ('user','ip','user_agent','hitcount')
    search_fields = ('ip','user_agent')
    date_hierarchy = 'created'
    list_display_links = [] # doesn't work, goes to default

任何想法如何在没有任何编辑链接的情况下获取我的对象列表?

4

13 回答 13

64

我只想要一个日志查看器作为列表。

我让它像这样工作:

class LogEntryAdmin(ModelAdmin):
    actions = None
    list_display = (
        'action_time', 'user',
        'content_type', 'object_repr', 
        'change_message')

    search_fields = ['=user__username', ]
    fieldsets = [
        (None, {'fields':()}), 
        ]

    def __init__(self, *args, **kwargs):
        super(LogEntryAdmin, self).__init__(*args, **kwargs)
        self.list_display_links = (None, )

这是两种答案之间的混合。

如果您只是这样做self.list_display_links = (),它将显示链接,无论如何,因为template-tag代码(templatetags/admin_list.py)再次检查以查看列表是否为空。

于 2009-12-30T21:02:14.110 回答
41

正确执行此操作需要两个步骤:

  • 隐藏编辑链接,所以没有人会错误地发现详细信息页面(更改视图)。
  • 修改更改视图以重定向回列表视图。

第二部分很重要:如果您不这样做,那么人们仍然可以通过直接输入 URL 来访问更改视图(这可能是您不想要的)。这与 OWASP 术语“不安全的直接对象引用”密切相关。

作为此答案的一部分,我将构建一个ReadOnlyMixin可用于提供显示的所有功能的类。

隐藏编辑链接

Django 1.7 使这变得非常简单:您只需list_display_linksNone.

class ReadOnlyMixin(): # Add inheritance from "object" if using Python 2
    list_display_links = None

Django 1.6(可能更早)并没有让这变得如此简单。这个问题的很多答案都建议重写__init__以便list_display_links在构造对象后设置,但这使得重用变得更加困难(我们只能重写构造函数一次)。

我认为更好的选择是覆盖 Django 的get_list_display_links方法,如下所示:

def get_list_display_links(self, request, list_display):
    """
    Return a sequence containing the fields to be displayed as links
    on the changelist. The list_display parameter is the list of fields
    returned by get_list_display().

    We override Django's default implementation to specify no links unless
    these are explicitly set.
    """
    if self.list_display_links or not list_display:
        return self.list_display_links
    else:
        return (None,)

这使我们的 mixin 易于使用:默认情况下它隐藏了编辑链接,但允许我们在特定管理视图需要时将其添加回来。

重定向到列表视图

change_view我们可以通过重写该方法来更改详细信息页面的行为(更改视图) 。这是 Chris Pratt 建议的技术的扩展,它会自动找到正确的页面:

enable_change_view = False

def change_view(self, request, object_id, form_url='', extra_context=None):
    """
    The 'change' admin view for this model.

    We override this to redirect back to the changelist unless the view is
    specifically enabled by the "enable_change_view" property.
    """
    if self.enable_change_view:
        return super(ReportMixin, self).change_view(
            request,
            object_id,
            form_url,
            extra_context
        )
    else:
        from django.core.urlresolvers import reverse
        from django.http import HttpResponseRedirect

        opts = self.model._meta
        url = reverse('admin:{app}_{model}_changelist'.format(
            app=opts.app_label,
            model=opts.model_name,
        ))
        return HttpResponseRedirect(url)

同样,这是可自定义的 - 通过切换enable_change_viewTrue您可以重新打开详细信息页面。

删除“添加项目”按钮

最后,您可能希望覆盖以下方法以防止人们添加或删除新项目。

def has_add_permission(self, request):
    return False

def has_delete_permission(self, request, obj=None):
    return False

这些变化将:

  • 禁用“添加项目”按钮
  • /add通过附加到 URL 来防止人们直接添加项目
  • 防止批量删除

最后,您可以通过修改参数来删除“删除选定项目”操作。actions

把它们放在一起

这是完成的mixin:

from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect

class ReadOnlyMixin(): # Add inheritance from "object" if using Python 2

    actions = None

    enable_change_view = False

    def get_list_display_links(self, request, list_display):
        """
        Return a sequence containing the fields to be displayed as links
        on the changelist. The list_display parameter is the list of fields
        returned by get_list_display().

        We override Django's default implementation to specify no links unless
        these are explicitly set.
        """
        if self.list_display_links or not list_display:
            return self.list_display_links
        else:
            return (None,)

    def change_view(self, request, object_id, form_url='', extra_context=None):
        """
        The 'change' admin view for this model.

        We override this to redirect back to the changelist unless the view is
        specifically enabled by the "enable_change_view" property.
        """
        if self.enable_change_view:
            return super(ReportMixin, self).change_view(
                request,
                object_id,
                form_url,
                extra_context
            )
        else:
            opts = self.model._meta
            url = reverse('admin:{app}_{model}_changelist'.format(
                app=opts.app_label,
                model=opts.model_name,
            ))
            return HttpResponseRedirect(url)

    def has_add_permission(self, request):
        return False

    def has_delete_permission(self, request, obj=None):
        return False
于 2014-10-22T18:50:37.573 回答
21

在 Django 1.7 及更高版本中,您可以执行

class HitAdmin(admin.ModelAdmin):
    list_display_links = None
于 2014-03-13T13:56:36.377 回答
20

正如用户 omat 在上面的评论中提到的那样,任何简单地删除链接的尝试都不会阻止用户仍然手动访问更改页面。但是,这也很容易补救:

class MyModelAdmin(admin.ModelAdmin)
    # Other stuff here
    def change_view(self, request, obj=None):
        from django.core.urlresolvers import reverse
        from django.http import HttpResponseRedirect
        return HttpResponseRedirect(reverse('admin:myapp_mymodel_changelist'))
于 2011-04-29T21:04:02.740 回答
8

在您的模型管理集中:

list_display_links = (None,)

那应该这样做。(无论如何都在 1.1.1 中工作。)

链接到文档:list_display_links

于 2009-11-02T02:09:42.527 回答
5

只需list_display_links = None在您的管理员中写

于 2019-03-06T09:47:47.190 回答
4

只是为了注释,您可以修改changelist_view:

class SomeAdmin(admin.ModelAdmin):
    def changelist_view(self, request, extra_context=None):
        self.list_display_links = (None, )
        return super(SomeAdmin, self).changelist_view(request, extra_context=None)

这对我来说很好。

于 2013-03-22T08:16:51.793 回答
3

在更“最新”的 Django 版本中,至少从 1.9 开始,可以简单地确定管理类的添加、更改和删除权限。请参阅django 管理文档以供参考。这是一个例子:

@admin.register(Object)
class Admin(admin.ModelAdmin):

    def has_add_permission(self, request):
        return False

    def has_change_permission(self, request, obj=None):
        return False

    def has_delete_permission(self, request, obj=None):
        return False
于 2019-11-21T21:32:26.160 回答
2

没有支持的方式来执行此操作。

查看代码,ModelAdmin.list_display_links如果您不将其设置为任何内容,它似乎会自动设置为第一个元素。因此,最简单的方法可能是覆盖子类中的__init__方法以ModelAdmin在初始化时取消设置该属性:

class HitAdmin(admin.ModelAdmin):
    list_display = ('user','ip','user_agent','hitcount')
    search_fields = ('ip','user_agent')
    date_hierarchy = 'created'

    def __init__(self, *args, **kwargs):
        super(HitAdmin, self).__init__(*args, **kwargs)
        self.list_display_links = []

经过非常粗略的测试后,这似乎可行。不过,我不能保证它不会破坏其他地方的任何东西,或者它不会被未来对 Django 的更改破坏。

评论后编辑

无需修补源代码,这将起作用:

    def __init__(self, *args, **kwargs):
        if self.list_display_links:
            unset_list_display = True
        else:
            unset_list_display = False
        super(HitAdmin, self).__init__(*args, **kwargs)
        if unset_list_display:
            self.list_display_links = []

但我非常怀疑任何补丁都会被 Django 接受,因为这会破坏代码目前明确执行的操作。

于 2009-10-24T21:03:27.640 回答
1

您也可能对此感到荒谬(如果您不想对覆盖大惊小怪init)并为第一个元素提供一个基本上如下所示的值:

</a>My non-linked value<a>

我知道,我知道,不是很漂亮,但也许不那么担心在其他地方破坏某些东西,因为我们所做的只是改变标记。

这是一些有关其工作原理的示例代码:

class HitAdmin(admin.ModelAdmin):
    list_display = ('user_no_link','ip','user_agent','hitcount')

    def user_no_link(self, obj):
        return u'</a>%s<a>' % obj
    user_no_link.allow_tags = True
    user_no_link.short_description = "user"

旁注:您还可以通过返回它来提高输出的可读性(因为您不希望它成为链接),return u'%s' % obj.get_full_name()这取决于您的用例。

于 2009-10-24T21:26:40.630 回答
1

使用 django 1.6.2 你可以这样做:

class MyAdmin(admin.ModelAdmin):

    def get_list_display_links(self, request, list_display):
        return []

它将隐藏所有自动生成的链接。

于 2014-02-21T02:16:11.797 回答
0

我将 get_list_display_links 方法和操作覆盖为 None。

class ChangeLogAdmin(admin.ModelAdmin):
    actions = None
    list_display = ('asset', 'field', 'before_value', 'after_value', 'operator', 'made_at')

    fieldsets = [
        (None, {'fields': ()}),
    ]

    def __init__(self, model, admin_site):
        super().__init__(model, admin_site)

    def get_list_display_links(self, request, list_display):
        super().get_list_display_links(request, list_display)
        return None
于 2020-02-23T10:14:22.760 回答
0

我基于@simpleigh 的解决方案构建了一个mixin。

class DeactivatableChangeViewAdminMixin:
    """
    Mixin to be used in model admins to disable the detail page / change view.
    """
    enable_change_view = True

    def can_see_change_view(self, request) -> bool:
        """
        This method determines if the change view is disabled or visible.
        """
        return self.enable_change_view

    def get_list_display_links(self, request, list_display):
        """
        When we don't want to show the change view, there is no need for having a link to it
        """
        if not self.can_see_change_view(request=request):
            return None
        return super().get_list_display_links(request, list_display)

    def change_view(self, request, *args, **kwargs):
        """
        The 'change' admin view for this model.

        We override this to redirect back to the changelist unless the view is
        specifically enabled by the "enable_change_view" property.
        """
        if self.can_see_change_view(request=request):
            return super().change_view(request, *args, **kwargs)
        else:
            opts = self.model._meta
            url = reverse('admin:{app}_{model}_changelist'.format(
                app=opts.app_label,
                model=opts.model_name,
            ))
            return HttpResponseRedirect(url)

好处是您可以重用它,并且可以进一步使其有条件

为 django 3.2.8 构建。

像这样用于静态方法:

class MyAdmin(DeactivatableChangeViewAdminMixin, admin.ModelAdmin):
  enable_change_view = False

对于非静态的,就像这样:

class MyAdmin(DeactivatableChangeViewAdminMixin, admin.ModelAdmin):  
    def can_see_change_view(self, request) -> bool:
        return request.user.my_condition
于 2021-10-06T06:48:26.610 回答