如何根据用户限制django admin中的字段?

如何根据用户限制django admin中的字段?,django,django-admin,Django,Django Admin,我想这里也会讨论类似的问题,但我找不到 假设我有一个编辑和一个主管。我希望编辑能够添加新内容(如新闻稿),但在发布前必须得到主管的认可 当编辑列出所有项目时,我想将模型上的一些字段(如“确认”字段)设置为只读(这样他就可以知道哪些已确认,哪些仍在等待批准),但主管应该能够更改所有内容(列表可编辑将是完美的) 这个问题的可能解决方案是什么?我在一个刚刚完成的项目中有一个类似这样的系统。要将其整合起来,需要做大量的工作,但以下是我必须让我的系统工作的一些组件: 您需要一种定义编辑器和主管的方法。有

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

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

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


这个问题的可能解决方案是什么?

我在一个刚刚完成的项目中有一个类似这样的系统。要将其整合起来,需要做大量的工作,但以下是我必须让我的系统工作的一些组件:

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

  • 一旦确定了用户的类型,您就需要一种方法来强制执行您正在寻找的授权。我认为这里最好的途径可能是一个通用的管理模型

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

下面是代码中的一些想法(未经测试,概念性强于实际)

制作一个名为“主持”的新应用程序,它将保存我们的一般资料

缓和的.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
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)
因此,现在我们应该有一个通用的、可重用的代码,我们可以识别给定模型的权限(我们将通过它的内容类型来识别模型)

接下来,我们可以在管理中实现我们的策略,同样是通过一个通用模型:

已审核的管理员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
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\u approved
项目实施过滤。(也就是说,你不想让未经批准的项目出现在新闻部分,对吧?

我认为有一种更简单的方法可以做到这一点:

嘉宾我们也有同样的问题博客帖子

blog/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"),
         )
神奇之处在于管理:

blog/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中使用的api,如果必须,您可以重写保存模型或获取查询集的方法。在methid
has_approval\u permission
中,您可以定义用户何时可以或何时不能做某事的逻辑。

使用@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
...
另一种方法是将
字段作为关键字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所需的适当关键字args


可能值得一看基本ModelAdmin上的
get\u form()
方法,以了解更多信息:

从Django 1.7开始,现在可以使用
get\u fields
钩子,这使得实现条件字段变得非常简单

类MyModelAdmin(admin.ModelAdmin):
...
def get_字段(self、request、obj=None):
fields=super(MyModelAdmin,self).get_字段(request,obj)
如果request.user.is_超级用户:
字段+=(“批准”,)
返回字段

get_form()中的self.exclude=['approved']可能是指self.exclude,而changelist_view()中也有一点小问题。)谢谢,这看起来很棒,再加上T.Stone的回答,这正是我一直在寻找的:)如果我得到的
对象没有属性“COOKIES”
?权限是否应该注册到其他可以在管理模块中看到的地方?很好的技术,但是
字段=self.fields
对我来说是
。我把那行改成了
fields=super(MyModelAdmin,self)。get_fields(request,obj)
,它就像一个符咒。