Добавление разрешений для объекта для администратора django

Фон

Я разрабатываю приложение django для сайта аренды для отпуска. Он будет иметь двух типов пользователей, арендаторов и управляющих недвижимостью.

Я хочу, чтобы менеджеры по недвижимости могли управлять своими свойствами аренды в admin django. Однако они должны иметь возможность управлять своими собственными свойствами.

Я понимаю, что admin 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, чтобы увидеть, имеет ли он от ForeignKey до auth.User (возможно, имеющее определенное имя, например "владелец" ).

  • При создании нового vacation.Rental значение поля ForeignKey будет принудительно привязано к текущему идентификатору пользователя. Поле ForeignKey не будет отображаться в форме.

  • При перечислении арендной платы будет отображаться только аренда с ForeignKey, соответствующая текущему пользователю.

  • При изменении арендной платы будет отображаться только аренда с ForeignKey, совпадающая с текущим пользователем. Поле ForeignKey не будет отображаться в форме.

Конечно, это должно работать для любой модели, имеющей соответствующее поле ForeignKey, а не только нашу модель vacation.Rental.

До сих пор это звучит, или я должен идти в другом направлении?


Осложнения

Теперь, здесь сложная часть; Я не уверен, как справиться с этим. Скажем, a Rental может иметь много "RentalPhotos". RentalPhoto имеет a ForeignKey to Rental. Пользователи должны иметь возможность добавлять фотографии в собственную аренду. Однако на фотографиях нет пользователя ForeignKey, поэтому нет возможности напрямую узнать, кому принадлежит фотография.

Можно ли это решить с помощью некоторой обманчивости в рамках, следуя ForeignKey, пока объект не будет найден с помощью ForeignKey для пользователя? Или я должен сделать простой выход и дать RentalPhoto (и все остальное "принадлежать" Rental) своему собственному ForeignKey к соответствующему auth.User? Второй подход потребует ненужного избыточности, первый, вероятно, потребует ненужных накладных расходов...

Если я собираюсь сбиться с пути, пожалуйста, не стесняйтесь указывать мне в правильном направлении. Заранее благодарим за любую помощь.

Ответы

Ответ 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, чем только ему разрешено редактировать все

Используя это, мы можем расширить auth, добавив пользовательский бэкэнд, например.

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 вместо того, чтобы выполнять грубый поиск строки, но это также должно работать, если у вас есть стандартные имена

Ответ 2

Можно ли это решить с помощью некоторых обманщиков в рамки, следуя за ForeignKeys пока объект не будет найден с ForeignKey для пользователя?

Я не вижу, где есть сложность: RentalPhoto → Аренда → Пользователь Таким образом, чтобы получить пользователя для конкретного RentalPhoto, вы вызываете что-то вроде этого в экземпляре:

photo.rental.user

После нескольких отношений за один шаг можно считать неинтеллектуальным.

Ответ 3

Если вы не хотите реализовывать свой собственный сервер разрешений, я рекомендую вам использовать https://github.com/chrisglass/django-rulez. Вы будете делать то, что хотите в намного проще.

Ответ 4

В 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