[英]Adding per-object permissions to 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
可以有很多“租借照片”。 RentalPhoto
有一个ForeignKey
以Rental
。 用户应该能够将照片添加到他们自己的租借中。 但是,照片没有用户ForeignKey
,因此无法直接找出谁拥有照片。
这可以通过框架中的一些技巧来解决,跟随ForeignKey
直到找到一个带有ForeignKey
给用户的对象吗? 或者我应该采取简单的方法并将其自己的ForeignKey
给合适的auth.User
给RentalPhoto
(以及所有其他“属于” Rental
)? 第二种方法会导致不必要的冗余,第一种方法可能需要不必要的处理开销......
如果我完全误入歧途,请不要犹豫,指出我正确的方向。 在此先感谢您的帮助。
我会简单地为每个模型添加一个方法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
而不是进行粗略的字符串搜索,但如果您有标准名称,这也应该有效
如果你不想实现自己的权限后端,我建议你使用https://github.com/chrisglass/django-rulez你会以更简单的方式做你想做的事。
它在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
这可以通过框架中的一些技巧来解决,跟随外键直到找到带有外键的对象给用户?
我不知道哪里有必要的技巧:RentalPhoto -> Rental -> User 因此,要获取特定 RentalPhoto 的用户,您可以在实例中调用类似的方法:
photo.rental.user
在一个步骤中遵循多个关系可以被认为是非欺骗性的。
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)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.