向django admin添加每个对象的权限

时间:2010-09-09 07:07:19

标签: python django django-admin

背景

我正在为度假租赁网站开发一款django应用程序。它将有两种类型的用户,租房者和物业经理。

我希望物业经理能够在django管理员中管理他们的出租物业。但是,他们应该只能管理自己的财产。

我意识到默认的django admin不支持这个。我想知道添加这个功能会有多麻烦,如果可行的话,处理它的最佳方法是。


目标

理想情况下,我认为它的工作原理如下:

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课程,看看它是否有ForeignKeyauth.User(可能有一些特定的名字,比如'所有者')。

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

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

  • 在更改租金时,仅显示与当前用户匹配的ForeignKey的租赁。 ForeignKey字段不会显示在表单上。

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

到目前为止这听起来是否可行,或者我应该向不同的方向发展?


并发症

现在,这是棘手的部分;我不知道如何处理这个问题。假设Rental可以有很多“RentalPhotos”。 RentalPhoto有一个ForeignKeyRental。用户应该能够将照片添加到自己的租借中。但是,这些照片没有用户ForeignKey,因此无法直接找出谁拥有该照片。

这可以通过框架中的一些技巧来解决,在ForeignKey之后,直到找到一个带有ForeignKey用户的对象?或者我应该采取简单的方法,将RentalPhoto(以及其他所有属于Rental的人)ForeignKey的{​​{1}}提供给相应的auth.User?第二种方法会引起不必要的冗余,第一种方法可能需要不必要的处理开销......

如果我完全误入歧途,请毫不犹豫地指出我正确的方向。提前感谢您的帮助。

5 个答案:

答案 0 :(得分:22)

我只是为每个模型is_owned_by(user)添加一个方法,由模型决定它是否由该用户拥有。在大多数情况下,is_owned_by可以是基础模型类中的通用函数,您可以在特殊情况下调整它。 e.g。

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权限,而不是为ownown添加两项权限,因此每个对象都有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而不是粗略的字符串搜索,但如果您有标准名称

,这也应该有效

答案 1 :(得分:1)

  

这可以通过一些诡计来解决   在ForeignKeys之后的框架   直到找到一个对象   ForeignKey to user?

我看不出哪里有诡计必需品: RentalPhoto - >出租 - >用户 因此,要获取特定RentalPhoto的用户,您可以在实例中调用类似的内容:

photo.rental.user

在一个步骤中遵循多个关系可以被认为是非欺骗性的。

答案 2 :(得分:1)

如果您不想实现自己的权限后端,我建议您使用https://github.com/chrisglass/django-rulez您将以更轻松的方式做您想做的事。

答案 3 :(得分:0)

它在Django docs

基本上,您为模型创建自定义管理类,并定义方法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

答案 4 :(得分:0)

class Rental(models.Model):
    owner: User = models.ForeignKey(
        User, verbose_name='owner', related_name='rentals',
        on_delete=models.CASCADE, blank=True, null=False
    )
    # owner_id automatically gets created by Django. Optionally annotate to help your IDE
    owner_id: int

    def is_owned_by(self, user: User):
        # You can use self.owner == user, or self.owner.id == user.id. 
        # But this way owner data won't be fetched from the database
        if self.owner_id == user.id:
            return True
        return False


class RentalPhoto(models.Model):

    rental: Rental = models.ForeignKey(
        Rental, on_delete=models.CASCADE, related_name='rental_photos',
        blank=False, null=False,
    )

    def is_owned_by(self, user: User):
        return self.rental.is_owned_by(user)


class RentalPhotoAdminInline(admin.StackedInline):
    model = RentalPhoto
    extra = 1


@admin.register(Rental)
class RentalAdmin(admin.ModelAdmin):

    inlines = (RentalPhotoAdminInline,)

    # staff members can only view/operate their rentals
    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        if not request.user.is_superuser:
            queryset = queryset.filter(owner_id=request.user.id)

        return queryset

    def save_model(self, request, obj: Rental, form, change):
        # set rental owner on admin save
        if obj.owner_id is None:
            obj.owner = request.user
        obj.save()

    def has_view_or_change_permission(self, request, obj=None):
        allowed = super().has_view_or_change_permission(request, obj)
        if obj is None:
            return allowed
        return request.user.is_superuser or obj.is_owned_by(request.user)

    def has_view_permission(self, request, obj=None):
        allowed = super().has_view_permission(request, obj)
        if obj is None:
            return allowed
        return request.user.is_superuser or obj.is_owned_by(request.user)

    def has_change_permission(self, request, obj=None):
        allowed = super().has_change_permission(request, obj)
        if obj is None:
            return allowed
        return request.user.is_superuser or obj.is_owned_by(request.user)

    def has_delete_permission(self, request, obj=None):
        allowed = super().has_delete_permission(request, obj)
        if obj is None:
            return allowed
        return request.user.is_superuser or obj.is_owned_by(request.user)
相关问题