简体   繁体   English

在 django 的管理员中禁用编辑 object 的链接(仅显示列表)?

[英]Disable link to edit object in django's admin (display list only)?

In Django's admin, I want disable the links provided on the "select item to change" page so that users cannot go anywhere to edit the item.在 Django 的管理员中,我想禁用“选择要更改的项目”页面上提供的链接,以便用户无法在任何地方编辑项目 go。 (I am going to limit what the users can do with this list to a set of drop down actions - no actual editing of fields). (我打算将用户可以使用此列表执行的操作限制为一组下拉操作 - 没有实际编辑字段)。

I see that Django has the ability to choose which fields display the link , however, I can't see how I can have none of them.我看到 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

Any ideas how to get my object list without any links to edit?任何想法如何在没有任何编辑链接的情况下获取我的 object 列表?

I wanted a Log viewer as a list only.我只想要一个日志查看器作为列表。

I got it working like this:我让它像这样工作:

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

It is kind of a mix between both answers.这是两种答案的混合。

If you just do self.list_display_links = () it will show the link, Anyway because the template-tag code (templatetags/admin_list.py) checks again to see if the list is empty.如果你只是做self.list_display_links = ()它会显示链接,因为template-tag代码 (templatetags/admin_list.py) 再次检查列表是否为空。

Doing this properly requires two steps:正确执行此操作需要两个步骤:

  • Hide the edit link, so nobody stumbles on the detail page (change view) by mistake.隐藏编辑链接,因此没有人会错误地在详细信息页面(更改视图)上绊倒。
  • Modify the change view to redirect back to the list view.修改更改视图以重定向回列表视图。

The second part is important: if you don't do this then people will still be able to access the change view by entering a URL directly (which presumably you don't want).第二部分很重要:如果您不这样做,那么人们仍然可以通过直接输入 URL 来访问更改视图(这可能是您不想要的)。 This is closely related to what OWASP term an "Insecure Direct Object Reference" .这与 OWASP 术语“不安全的直接对象引用”密切相关。

As part of this answer I'll build a ReadOnlyMixin class that can be used to provide all the functionality shown.作为此答案的一部分,我将构建一个ReadOnlyMixin类,该类可用于提供显示的所有功能。

Hiding the Edit Link隐藏编辑链接

Django 1.7 makes this really easy: you just set list_display_links to None . Django 1.7 使这变得非常简单:您只需将list_display_links设置为None

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

Django 1.6 (and presumably earlier) don't make this so simple. Django 1.6(大概更早版本)并没有让这变得如此简单。 Quite a lot of answers to this question have suggested overriding __init__ in order to set list_display_links after the object has been constructed, but this makes it harder to reuse (we can only override the constructor once).这个问题的很多答案都建议覆盖__init__以在构造对象后设置list_display_links ,但这使得重用变得更加困难(我们只能覆盖一次构造函数)。

I think a better option is to override Django's get_list_display_links method as follows:我认为更好的选择是覆盖 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,)

This makes our mixin easy to use: it hides the edit link by default but allows us to add it back in if required for a particular admin view.这使我们的 mixin 易于使用:它默认隐藏编辑链接,但允许我们在特定管理视图需要时将其添加回来。

Redirecting to the List View重定向到列表视图

We can change the behaviour of the detail page (change view) by overriding the change_view method.我们可以通过覆盖change_view方法来更改详细信息页面的行为(更改视图)。 Here's an extension to the technique suggested by Chris Pratt which automatically finds the right page:这是 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)

Again this is customisable - by toggling enable_change_view to True you can switch the details page back on.同样,这是可自定义的 - 通过将enable_change_view切换为True您可以重新打开详细信息页面。

Removing the "Add ITEM " Button删除“添加项目”按钮

Finally, you might want to override the following methods in order to prevent people adding or deleting new items.最后,您可能希望覆盖以下方法以防止人们添加或删除新项目。

def has_add_permission(self, request):
    return False

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

These changes will:这些变化将:

  • disable the "Add item " button禁用“添加项目”按钮
  • prevent people directly adding items by appending /add to the URL防止人们通过将/add到 URL 来直接添加项目
  • prevent bulk delete防止批量删除

Finally you can remove the "Delete selected items " action by modifying the actions parameter.最后,您可以通过修改actions参数来删除“删除所选项目”操作。

Putting it all together把这一切放在一起

Here's the completed 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

In Django 1.7 and later, you can do在 Django 1.7 及更高版本中,你可以这样做

class HitAdmin(admin.ModelAdmin):
    list_display_links = None

As user, omat, mentioned in a comment above, any attempt to simply remove the links does not prevent users from still accessing the change page manually.作为用户 omat,在上面的评论中提到,任何简单地删除链接的尝试都不会阻止用户仍然手动访问更改页面。 However, that, too, is easy enough to remedy:然而,这也很容易补救:

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

In your model admin set:在您的模型管理集中:

list_display_links = (None,)

That should do it.那应该这样做。 (Works in 1.1.1 anyway.) (无论如何都可以在 1.1.1 中使用。)

Link to docs: list_display_links链接到文档: list_display_links

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

Just for the notes, you may modify changelist_view:只是为了笔记,你可以修改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)

This works fine for me.这对我来说很好用。

In more "recent" versions of Django, since at least 1.9, it is possible to simple determine the add, change and delete permissions on the admin class.在 Django 的“最新”版本中,至少从 1.9 开始,可以简单地确定管理类的添加、更改和删除权限。 See the django admin documentation for reference.请参阅django 管理文档以供参考。 Here is an example:下面是一个例子:

@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

There isn't a supported way to do this.没有支持的方法来做到这一点。

Looking at the code, it seems that it automatically sets ModelAdmin.list_display_links to the first element if you don't set it to anything.查看代码,如果您不将其设置为任何内容,它似乎会自动将ModelAdmin.list_display_links设置为第一个元素。 So the easiest way might be to override the __init__ method in your ModelAdmin subclass to unset that attribute on initialization:因此,最简单的方法可能是覆盖ModelAdmin子类中的__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 = []

This appears to work, after a very cursory test.经过非常粗略的测试,这似乎有效。 I can't guarantee that it won't break anything elsewhere, or that it won't be broken by future changes to Django, though.不过,我不能保证它不会在其他地方破坏任何东西,或者它不会被未来对 Django 的更改所破坏。

Edit after comment :评论后编辑

No need to patch the source, this would work:无需修补源,这会起作用:

    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 = []

But I highly doubt any patch would be accepted into Django, since this breaks something that the code explicitly does at the moment.但我非常怀疑 Django 是否会接受任何补丁,因为这破坏了代码目前明确所做的事情。

You could also be ridiculously hacky about it (if you didn't want to fuss with overriding init ) and provide a value for the first element that basically looks like this:您也可能对此感到可笑(如果您不想为覆盖init大惊小怪)并为第一个元素提供一个基本上如下所示的值:

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

I know, I know, not very pretty, but perhaps less anxiety about breaking something elsewhere since all we're doing is changing markup.我知道,我知道,不是很漂亮,但也许不会那么担心破坏其他地方的东西,因为我们所做的只是改变标记。

Here's some sample code about how this works:下面是一些关于它是如何工作的示例代码:

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"

Side Note: You could also improve the readability of the output (since you don't want it to be a link) by returning return u'%s' % obj.get_full_name() which might be kinda neat depending on your use case.旁注:您还可以通过返回return u'%s' % obj.get_full_name()来提高输出的可读性(因为您不希望它成为链接),这取决于您的用例,这可能有点整洁。

with django 1.6.2 you can do like this:使用 django 1.6.2 你可以这样做:

class MyAdmin(admin.ModelAdmin):

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

it will hide all auto generated links.它将隐藏所有自动生成的链接。

I overrided get_list_display_links method and action to None.我将 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

I build a mixin based on @simpleigh 's solution.我基于@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)

The benefit is that you can reuse it and you can furthermore make it conditional好处是您可以重用它,还可以使其成为有条件的

Build for django 3.2.8.为 django 3.2.8 构建。

To be used like this for a static approach:像这样用于 static 方法:

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

And like this for a non-static one:像这样的非静态的:

class MyAdmin(DeactivatableChangeViewAdminMixin, admin.ModelAdmin):  
    def can_see_change_view(self, request) -> bool:
        return request.user.my_condition

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM