在Django管理员中禁用编辑对象链接(仅显示列表)?

56 投票
13 回答
41193 浏览
提问于 2025-04-15 15:20

在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

有没有什么办法可以让我得到一个没有任何编辑链接的对象列表呢?

13 个回答

24

在Django 1.7及之后的版本中,你可以这样做:

class HitAdmin(admin.ModelAdmin):
    list_display_links = None
45

要正确完成这个操作,需要两个步骤:

  • 隐藏编辑链接,这样就不会有人误点到详情页面(更改视图)。
  • 修改更改视图,让它能重新定向回列表视图。

第二步很重要:如果不这样做,人们仍然可以通过直接输入网址来访问更改视图(这显然不是你想要的)。这和OWASP所说的“不安全的直接对象引用”有很大关系。

在这个回答中,我会构建一个ReadOnlyMixin类,用来提供所有展示的功能。

隐藏编辑链接

Django 1.7让这变得非常简单:只需将list_display_links设置为None

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

这样我们的混合类就容易使用了:默认情况下它会隐藏编辑链接,但如果需要特定的管理视图,我们可以再把它加回来。

重定向到列表视图

我们可以通过重写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_view切换为True,你可以重新启用详情页面。

移除“添加项目”按钮

最后,你可能想要重写以下方法,以防止人们添加或删除新项目。

def has_add_permission(self, request):
    return False

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

这些更改将会:

  • 禁用“添加项目”按钮
  • 防止人们通过在网址后面加/add直接添加项目
  • 防止批量删除

最后,你可以通过修改actions参数来移除“删除选中的项目”操作。

把所有内容整合在一起

以下是完成的混合类:

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
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)会再次检查列表是否为空。

撰写回答