Разрешения модели 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(…)
.