在Django管理员中禁用编辑对象链接(仅显示列表)?
在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 个回答
在Django 1.7及之后的版本中,你可以这样做:
class HitAdmin(admin.ModelAdmin):
list_display_links = None
要正确完成这个操作,需要两个步骤:
- 隐藏编辑链接,这样就不会有人误点到详情页面(更改视图)。
- 修改更改视图,让它能重新定向回列表视图。
第二步很重要:如果不这样做,人们仍然可以通过直接输入网址来访问更改视图(这显然不是你想要的)。这和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
我想要一个只显示列表的日志查看器。
我这样做让它工作起来了:
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)会再次检查列表是否为空。