Django管理界面中的只读模型?

101

如何在管理界面中使模型完全只读?这是针对一种日志表的情况,我使用管理功能进行搜索、排序、过滤等操作,但没有必要修改日志。

如果看起来像一个重复的问题,那么这不是我想做的:

  • 我不是在寻找只读字段(即使将每个字段设为只读,仍然可以创建新记录)
  • 我不是在创建只读用户:每个用户都应该是只读的。

3
这个功能应该很快就会推出:https://github.com/django/django/pull/5297 - Bosco
4
has_view_permission 在 Django 2.1 中终于得到了实现。另请参见下面的 https://dev59.com/emsy5IYBdhLWcg3w3hyI#51641149。 - djvg
14个回答

77
管理员是用来编辑的,而不仅仅是查看(你不会找到“查看”权限)。为了实现你想要的效果,你需要禁止添加和删除,并将所有字段设置为只读。
class MyAdmin(ModelAdmin):

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

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

(如果你禁止更改,你甚至都看不到这些对象)
对于一些未经测试的代码,试图自动设置所有字段为只读,请参考我的回答将整个模型设为只读 编辑:这将使所有字段变为只读。
readonly_fields = [field.name for field in MyModel._meta.get_fields()]

编辑:QuerySet.delete()仍然可以批量删除对象。为了解决这个问题,您可以提供自己的“objects”管理器和相应的QuerySet子类,该子类不会执行删除操作-请参阅Django中重写QuerySet.delete()

2
P.S.: 是的,就像其他答案中所说的那样,最好的方法可能是在一个ReadOnlyAdmin类中定义这三个内容,然后在需要该行为的任何地方进行子类化。甚至可以变得花哨一些,允许定义可以编辑的组/权限,并相应地返回True(并使用具有访问请求和当前用户的get_readonly_fields())。 - Danny W. Adair
几乎完美。我能贪心地问一下是否有不将行链接到编辑页面的方法吗?(再次强调,没有必要放大任何行,也没有必要编辑任何内容) - Steve Bennett
1
如果您将ModelAdmin的list_display_links设置为某些计算结果为False的内容(例如空列表/元组),ModelAdmin.init()会将list_display_links设置为所有列(除了操作复选框)-请参见options.py。我猜这是为了确保有链接存在。因此,我会在ReadOnlyAdmin中覆盖__init__(),调用父类,然后将list_display_links设置为空列表或元组。考虑到您现在不会有指向只读更改表单的链接,最好为此创建一个参数/类属性-我认为这不是通常所期望的行为。希望对您有所帮助。 - Danny W. Adair
关于从模型设置readonly_fields,如果您覆盖表单并添加其他字段,则可能不起作用...基于实际表单字段可能更好。 - Danny W. Adair
我不明白为什么你要对这个答案进行负投票 - 它只是在处理一个现有的混乱! Django 票号 #820 在9年前被打开,标记为“不修复”。我仍然想不出更干净的 ReadOnlyModelAdmin。顺便说一句,理想的方式不会明确地给用户“查看”权限 - 如果他们没有它(可能是通过“更改”权限“隐含”),那么用户将无法查看...根据当前管理员逻辑,该模型将根本不会显示在该用户的管理员屏幕上。 - Danny W. Adair
显示剩余4条评论

57

这里有两个类,我正在使用它们来使模型及其内联变成只读。

对于模型管理:

from django.contrib import admin

class ReadOnlyAdmin(admin.ModelAdmin):
    readonly_fields = []

    def get_readonly_fields(self, request, obj=None):
        return list(self.readonly_fields) + \
               [field.name for field in obj._meta.fields] + \
               [field.name for field in obj._meta.many_to_many]


    def has_add_permission(self, request):
        return False

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

class MyModelAdmin(ReadOnlyAdmin):
    pass

对于内联元素:

class ReadOnlyTabularInline(admin.TabularInline):
    extra = 0
    can_delete = False
    editable_fields = []
    readonly_fields = []
    exclude = []

    def get_readonly_fields(self, request, obj=None):
        return list(self.readonly_fields) + \
               [field.name for field in self.model._meta.fields
                if field.name not in self.editable_fields and
                   field.name not in self.exclude]

    def has_add_permission(self, request):
        return False


class MyInline(ReadOnlyTabularInline):
    pass

如何将两个类应用于一个子类。例如,如果我在一个类中有普通字段和内联字段?我可以同时扩展它们吗? - Timo
@timo 使用这些类 作为混入 - MartinM
1
ReadOnlyAdmin 中的 has_add_permission 方法只接受请求参数。 - MartinM
has_change_permission() 方法也需要被重写。 def has_change_permission(self, request, obj=None): - david euler

24

参见https://djangosnippets.org/snippets/10539/

class ReadOnlyAdminMixin(object):
    """Disables all editing capabilities."""
    change_form_template = "admin/view.html"

    def __init__(self, *args, **kwargs):
        super(ReadOnlyAdminMixin, self).__init__(*args, **kwargs)
        self.readonly_fields = [f.name for f in self.model._meta.get_fields()]

    def get_actions(self, request):
        actions = super(ReadOnlyAdminMixin, self).get_actions(request)
        del_action = "delete_selected"
        if del_action in actions:
            del actions[del_action]
        return actions

    def has_add_permission(self, request):
        return False

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

    def save_model(self, request, obj, form, change):
        pass

    def delete_model(self, request, obj):
        pass

    def save_related(self, request, form, formsets, change):
        pass

模板/管理员/视图.html

{% extends "admin/change_form.html" %}
{% load i18n %}

{% block submit_buttons_bottom %}
  <div class="submit-row">
    <a href="../">{% blocktrans %}Back to list{% endblocktrans %}</a>
  </div>
{% endblock %}

模板/admin/view.html(适用于Grappelli)

{% extends "admin/change_form.html" %}
{% load i18n %}

{% block submit_buttons_bottom %}
  <footer class="grp-module grp-submit-row grp-fixed-footer">
    <header style="display:none"><h1>{% trans "submit options"|capfirst context "heading" %}</h1></header>
    <ul>
       <li><a href="../" class="grp-button grp-default">{% blocktrans %}Back to list{% endblocktrans %}</a></li>
    </ul>
  </footer>
{% endblock %}

听起来不错。不过我已经很久没有使用Django了,可能要等待其他评论者的意见。 - Steve Bennett
这是为 Model 还是为 ModelAdmin 编写的 mixin? - OrangeDog
这是为 ModelAdmin 设计的。 - Pascal Polleunus
对于Django 1.8及更高版本,get_all_field_names已被废弃。向后兼容的方法获取它们快速获取方式 - fzzylogic
您可以使用 has_add_permission。 - rluts

14
使用 Django 2.2+ 版本,只读管理页面可以非常简单:
class ReadOnlyAdminMixin:
    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


class LogEntryAdmin(ReadOnlyAdminMixin, admin.ModelAdmin):
    list_display = ('id', 'user', 'action_flag', 'content_type', 'object_repr')

这正是我想要的。谢谢~ - Waket Zheng

13

如果您想让用户知道他/她无法编辑它,第一个解决方案缺少两个要素。您需要删除“删除”操作!

class MyAdmin(ModelAdmin)
    def has_add_permission(self, request, obj=None):
        return False
    def has_delete_permission(self, request, obj=None):
        return False

    def get_actions(self, request):
        actions = super(MyAdmin, self).get_actions(request)
        if 'delete_selected' in actions:
            del actions['delete_selected']
        return actions

第二点:对于普通模型,只读解决方案可行。但如果您使用具有外键的继承模型,则不可行。不幸的是,我目前还不知道解决方法。一个不错的尝试是:

整个模型设为只读

但这对我也不起作用。

最后需要注意的是,如果您想考虑一个广泛的解决方案,您必须强制每个内联也是只读的。


11

这是在 Django 2.1 中添加的功能,发布日期为 2018 年 8 月 1 日!

ModelAdmin.has_view_permission() 与现有的 has_delete_permission、has_change_permission 和 has_add_permission 类似。您可以在文档中阅读有关它的信息。

来自版本说明:

这使得用户可以以只读方式访问管理中的模型。 ModelAdmin.has_view_permission() 是新功能。实现向后兼容,因为无需分配“查看”权限即可允许具有“更改”权限的用户编辑对象。


1
超级用户仍然可以在管理界面中修改对象,对吗? - Flimm
1
这是正确的,除非您覆盖其中一个方法以更改行为以禁止超级用户访问。 - grrrrrr

11

实际上,您可以尝试这个简单的解决方案:

class ReadOnlyModelAdmin(admin.ModelAdmin):
    actions = None
    list_display_links = None
    # more stuff here

    def has_add_permission(self, request):
        return False
  • actions = None: 避免显示带有“删除所选...”选项的下拉菜单
  • list_display_links = None: 避免在列中单击以编辑对象
  • has_add_permission() 返回False可避免为该模型创建新对象

1
这禁止打开任何实例以查看字段,但如果只是列出,那么它可以工作。 - Sebastián Vansteenkiste

6

如果被接受的答案对你无效,请尝试以下方法:

def get_readonly_fields(self, request, obj=None):
    readonly_fields = []
    for field in self.model._meta.fields:
        readonly_fields.append(field.name)

    return readonly_fields

6

结合@darklow和@josir的优秀回答,并添加一些内容以删除“保存”和“保存并继续”按钮,得到以下代码(使用Python 3语法):

class ReadOnlyAdmin(admin.ModelAdmin):
    """Provides a read-only view of a model in Django admin."""
    readonly_fields = []

    def change_view(self, request, object_id, extra_context=None):
        """ customize add/edit form to remove save / save and continue """
        extra_context = extra_context or {}
        extra_context['show_save_and_continue'] = False
        extra_context['show_save'] = False
        return super().change_view(request, object_id, extra_context=extra_context)

    def get_actions(self, request):
        actions = super().get_actions(request)
        if 'delete_selected' in actions:
            del actions['delete_selected']
        return actions

    def get_readonly_fields(self, request, obj=None):
        return list(self.readonly_fields) + \
           [field.name for field in obj._meta.fields] + \
           [field.name for field in obj._meta.many_to_many]

    def has_add_permission(self, request):
        return False

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

然后你就可以像这样使用

class MyModelAdmin(ReadOnlyAdmin):
    pass

我只在Django 1.11 / Python 3上尝试过这个功能。


我已经很久没有使用Django了。还有其他人可以证明吗? - Steve Bennett
@SteveBennett,这个问题有很多变化,这个答案并不是完全正确的。建议参考这里的解释:https://dev59.com/LGoy5IYBdhLWcg3wl_Mx#36019597 和您评论的答案:https://dev59.com/emsy5IYBdhLWcg3w3hyI#33543817,它们比被接受的答案更完整。 - ptim

4

使用Django 2.2,我可以这样做:

@admin.register(MyModel)
class MyAdmin(admin.ModelAdmin):
    readonly_fields = ('all', 'the', 'necessary', 'fields')
    actions = None # Removes the default delete action in list view

    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

使用Django 2.2,readonly_fieldsactions行不是必需的。 - cheng10

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接