【问题标题】:How to limit fields in django-admin depending on user?如何根据用户限制 django-admin 中的字段?
【发布时间】:2011-02-06 14:13:47
【问题描述】:

我想类似的问题会在这里讨论,但我找不到。

假设我有一个编辑和一个主管。我希望编辑能够添加新内容(例如新闻帖子),但在发布之前必须得到主管的认可。

当编辑器列出所有项目时,我想将模型上的某些字段(例如“确认”字段)设置为只读(这样他就可以知道哪些已确认以及哪些仍在等待批准)但主管应该能够改变一切(list_editable 将是完美

这个问题有哪些可能的解决方案?

【问题讨论】:

    标签: django django-admin


    【解决方案1】:

    我认为有一个更简单的方法来做到这一点:

    请客,我们也有同样的Blog-Post问题

    博客/models.py:

    Class Blog(models.Model):
         ...
         #fields like autor, title, stuff..
         ...
    
    class Post(models.Model):
         ...
         #fields like blog, title, stuff..
         ...
         approved = models.BooleanField(default=False)
         approved_by = models.ForeignKey(User) 
         class Meta:
             permissions = (
                 ("can_approve_post", "Can approve post"),
             )
    

    魔法就在管理中:

    博客/admin.py:

    ...
    from django.views.decorators.csrf import csrf_protect
    ...
    def has_approval_permission(request, obj=None):
         if request.user.has_perm('blog.can_approve_post'):
             return True
         return False
    
    Class PostAdmin(admin.ModelAdmin):
         @csrf_protect
         def changelist_view(self, request, extra_context=None):
             if not has_approval_permission(request):
                 self.list_display = [...] # list of fields to show if user can't approve the post
                 self.editable = [...]
             else:
                 self.list_display = [...] # list of fields to show if user can approve the post
             return super(PostAdmin, self).changelist_view(request, extra_context)
         def get_form(self, request, obj=None, **kwargs):
             if not has_approval_permission(request, obj):
                 self.fields = [...] # same thing
             else:
                 self.fields = ['approved']
             return super(PostAdmin, self).get_form(request, obj, **kwargs)
    

    这样你就可以在django中使用custom permission的api了,如果需要的话你可以重写保存模型或者获取查询集的方法。在方法has_approval_permission 中,您可以定义用户何时可以或不能做某事的逻辑。

    【讨论】:

    • 您可能是指 get_form() 中的 self.exclude = ['approved'] 并且 changelist_view() 中也有一些小故障;)谢谢,这看起来很棒,并结合了 T.Stone 的答案,这正是我一直在寻找的 :)
    • 如果我得到object has no attribute 'COOKIES'怎么办?
    • 是否应该在其他地方注册权限以便在管理模块中可见?
    【解决方案2】:

    从 Django 1.7 开始,您现在可以使用 get_fields 挂钩,这使得实现条件字段变得更加简单。

    class MyModelAdmin(admin.ModelAdmin):
        ...
    
        def get_fields(self, request, obj=None):
            fields = super(MyModelAdmin, self).get_fields(request, obj)
            if request.user.is_superuser:
                fields += ('approve',)
    
            return fields
    

    【讨论】:

    • 技术很好,但fields = self.fields 为我带来了None。我把那条线改成了fields = super(MyModelAdmin, self).get_fields(request, obj),它就像一个魅力。
    【解决方案3】:

    在我刚刚完成的项目中,我有一个类似这样的系统。将这些组合在一起需要做很多工作,但以下是我必须使我的系统正常工作的一些组件:

    • 您需要一种方法来定义编辑器和主管。可以做到这一点的三种方法是:1.)通过定义主管的 M2M 字段[并假设其他所有有权读/写的人都是编辑器],2.)创建 2 个从用户继承的新用户模型 [可能比必要的工作更多] 或 3.) 使用 django.auth 拥有 UserProfile 类的能力。方法#1 可能是最合理的。

    • 一旦您可以确定用户是什么类型,您就需要一种方法来普遍地强制执行您正在寻找的授权。我认为这里最好的路线可能是通用管理模型。

    • 最后,您需要某种类型的“父”模型,该模型将持有任何需要审核的权限。例如,如果您有一个 Blog 模型和 BlogPost 模型(假设同一站点中有多个博客),那么 Blog 是父模型(它可以拥有谁批准什么的权限)。但是,如果您有一个博客并且没有 BlogPost 的父模型,我们将需要一些地方来存储权限。我发现ContentType 在这里效果很好。

    以下是代码中的一些想法(未经测试,比实际更具概念性)。

    制作一个名为 'moderated' 的新应用,用于保存我们的通用内容。

    moderated.models.py

    class ModeratedModelParent(models.Model):
        """Class to govern rules for a given model"""
        content_type = models.OneToOneField(ContentType)
        can_approve = models.ManyToManyField(User)
    
    class ModeratedModel(models.Model):
        """Class to implement a model that is moderated by a supervisor"""
        is_approved = models.BooleanField(default=False)
    
        def get_parent_instance(self):
            """
            If the model already has a parent, override to return the parent's type
            For example, for a BlogPost model it could return self.parent_blog
            """
    
            # Get self's ContentType then return ModeratedModelParent for that type
            self_content_type = ContentType.objects.get_for_model(self)
            try:            
                return ModeratedModelParent.objects.get(content_type=self_content_type)
            except:
                # Create it if it doesn't already exist...
                return ModeratedModelParent.objects.create(content_type=self_content_type).save()
    
        class Meta:
            abstract = True
    

    所以现在我们应该有一个通用的、可重用的代码,我们可以识别给定模型的权限(我们将通过它的内容类型来识别模型)。

    接下来,我们可以再次通过通用模型在管理员中实施我们的政策:

    moderated.admin.py

    class ModeratedModelAdmin(admin.ModelAdmin):
    
        # Save our request object for later
        def __call__(self, request, url):
            self.request = request
            return super(ModeratedModelAdmin, self).__call__(request, url)
    
        # Adjust our 'is_approved' widget based on the parent permissions
        def formfield_for_dbfield(self, db_field, **kwargs):
            if db_field.name == 'is_approved':
                if not self.request.user in self.get_parent_instance().can_approve.all():
                    kwargs['widget'] = forms.CheckboxInput(attrs={ 'disabled':'disabled' })
    
        # Enforce our "unapproved" policy on saves
        def save_model(self, *args, **kwargs):
            if not self.request.user in self.get_parent_instance().can_approve.all():
                self.is_approved = False
            return super(ModeratedModelAdmin, self).save_model(*args, **kwargs)
    

    一旦这些设置和工作,我们可以在许多模型中重复使用它们,正如我发现的那样,一旦你为这样的东西添加结构化权限,你很容易想要它用于许多其他事情。

    假设你有一个新闻模型,你只需要让它继承我们刚刚制作的模型,你就很好了。

    # in your app's models.py
    class NewsItem(ModeratedModel):
        title = models.CharField(max_length=200)
        text = models.TextField()
    
    
    # in your app's admin.py
    class NewsItemAdmin(ModeratedModelAdmin):
        pass
    
    admin.site.register(NewsItem, NewsItemAdmin)
    

    我确信我在那里犯了一些代码错误和错误,但希望这可以给你一些想法,作为你决定实施的任何事情的跳板。

    您需要做的最后一件事是对is_approved 项目实施过滤,我将留给您。 (即,您不希望在新闻部分列出未经批准的项目,对吧?)

    【讨论】:

      【解决方案4】:

      使用@diegueus9 概述的方法的问题在于,ModelAdmin 的行为就像一个单例,并且对于每个请求都没有实例化。这意味着每个请求都在修改其他请求正在访问的同一个 ModelAdmin 对象,这并不理想。以下是@diegueus9 提出的解决方案:

      # For example, get_form() modifies the single PostAdmin's fields on each request
      ...
      class PostAdmin(ModelAdmin):
          def get_form(self, request, obj=None, **kwargs):
              if not has_approval_permission(request, obj):
                  self.fields = [...] # list of fields to show if user can't approve the post
              else:
                  self.fields = ['approved', ...] # add 'approved' to the list of fields if the user can approve the post
      ...
      

      另一种方法是将 fields 作为关键字 arg 传递给父级的 get_form() 方法,如下所示:

      ...
      from django.contrib.admin.util import flatten_fieldsets
      
      class PostAdmin(ModelAdmin):
          def get_form(self, request, obj=None, **kwargs):
              if has_approval_permission(request, obj):
                  fields = ['approved']
                  if self.declared_fieldsets:
                      fields += flatten_fieldsets(self.declared_fieldsets)
      
                  # Update the keyword args as needed to allow the parent to build 
                  # and return the ModelForm instance you require for the user given their perms
                  kwargs.update({'fields': fields})
              return super(PostAdmin, self).get_form(request, obj=None, **kwargs)
      ...
      

      这样,您不会在每个请求上都修改 PostAdmin 单例;您只是传递了构建并从父级返回 ModelForm 所需的适当关键字参数。

      可能值得查看基础 ModelAdmin 上的 get_form() 方法以获取更多信息:https://code.djangoproject.com/browser/django/trunk/django/contrib/admin/options.py#L431

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-22
        • 2016-02-06
        • 1970-01-01
        • 2021-12-24
        • 1970-01-01
        • 1970-01-01
        • 2019-09-17
        • 2019-08-25
        相关资源
        最近更新 更多