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

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

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

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

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")

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

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

Во фреймворке 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(…), поскольку именно там фреймворк Django REST проверяет .has_object_permission(…) все установленные разрешения и, кроме того, позволяет ему самому выполнить диспетчеризацию для вызова метода .has_permission(…).

Вернуться на верх