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

Posted

技术标签:

【中文标题】Django管理界面中的只读模型?【英文标题】:Readonly models in Django admin interface? 【发布时间】:2012-01-06 02:39:41 【问题描述】:

如何在管理界面中将模型设为完全只读?这是一种日志表,我使用管理功能进行搜索、排序、过滤等,但不需要修改日志。

如果这看起来像重复,这是我想要做的不是

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

【问题讨论】:

此功能即将推出:github.com/django/django/pull/5297 has_view_permission 终于在 Django 2.1 中实现了。另请参阅下面的***.com/a/51641149。 【参考方案1】:

管理员用于编辑,而不仅仅是查看(您不会找到“查看”权限)。为了实现您想要的,您必须禁止添加、删除和将所有字段设为只读:

class MyAdmin(ModelAdmin):

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

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

(如果你禁止更改,你甚至不会看到对象)

对于一些试图自动将所有字段设置为只读的未经测试的代码,请参阅我对Whole model as read-only的回答

编辑:也未经测试,但只是看看我的 LogEntryAdmin,它有

readonly_fields = MyModel._meta.get_all_field_names()

不知道这是否适用于所有情况。

编辑:QuerySet.delete() 仍可能批量删除对象。要解决这个问题,请提供您自己的“对象”管理器和相应的不会删除的 QuerySet 子类 - 请参阅 Overriding QuerySet.delete() in Django

【讨论】:

P.S.:是的,就像在另一个答案中一样,要走的路可能是在 ReadOnlyAdmin 类中定义这三件事,然后在您需要该行为的任何地方从该类中定义。甚至可以花哨并允许定义允许编辑的组/权限,然后相应地返回 True(并使用 get_readonly_fields() 可以访问请求并因此访问当前用户)。 几乎完美。我可以贪婪地问是否有办法不让行链接到编辑页面? (同样,无需放大任何行,也无需编辑任何内容) 如果您将 ModelAdmin 的 list_display_links 设置为评估为 False 的内容(如空列表/元组),ModelAdmin.__init__() 会将 list_display_links 设置为所有列(操作复选框除外) - 请参阅 options.py .我想这样做是为了确保有链接。所以我会在 ReadOnlyAdmin 中覆盖 __init__(),调用父级然后将 list_display_links 设置为空列表或元组。鉴于您现在没有指向只读更改表单的链接,可能最好为此创建一个参数/类属性 - 我不认为这是通常需要的行为。 Hth 关于从模型中设置的 readonly_fields,如果您覆盖表单并添加其他字段,这可能不起作用...基于 actual 表单字段可能是更好。 这不起作用:def __init__(self, *args): super(RegistrationStatusAdmin, self).__init__(*args) self.display_links=[]【参考方案2】:

如果您希望用户意识到他/她无法对其进行编辑,则第一个解决方案中缺少 2 个部分。您已移除删除操作!

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

第二:只读解决方案适用于普通模型。但如果您有一个带有外键的继承模型,它确实工作。不幸的是,我还不知道解决方案。一个好的尝试是:

Whole model as read-only

但这对我也不起作用。

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

【讨论】:

【参考方案3】:

如果接受的答案不适合你,试试这个:

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

【讨论】:

【参考方案4】:

这是我用来制作模型和/或内联只读的两个类。

对于模型管理员:

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 使用这些类as mixins has_add_permission in ReadOnlyAdmin 只接受请求作为参数 has_change_permission() 也需要被覆盖。 def has_change_permission(self, request, obj=None):【参考方案5】:

接受的答案应该有效,但这也会保留只读字段的显示顺序。您也不必使用此解决方案对模型进行硬编码。

class ReadonlyAdmin(admin.ModelAdmin):
   def __init__(self, model, admin_site):
      super(ReadonlyAdmin, self).__init__(model, admin_site)
      self.readonly_fields = [field.name for field in filter(lambda f: not f.auto_created, model._meta.fields)]

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

【讨论】:

【参考方案6】:

见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

templates/admin/view.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 %

templates/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 了,可能会等着看其他评论者怎么说。 这是Model 还是ModelAdmin 的mixin? 这是给ModelAdmin的。 对于 Django 1.8 及更高版本,不推荐使用 get_all_field_names。 Backwards compatible way to get them。 Short way to get them. 你可以使用 has_add_permission【参考方案7】:

其实你可以试试这个简单的解决方案:

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 可避免为该模型创建新对象

【讨论】:

这禁止打开任何实例来查看字段,但是如果只列出列表就可以了,那么它可以工作。【参考方案8】:

当需要为 django admin 中的某些用户设置所有字段只读时,我遇到了相同的要求,最终利用 django 模块“django-admin-view-permission”而不滚动我自己的代码。如果您需要更细粒度的控制来明确定义哪些字段,那么您需要扩展模块。您可以查看正在使用的插件here

【讨论】:

【参考方案9】:

编译 @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

然后你用like

class MyModelAdmin(ReadOnlyAdmin):
    pass

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

【讨论】:

很久没用Django了。其他人可以为此担保吗? @SteveBennettㄹ 这个解决的要求有很多变化......这个答案不是水密的......在这里建议解释:@ 987654321@和你评论的答案@ 987654322@ 比接受的答案更完整【参考方案10】:

只读 => 查看权限

    pipenv install django-admin-view-permission 在 settings.py 中将 'admin_view_permission' 添加到 INSTALLED_APPS。如下所示: `INSTALLED_APPS = [ 'admin_view_permission', python manage.py 迁移 python manage.py runserver 6666

ok.享受“views”权限的乐趣​​

【讨论】:

【参考方案11】:

我编写了一个通用类来根据用户权限(包括内联)处理 ReadOnly 视图;)

在models.py中:

class User(AbstractUser):
    ...
    def is_readonly(self):
        if self.is_superuser:
            return False
        # make readonly all users not in "admins" group
        adminGroup = Group.objects.filter(name="admins")
        if adminGroup in self.groups.all():
            return False
        return True

在 admin.py 中:

# read-only user filter class for ModelAdmin
class ReadOnlyAdmin(admin.ModelAdmin):
    def __init__(self, *args, **kwargs):
        # keep initial readonly_fields defined in subclass
        self._init_readonly_fields = self.readonly_fields
        # keep also inline readonly_fields
        for inline in self.inlines:
            inline._init_readonly_fields = inline.readonly_fields
        super().__init__(*args,**kwargs)
    # customize change_view to disable edition to readonly_users
    def change_view( self, request, object_id, form_url='', extra_context=None ):
        context = extra_context or 
        # find whether it is readonly or not 
        if request.user.is_readonly():
            # put all fields in readonly_field list
            self.readonly_fields = [ field.name for field in self.model._meta.get_fields() if not field.auto_created ]
            # readonly mode fer all inlines
            for inline in self.inlines:
                inline.readonly_fields = [field.name for field in inline.model._meta.get_fields() if not field.auto_created]
            # remove edition buttons
            self.save_on_top = False
            context['show_save'] = False
            context['show_save_and_continue'] = False
        else:
            # if not readonly user, reset initial readonly_fields
            self.readonly_fields = self._init_readonly_fields
            # same for inlines
            for inline in self.inlines:
                inline.readonly_fields = self._init_readonly_fields
        return super().change_view(
                    request, object_id, form_url, context )
    def save_model(self, request, obj, form, change):
        # disable saving model for readonly users
        # just in case we have a malicious user...
        if request.user.is_readonly():
            # si és usuari readonly no guardem canvis
            return False
        # if not readonly user, save model
        return super().save_model( request, obj, form, change )

然后,我们就可以正常继承 admin.py 中的类了:

class ContactAdmin(ReadOnlyAdmin):
    list_display = ("name","email","whatever")
    readonly_fields = ("updated","created")
    inlines = ( PhoneInline, ... )

【讨论】:

【参考方案12】:

这是在 2018 年 8 月 1 日发布的 Django 2.1 中添加的!

ModelAdmin.has_view_permission() 就像现有的 has_delete_permission、has_change_permission 和 has_add_permission。您可以在文档here

中阅读相关内容

来自发行说明:

这允许在管理员中授予用户对模型的只读访问权限。 ModelAdmin.has_view_permission() 是新的。实现是 向后兼容,因为不需要分配“视图” 允许具有“更改”权限的用户进行编辑的权限 对象。

【讨论】:

超级用户仍然可以在管理界面中修改对象,对吧? 这是正确的,除非您覆盖其中一种方法来更改行为以禁止超级用户访问。【参考方案13】:

在 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 行不是必需的【参考方案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')

【讨论】:

这正是我想要的。谢谢~

以上是关于Django管理界面中的只读模型?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Django 模型设为只读?

在第一次保存对象时使 django 模型字段只读或在管理员中禁用

将 Django 管理“添加”界面中的字段拆分为其组成字段

显示聚合的 Django 管理界面

如何使 Django 管理员只读 textarea 字段

Django Admin Cookbook-39如何两次向Django管理员添加模型