为Django Admin添加按对象权限

14 投票
5 回答
10990 浏览
提问于 2025-04-16 03:54

背景

我正在开发一个用于度假租赁的网站的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.UserForeignKey(可能有一个特定的名称,比如'owner')。

  • 在创建新的vacation.Rental时,ForeignKey字段的值会被强制设置为当前用户的ID。这个ForeignKey字段不会在表单上显示。

  • 在列出租赁时,只会显示ForeignKey与当前用户匹配的租赁。

  • 在修改租赁时,只会显示ForeignKey与当前用户匹配的租赁。这个ForeignKey字段也不会在表单上显示。

当然,这种方法应该适用于任何有合适ForeignKey字段的模型,而不仅仅是我们的vacation.Rental模型。

到目前为止,这听起来可行吗,还是我应该换个方向?


复杂性

现在,问题来了;我不太确定该如何处理。假设一个Rental可以有很多“RentalPhotos”。RentalPhoto有一个指向RentalForeignKey。用户应该能够给自己的租赁添加照片。不过,照片没有用户的ForeignKey,所以没有办法直接找出谁拥有这张照片。

这个问题能否通过在框架中一些技巧来解决,沿着ForeignKey查找,直到找到一个指向用户的对象?还是我应该简单点,给RentalPhoto(以及所有属于Rental的东西)添加一个指向相应auth.UserForeignKey?第二种方法会引入不必要的冗余,而第一种方法可能会需要额外的处理开销……

如果我完全偏离了方向,请随时指正我。提前感谢任何帮助。

5 个回答

1

这段内容来自于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

2

如果你不想自己去实现权限管理的功能,我建议你使用这个链接里的工具:https://github.com/chrisglass/django-rulez。这样你可以更简单地完成你想做的事情。

23

我会给每个模型加一个方法,叫做 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"),
        )

我觉得与其为 anyown 添加两个权限,不如只添加 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,而不是简单的字符串搜索。不过如果你有标准的名称,这样做也能正常工作。

撰写回答