【问题标题】:Disable link to edit object in django's admin (display list only)?禁用链接以在 django 的管理员中编辑对象(仅显示列表)?
【发布时间】:2010-12-09 18:38:41
【问题描述】:

在 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

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

【问题讨论】:

  • 在 Django 1.11 中,list_display_links = None 禁用显示链接。
  • 在 django 2.0 中只需使用:list_display_links = None

标签: python django django-admin modeladmin


【解决方案1】:

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

我是这样工作的:

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

【讨论】:

  • 刚找到你的帖子,这对我也有用(在__init__ 中设置self.list_diplay_links = (None,)。谢谢!
  • 非答案提供了一种实际上不允许更改日志条目的方法。尽管用户看不到链接,但他可以通过键入编辑表单的 url 来访问编辑表单,并可以更改条目。用户必须拥有“can_change”权限才能查看更改列表视图。这引入了一个严重的安全漏洞。
  • @omat:您可以覆盖 ModelAdmin.change_view 以重定向到更改列表页面,或者您喜欢的任何地方,如果有人尝试手动访问该页面。我将在下面提供一个示例。
  • 这个解决方案只是破解问题本身!
  • 不幸的是,这在 Django 2.2 中似乎不起作用。现在 Django 给你错误The value of 'list_display_links[0]' refers to 'None', which is not defined in 'list_display'.
【解决方案2】:

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

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

第二部分很重要:如果您不这样做,那么人们仍然可以通过直接输入 URL 来访问更改视图(这可能是您不想要的)。这与 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,但这使得重用变得更加困难(我们只能覆盖构造函数一次)。

我认为更好的选择是重写 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 附加到 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

【讨论】:

  • 只是给未来读者的注意事项:此答案中使用的 super() 语法是 Py2 语法。使用 Py3 super() 语法,这个 mixin 更容易被任何模型重用,因为你不需要在 super() 调用中包含类名作为属性,它只是 super().change_view(...)
  • 这真的是最全面最棒的帖子!我建议为逻辑构建一个 mixin,以便您可以重用它并覆盖 change_view (如def change_view(self, *args, **kwargs)),以便对未来的更改更具弹性:)
  • 我添加了我的 mixin 作为单独的答案,如果有人需要配置和可重用性:stackoverflow.com/a/69461048/1331671?
【解决方案3】:

在 Django 1.7 及更高版本中,您可以这样做

class HitAdmin(admin.ModelAdmin):
    list_display_links = None

【讨论】:

    【解决方案4】:

    作为用户 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'))
    

    【讨论】:

    • +1 用于此方法,因为它允许在强制执行“禁令”之前与 request 对象进行交互。因此,例如可以基于request.user 甚至request.session 实现限制。
    【解决方案5】:

    在您的模型管理集中:

    list_display_links = (None,)
    

    应该这样做。 (无论如何都可以在 1.1.1 中使用。)

    文档链接:list_display_links

    【讨论】:

    • 嗯 - 我正在使用主干,但我在尝试时收到 TypeError: getattr(): attribute name must be string [02/Nov/2009 12:05:22] "GET /admin/ HTTP/1.1" 500 2524 错误。很有趣。
    • 嗯,很有趣。我刚刚检查了我正在执行的项目,它实际上运行的是 1.1 (r11602)。我刚刚尝试将我的项目升级到主干(r11706),它似乎仍然可以正常工作。但是,我确实有其他一些管理工作正在进行(您可以在此处查看joshourisman.com/2009/10/15/django-admin-awesomeness 的详细信息),因此将 list_display_links 设置为 (None,) 的 ModelAdmin 实际上不在我的 admin.py 中...我没有但是,不明白为什么这会有所不同。
    • 你的工作很奇怪;不知道它有什么不同......但是,我每次都能精确定位那行代码。奇怪。
    • 如果您在 ModelAdmin 中注释掉其他设置并保留它会怎样?
    • 我已经用一个完全空的 modelAdmin 进行了尝试,并且只有:list_display_links = (None,) 并且仍然得到相同的 TypeError ...如果它对你有用,但我觉得我一定做错了什么。
    【解决方案6】:

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

    【讨论】:

    • 这确实需要澄清一下!这也与 2017 年 8 月的 @sam 相同。
    【解决方案7】:

    只是为了注释,你可以修改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)
    

    这对我来说很好。

    【讨论】:

      【解决方案8】:

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

      @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
      

      【讨论】:

        【解决方案9】:

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

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

        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 接受,因为这会破坏代码目前明确执行的操作。

        【讨论】:

        • 谢谢你 - 它也适用于我。您是否看到一种明显/pythonic 的方式来修补源代码 如果孩子将其设置为空,则不要做任何事情 // 但如果孩子根本没有设置它,请做一些事情 ?可以将它作为一个补丁提交......但在这一点上,我所有的补丁想法都是相当骇人听闻的。
        • 谢谢 - 也许如果他们向ModelAdmin 添加了另一个变量,例如您建议的unset_list_display_links [True/False],那么他们可以在同一个if 语句中检查他们正在检查@987654329 @ ...再一次,他们可能只是建议按照您的方式覆盖它。
        • 仔细检查后,我注意到该链接现在已添加到选择框中 - 因此,当您选择一个项目时,它会打开页面!呃。
        • 这是 Django 的(有时是令人愤怒的)管理策略的一部分:用户应该被信任。因此,没有内置的方法来限制对用户本来可以访问的内容的访问。补丁无疑会被拒绝,但仍有办法绕过它。
        【解决方案10】:

        您也可能对它可笑 hacky(如果您不想大惊小怪地覆盖<strong>init</strong>)并为第一个元素提供一个值,基本上看起来像这样:

        </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() 来提高输出的可读性(因为您不希望它是一个链接),根据您的用例,这可能有点简洁。

        【讨论】:

        • 这也是一种有趣的方法——虽然上面的__init__ 策略可能会破坏它,但它似乎更直观......但这给了我一些想法,无论如何。谢谢。
        【解决方案11】:

        使用 django 1.6.2 你可以这样做:

        class MyAdmin(admin.ModelAdmin):
        
            def get_list_display_links(self, request, list_display):
                return []
        

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

        【讨论】:

        • 这使得复选框成为一个链接。
        【解决方案12】:

        我将 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
        

        【讨论】:

          【解决方案13】:

          我基于 @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
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2021-08-14
            • 1970-01-01
            • 2016-01-28
            • 2015-04-26
            • 2012-04-24
            • 1970-01-01
            • 2019-05-14
            相关资源
            最近更新 更多