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

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

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

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

【问题讨论】:

标签: django django-admin readonly


【解决方案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 themShort 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】:

          只读 => 查看权限

          1. pipenv install django-admin-view-permission
          2. 在 settings.py 中将 'admin_view_permission' 添加到 INSTALLED_APPS。如下所示: `INSTALLED_APPS = [ 'admin_view_permission',
          3. python manage.py 迁移
          4. 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')
              

              【讨论】:

              • 这正是我想要的。谢谢~