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

发布于 2024-09-17 23:37:38 字数 1902 浏览 8 评论 0原文

背景

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

我希望物业经理能够在 django admin 中管理他们的租赁物业。然而,他们应该只能管理自己的财产。

我意识到默认的 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

可能的解决方案 strong>

框架如何决定租赁(或其他)是否属于用户?我认为它会检查 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 >auth.User?第二种方法会带来不必要的冗余,第一种方法可能会需要不必要的处理开销......

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

Background

I'm developing a django app for a vacation rental site. It will have two types of users, renters and property managers.

I'd like the property managers to be able to manage their rental properties in the django admin. However, they should only be able to manage their own properties.

I realize the default django admin doesn't support this. I'm wondering how much trouble it would be to add this functionality, and, if it's feasible, what the best way to handle it is.


Goal

Ideally, I picture it working something like this:

auth already allows permissions like this:

vacation | rental | Can add rental
vacation | rental | Can change rental
vacation | rental | Can delete rental

I'd like to change this to something like:

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

Possible solution

How would the framework decide if the rental (or whatever) belongs to the user? I'm thinking it checks the vacation.Rental class to see if it has a ForeignKey to auth.User (possibly having some particular name, like 'owner').

  • On creating a new vacation.Rental, the value of the ForeignKey field would be forced to the current user's id. The ForeignKey field would not be displayed on the form.

  • On listing rentals, only rentals with the ForeignKey matching the current user would be displayed.

  • On changing rentals, only rentals with the ForeignKey matching the current user would be displayed. The ForeignKey field would not be displayed on the form.

Of course, this should be able to work for any model having an appropriate ForeignKey field, not just our vacation.Rental model.

Does this sound feasible so far, or should I be going in a different direction?


Complications

Now, here's the tricky part; I'm not sure how to handle this. Let's say a Rental can have many "RentalPhotos." RentalPhoto has a ForeignKey to Rental. Users should be able to add photos to their own rentals. However, the photos don't have a user ForeignKey, so there's no way to directly find out who owns the photo.

Can this be solved by some trickery in the framework, following ForeignKeys until an object is found with a ForeignKey to user? Or should I take the easy way out and give RentalPhoto (and everything else 'belonging' to Rental) its own ForeignKey to the appropriateauth.User? The second approach would invite unneeded redundancy, the first would probably require unnecessary processing overhead...

If I'm going entirely astray please don't hesitate to point me in the right direction. Thanks in advance for any help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

鸵鸟症 2024-09-24 23:37:38

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

I would simply add a method to each model is_owned_by(user), and it is upto the model to decide if it is owned by that user or not. In most case is_owned_by can be a generic function in a base model class and you can tweak it in special cases. e.g.

class RentalPhoto(BaseModel):
    def is_owned_by(self, user):
        return self.rental.is_owned_by(user)

This is generic enough and being explicit you will have full control how things behave.

To add new permission you can add that to your models e.g.

class Rental(models.Model):
    # ...
    class Meta:
        permissions = (
            ("can_edit_any", "Can edit any rentals"),
        )

I think instead of adding two permission for any and own, you should add only own permission , so each object already has can_edit which you can treat as user can edit only his object, and if user has permission can_edit_any than only he is allowed to edit all

Using this we can extend auth by adding a custom backend e.g.

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

This is a very quick implemenation, in reality you can extend permission objects to check if it needs and object or not e.g. permission.is_per_object instead of doing crude string search but that should also work if you have standard names

不语却知心 2024-09-24 23:37:38

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

If you don't want to implement your own Permission Backend, I recommend you to use https://github.com/chrisglass/django-rulez You will do what you want in a much easier way.

峩卟喜欢 2024-09-24 23:37:38

它位于 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

It's in Django docs.

Basically you create custom admin class for your model, and define the method get_queryset. In your case it could be something like below. Super user would see all rentals, while owner only his.

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)

Here's another possible lead: https://code.djangoproject.com/wiki/RowLevelPermissions

分分钟 2024-09-24 23:37:38

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

我看不出哪里有必要耍花招:
出租照片 ->出租->用户
因此,要获取特定 RentalPhoto 的用户,您可以在实例中调用类似以下内容:

photo.rental.user

一步中遵循多个关系可以被视为非欺骗。

Can this be solved by some trickery in
the framework, following ForeignKeys
until an object is found with a
ForeignKey to user?

I don't see where there is trickery necessairy:
RentalPhoto -> Rental -> User
So to get the User for a particular RentalPhoto you would call something like this in the instance:

photo.rental.user

Following multiple relations in one step can be considered as non-trickery.

风启觞 2024-09-24 23:37:38
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)
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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文