为Django Admin添加按对象权限
背景
我正在开发一个用于度假租赁的网站的django应用。这个应用会有两种用户类型,分别是租客和房产管理者。
我希望房产管理者能够在django的后台管理他们的租赁房产。不过,他们只能管理自己的房产。
我意识到默认的django后台并不支持这个功能。我在想,添加这个功能会有多麻烦,如果可行,最好的处理方式是什么。
目标
理想情况下,我希望它能像这样工作:
auth
已经允许这样的权限设置:
vacation | rental | Can add rental vacation | rental | Can change rental vacation | rental | Can delete rental
我想把它改成类似这样的:
vacation | rental | Can add any rental vacation | rental | Can change any rental vacation | rental | Can delete any rental vacation | rental | Can add own rental vacation | rental | Can change own rental vacation | rental | Can delete own rental
可能的解决方案
框架将如何判断某个租赁(或其他)是否属于某个用户呢?我在想,它可以检查vacation.Rental
类,看看是否有一个指向auth.User
的ForeignKey
(可能有一个特定的名称,比如'owner')。
在创建新的
vacation.Rental
时,ForeignKey
字段的值会被强制设置为当前用户的ID。这个ForeignKey
字段不会在表单上显示。在列出租赁时,只会显示
ForeignKey
与当前用户匹配的租赁。在修改租赁时,只会显示
ForeignKey
与当前用户匹配的租赁。这个ForeignKey
字段也不会在表单上显示。
当然,这种方法应该适用于任何有合适ForeignKey
字段的模型,而不仅仅是我们的vacation.Rental
模型。
到目前为止,这听起来可行吗,还是我应该换个方向?
复杂性
现在,问题来了;我不太确定该如何处理。假设一个Rental
可以有很多“RentalPhotos”。RentalPhoto
有一个指向Rental
的ForeignKey
。用户应该能够给自己的租赁添加照片。不过,照片没有用户的ForeignKey
,所以没有办法直接找出谁拥有这张照片。
这个问题能否通过在框架中一些技巧来解决,沿着ForeignKey
查找,直到找到一个指向用户的对象?还是我应该简单点,给RentalPhoto
(以及所有属于Rental
的东西)添加一个指向相应auth.User
的ForeignKey
?第二种方法会引入不必要的冗余,而第一种方法可能会需要额外的处理开销……
如果我完全偏离了方向,请随时指正我。提前感谢任何帮助。
5 个回答
这段内容来自于Django的官方文档。
简单来说,你需要为你的模型创建一个自定义的管理类,并定义一个叫做get_queryset
的方法。在你的情况下,代码可能会像下面这样。超级用户可以看到所有的租赁信息,而房东只能看到自己的。
class RentalAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(RentalAdmin, self).get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(owner=request.user)
这里还有另一个可能的方向:https://code.djangoproject.com/wiki/RowLevelPermissions
如果你不想自己去实现权限管理的功能,我建议你使用这个链接里的工具:https://github.com/chrisglass/django-rulez。这样你可以更简单地完成你想做的事情。
我会给每个模型加一个方法,叫做 is_owned_by(user)
,这个方法会判断这个模型是不是属于那个用户。一般来说,is_owned_by
可以放在一个基础模型类里,作为一个通用的函数,特殊情况再进行调整。例如:
class RentalPhoto(BaseModel):
def is_owned_by(self, user):
return self.rental.is_owned_by(user)
这个方法设计得很通用,而且很明确,这样你就能完全掌控它的行为。
如果要添加新的权限,可以直接加到你的模型里,比如:
class Rental(models.Model):
# ...
class Meta:
permissions = (
("can_edit_any", "Can edit any rentals"),
)
我觉得与其为 any
和 own
添加两个权限,不如只添加 own
权限。这样每个对象都有 can_edit
权限,意味着用户只能编辑自己的对象。如果用户有 can_edit_any
权限,那他就可以编辑所有对象。
通过这种方式,我们可以通过添加一个自定义的后端来扩展权限,例如:
class PerObjectBackend(ModelBackend):
def has_perm(self, user_obj, perm, obj=None):
allowed = ModelBackend.has_perm(self, user_obj, perm)
if perm.find('any') >=0 :
return allowed
if perm.find('edit') >=0 or perm.find('delete') >=0:
if obj is None:
raise Exception("Perm '%s' needs an object"%perm)
if not obj.is_owned_by(user_obj):
return False
return allowed
这个实现方式很快,实际上你可以扩展权限对象,来检查是否需要一个对象,比如用 permission.is_per_object
,而不是简单的字符串搜索。不过如果你有标准的名称,这样做也能正常工作。