【问题标题】:Adding per-object permissions to django admin将每个对象的权限添加到 django admin
【发布时间】:2010-09-09 07:07:19
【问题描述】:

背景

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

我希望物业经理能够在 django 后台管理他们的出租物业。但是,他们应该只能管理自己的属性。

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


目标

理想情况下,我想象它是这样工作的:

auth 已经允许这样的权限:

假期 |出租|可以加租 假期 |出租|可以改租 假期 |出租|可以删除租借

我想将其更改为:

假期 |出租|可以添加任何租金 假期 |出租|可以更改任何租金 假期 |出租|可以删除任何租借 假期 |出租|可以添加自己的租金 假期 |出租|可以改变自己的租金 假期 |出租|可以删除自己的租借

可能的解决方案

框架将如何决定租赁(或其他)是否属于用户?我想它会检查vacation.Rental 类,看看它是否有一个ForeignKey 到auth.User(可能有一些特定的名称,比如“所有者”)。

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

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

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

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

到目前为止,这听起来可行吗,还是我应该朝着不同的方向前进?


并发症

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

这是否可以通过框架中的一些技巧来解决,遵循ForeignKeys 直到找到一个带有ForeignKey 的对象给用户?还是我应该采取简单的方法,将RentalPhoto(以及其他“属于”Rental 的所有内容)自己的ForeignKey 提供给适当的auth.User?第二种方法会带来不必要的冗余,第一种方法可能需要不必要的处理开销......

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

【问题讨论】:

    标签: python django django-admin


    【解决方案1】:

    我只需向每个模型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 而不是进行粗略的字符串搜索,但如果您有标准名称,这也应该可以工作

    【讨论】:

    • 谢谢,阿努拉格;它不像我想要的那样通用,但我会考虑它。如何将其与auth 绑定,以便我的新权限显示在列表中?
    【解决方案2】:

    如果您不想实现自己的 Permission Backend,我建议您使用 https://github.com/chrisglass/django-rulez 您会以更简单的方式做您想做的事。

    【讨论】:

      【解决方案3】:

      它在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】:

        这可以通过一些技巧来解决吗 框架,遵循 ForeignKeys 直到找到一个对象 用户的外键?

        我看不出哪里有必要的诡计: 租借照片 -> 租借 -> 用户 因此,要获取特定 RentalPhoto 的用户,您可以在实例中调用如下代码:

        photo.rental.user
        

        一步完成多个关系可以认为是非欺骗性的。

        【讨论】:

        • 这适用于这种特殊情况,但我想以一种通用的方式来做——“当然,这应该能够适用于任何具有适当 ForeignKey 字段的模型,而不是只是我们的vacation.Rental [和相关] 模型[s]。”
        • 有几十种可能的方法可以更通用。例如。有一个 model.Ownable,它定义了字段所有者并在可拥有的模型中扩展它。
        • 这个答案不是很有帮助。它留下了大部分问题没有答案。也许我在解释问题方面做得不好?
        • 如果你能告诉我为什么使用模型继承对你的情况没有帮助,我更好地理解你的问题是什么。
        【解决方案5】:
        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)
        

        【讨论】:

        • 虽然这段代码可以回答这个问题,但最好包含一些上下文,解释它是如何工作的以及何时使用它。从长远来看,纯代码的答案没有用处。
        猜你喜欢
        • 2014-06-18
        • 2014-01-10
        • 2014-06-21
        • 1970-01-01
        • 2018-12-14
        • 2021-06-19
        • 1970-01-01
        • 1970-01-01
        • 2013-02-26
        相关资源
        最近更新 更多