在django的admin(仅显示列表)中禁用编辑对象的链接?

时间:2009-10-24 18:42:22

标签: python django django-admin modeladmin

在Django的管理员中,我希望禁用“选择要更改的项目”页面上提供的链接,以便用户无法去任何地方编辑项目。 (我将限制用户可以对此列表执行的操作到一组下拉操作 - 没有实际编辑字段。)

我看到Django有choose which fields display the link的能力,但是,我看不出我怎么能拥有 none

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

如何获取我的对象列表而没有任何编辑链接?

12 个答案:

答案 0 :(得分:59)

我只想将日志查看器作为列表。

我得到了这样的工作:

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

答案 1 :(得分:30)

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

  • 隐藏编辑链接,因此没有人错误地在详细信息页面(更改视图)上绊倒。
  • 修改更改视图以重定向回列表视图。

第二部分很重要:如果你不这样做,那么人们仍然可以通过直接输入网址来访问更改视图(这可能是你不想要的)。这与OWASP的术语"Insecure Direct Object Reference"密切相关。

作为这个答案的一部分,我将构建一个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,但是这使得重用更加困难(我们只能覆盖构造函数一次)。 / p>

我认为更好的选择是覆盖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_view切换为True,您可以重新打开详细信息页面。

删除"添加 ITEM "按钮

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

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

答案 2 :(得分:18)

作为上述评论中提到的用户,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'))

答案 3 :(得分:11)

在Django 1.7及更高版本中,你可以做到

class HitAdmin(admin.ModelAdmin):
    list_display_links = None

答案 4 :(得分:7)

在您的模型管理集中:

list_display_links = (None,)

应该这样做。 (无论如何都适用于1.1.1。)

链接到文档:list_display_links

答案 5 :(得分: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会接受任何补丁,因为这会破坏代码目前明确做的事情。

答案 6 :(得分:2)

仅为备注,您可以修改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)

这对我来说很好。

答案 7 :(得分:1)

你也可能 荒谬 hacky(如果你不想对重写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()来提高输出的可读性(因为您不希望它成为链接),这可能是有点整洁,具体取决于您的用例。

答案 8 :(得分:1)

使用django 1.6.2你可以这样做:

class MyAdmin(admin.ModelAdmin):

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

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

答案 9 :(得分:1)

只需在管理员中写入sdk-1.38.28-64bit

答案 10 :(得分:1)

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

let newArray = lodash.uniqBy(lodash.concat(myArray, 'subdata.subid'), '_id');

答案 11 :(得分:0)

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

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