我想这里会讨论类似的问题,但我找不到它。
假设我有一名编辑和一名主管。我希望编辑能够添加新内容(例如新闻发布),但在发布之前必须得到主管的认可。
当编辑器列出所有项目时,我想在模型上设置一些字段(如'ack'字段)为只读(因此他可以知道什么是ack'ed以及还有什么等待批准)但是主管应该能够改变一切(list_editable将是完美)
这个问题的可能解决方案是什么?
答案 0 :(得分:18)
我认为有一种更简单的方法:
访客我们遇到了与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,并且可以覆盖保存模型的方法或获取查询集(如果必须)。在methid has_approval_permission
中,您可以定义用户何时能够或不能做某事的逻辑。
答案 1 :(得分:2)
我在一个项目中有一个这样的系统,我刚刚完成。将这些工作放在一起将会有很多工作,但以下是我必须使系统工作的一些组件:
您需要一种方法来定义编辑器和主管。这可以做到的三种方式是:1)通过一个定义Supervisor的M2M字段[并假设其他所有有读/写权限的人都是编辑],2。)制作2个继承自User的新用户模型[可能需要更多的工作]或3.)使用django.auth能力拥有UserProfile类。方法#1可能是最合理的。
一旦您确定了用户的类型,您就需要一种通用的方法来强制执行您正在寻找的授权。我认为这里最好的路线可能是通用的管理模式。
最后,您需要某种类型的“父”模型,该模型将保留任何需要审核的权限。例如,如果您有一个Blog模型和BlogPost模型(假设同一站点中有多个博客),那么Blog就是父模型(它可以拥有谁批准什么的权限)。但是,如果您有一个博客并且没有BlogPost的父模型,我们需要一些地方来存储权限。我发现ContentType
在这里工作得很好。
以下是代码中的一些想法(未经测试,更具概念性而非实际)。
创建一个名为“已审核”的新应用,它将保留我们的通用内容。
<强> 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
项目实施过滤。 (即你不希望新闻部分列出未经批准的项目,对吗?)
答案 2 :(得分:2)
使用@ 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单例;您只需传递构建所需的相应关键字args并从父级返回ModelForm。
可能值得查看基础ModelAdmin上的get_form()
方法以获取更多信息:https://code.djangoproject.com/browser/django/trunk/django/contrib/admin/options.py#L431
答案 3 :(得分: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