Разрешения модели Django. Почему это сложно?

Django создает разрешения для каждой создаваемой вами модели, например:

can_view_{model_name}can_add_{model_name}can_edit_{model_name}

Изначально они применимы только к администратору Django. Хорошо, если я хочу применить их на уровне модели, почему я не могу это сделать:

class MyModel(models.Model)
    def can_view(self, user):
        if user.has_perm('my_app.can_view_my_model'):
            return True
        return False

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

Вместо этого мне приходится заходить в каждое представление и вручную проверять:

class MyModelDetail(APIView):

    @transaction.atomic
    def get(self, request):
        try:
            if not request.user.has_perm("my_app.can_view_my_model"):
                raise APIException("You do not have permission to view this model")

И повторите это для всех представлений, которые ищут мою модель.

Есть ли более простой способ?

Пробовали ли вы использовать класс разрешений DjangoModelPermissions? См. django-rest-framework.org/api-guide/permissions/… и stackoverflow.com/questions/46584653/…

Nick ODell 12.04.2024 01:13

Это применимо только к просмотру списка. «Это разрешение должно применяться только к представлениям, имеющим свойство .queryset или метод get_queryset()»

AlxVallejo 12.04.2024 01:48

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

Nick ODell 12.04.2024 02:33

Я не вижу там APIView. Вы предлагаете мне разбить APIView на его общие классы? не понимаю, чем это лучше..

AlxVallejo 12.04.2024 03:14

Да. Альтернативно, вместо использования общего представления, вы можете определить queryset самостоятельно.

Nick ODell 12.04.2024 03:59

«Это разрешение должно применяться только к представлениям, имеющим свойство .queryset или метод get_queryset()» относится к разрешениям >object<. В предыдущем разделе объясняется, как обращаться с проверками разрешений, которые можно выполнить с помощью стандартных классов разрешений. В следующем разделе объясняется, как создавать собственные классы разрешений. Ваш пример не похож на разрешение объекта.

Stephen C 12.04.2024 04:04

И чтобы ответить на ваш вопрос «почему нельзя?» эти разрешения относятся к конкретным функциям страниц администрирования. Расширение их для более общей семантики - это... не то, чего хотели бы все разработчики Django. (В идеальном мире, возможно, Django мог бы иметь более единообразную структуру авторизации/разрешения для веб-страниц, остальных страниц, интерфейса администратора и т. д. Но вам также необходимо учитывать всю историю Django при критике дизайнерских решений. Назад совместимость является серьезной проблемой.)

Stephen C 12.04.2024 04:13

Вы можете это сделать, вам просто нужно будет реализовать это в APIView, так что добавьте это.

willeM_ Van Onsem 12.04.2024 09:25

@NickODell Я не понимаю, что ты предлагаешь. Определить набор запросов в APIView?

AlxVallejo 12.04.2024 15:10

@StephenC Я не понимаю, что показано в APIView. Возможно, вы сможете дать ответ. Я не критикую дизайнерские решения, но когда клиент спрашивает, можем ли мы гарантировать, что пользователь не сможет создать объект на уровне модели, я должен сказать... нет, это не поддерживается Django... нет. проезд очень хорошо.

AlxVallejo 12.04.2024 15:15

@willeM_VanOnsem Вы не можете просто добавить DjangoModelPermissions в APIView, поскольку он будет жаловаться, что «невозможно применить DjangoModelPermissions к представлению, которое не устанавливает .queryset или не имеет .get_queryset() метода».

AlxVallejo 12.04.2024 15:18

@AlxVallejo: Я говорю о has_object_permission: django-rest-framework.org/api-guide/permissions/… вы можете даже использовать это как «механизм двойной объектно-ориентированной диспетчеризации», если хотите.

willeM_ Van Onsem 12.04.2024 15:25

Использование простого .get, конечно, не сработает, поскольку тогда вы по сути обходите разрешения, но для GenericAPIView или ModelViewSet это работает.

willeM_ Van Onsem 12.04.2024 15:25

@StephenC Кроме того, я не понимаю, насколько вы правы в применении к разрешениям объекта. Это четко указано в DjangoModelPermissions, на что намекает верхний комментарий.

AlxVallejo 12.04.2024 15:34

@willeM_VanOnsem Хотя я не думал, что это права доступа к объектам. Если у вас есть решение, опубликуйте его, и я попробую.

AlxVallejo 12.04.2024 15:34

@AlxVallejo: базовое разрешение имеет два метода: has_permission для первой проверки запроса без объекта и для объектно-ориентированных запросов (например, получение, обновление и т. д. одного объекта) затем используется .has_object_permission.

willeM_ Van Onsem 12.04.2024 15:37

Что касается разрешения can_add, это выглядит странно, поскольку оно не связано с объектом: вы не добавляете объект модели, возможно, сам класс модели.

willeM_ Van Onsem 12.04.2024 15:40

@willeM_VanOnsem Это все здорово и все такое, но если не будет предложено какое-то конкретное решение, я, к сожалению, не понимаю, как что-то будет лучше простой проверки разрешений для каждого метода просмотра.

AlxVallejo 12.04.2024 16:00
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
18
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В платформе Django REST есть классы BasePermission [drf-doc], которые можно использовать для этого. Такое разрешение может выглядеть так:

from rest_framework import permissions


class AdminModelPermission(permissions.BasePermission):
    METHOD_MAPPING = {
        'GET': 'view',
        'POST': 'add',
        'PUT': 'edit',
        'PATCH': 'edit',
        'DELETE': 'delete',
    }

    def has_permission(self, request, view):
        meta = view.get_queryset().model._meta
        action = self.METHOD_MAPPING.get(request.method)
        if action is not None:
            return request.user.has_perm(
                f'{meta.app_label}.{action}_{meta.model_name}'
            )
        return True

    def has_object_permission(self, request, view, obj):
        action = self.METHOD_MAPPING.get(request.method)
        if action is not None:
            method = getattr(obj, f'can_{action}', None)
            if method is not None:
                return method(request.user)
        return True

а затем вставьте это в GenericAPIView, GenericViewSet или ModelViewSet, например:

from rest_framework import viewsets


class MyModelViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    permission_classes = (AdminModelPermission,)

Если мы теперь, например, отправим запрос DELETE, он сначала проверит, есть ли у пользователя разрешение app_label.delete_mymodel. Если да, он извлечет объект, а если сама модель имеет метод .can_delete(…), она вызовет его для вошедшего в систему пользователя, чтобы проверить, может ли он удалить этот конкретный объект. Если так, то, наконец, оно продолжится.

Однако важно, по крайней мере, получить объект через .get_object(…), поскольку именно здесь платформа REST Django проверяет .has_object_permission(…) всех установленных разрешений и, кроме того, позволяет ей самой выполнить диспетчеризацию для вызова метода .has_permission(…).

ОК, это кажется отличным подходом, которого я больше нигде не видел. Я отчитаюсь о том, чем это обернется.

AlxVallejo 12.04.2024 17:01

Другие вопросы по теме