Django rest-marco por acción permiso

Soy un novato en desarrollo con Django + Django Rest-framework y estoy trabajando en un proyecto que proporciona acceso a la API REST. Me preguntaba cuál es la mejor práctica para asignar un permiso diferente a cada acción de un ApiView o Viewset determinado.

Supongamos que he definido algunas clases de permisos como ‘IsAdmin’, ‘IsRole1’, ‘IsRole2’, …, y quiero conceder diferentes permisos a las acciones individuales (por ejemplo, un usuario con Role1 puede crear o recuperar, un usuario con Role2 puede actualizarse, y solo un administrador puede eliminar).

¿Cómo puedo estructurar una vista basada en clase para asignar una clase de permiso a las acciones ‘crear’, ‘lista’, ‘recuperar’, ‘actualizar’, ‘eliminar’? Intento hacerlo para tener una clase que pueda reutilizarse para diferentes tablas que tengan el mismo patrón de permisos.

Tal vez me estoy ahogando en una pulgada de agua, gracias por sus respuestas.

Puede crear una clase de permiso personalizada extendiendo el BasePermission de DRF.

Implementa has_permission donde tiene acceso a los objetos de request y view . Puede verificar request.user para el rol apropiado y devolver True / False según corresponda.

Eche un vistazo a la clase proporcionada por IsAuthenticatedOrReadOnly (y otras) para ver un buen ejemplo de lo fácil que es.

Espero que eso ayude.

En la documentación DRF,

Nota: el método has_object_permission a nivel de instancia solo se llamará si las comprobaciones has_permission a nivel de vista ya han pasado

Asummos el siguiente permiso sobre user objeto de user

  • Lista: solo personal
  • Crear: cualquiera
  • Recuperar: propio o personal
  • Actualización, actualización parcial: propia o personal
  • Destruir: solo personal

permissons.py

 from rest_framework import permissions class UserPermission(permissions.BasePermission): def has_permission(self, request, view): if view.action == 'list': return request.user.is_authenticated() and request.user.is_admin elif view.action == 'create': return True elif view.action in ['retrieve', 'update', 'partial_update', 'destroy']: return True else: return False def has_object_permission(self, request, view, obj): # Deny actions on objects if the user is not authenticated if not request.user.is_authenticated(): return False if view.action == 'retrieve': return obj == request.user or request.user.is_admin elif view.action in ['update', 'partial_update']: return obj == request.user or request.user.is_admin elif view.action == 'destroy': return request.user.is_admin else: return False 

vistas.py

 from .models import User from .permissions import UserPermission from .serializers import UserSerializer from rest_framework import viewsets class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer permission_classes = (UserPermission,) 

EDITAR

Para Django 2.0, reemplace is_authenticated() con is_authenticated . El método se ha convertido en un atributo.

Django tiene una clase de persmissions llamada DjangoObjectPermissions que usa Django Guardian como backend de autenticación.

Cuando tienes a Django guardian activo en tu configuración, simplemente agregas permission_classes = [DjandoObjectPermissions] a tu vista y realiza la autenticación de permisos automáticamente, por lo que puedes ‘CRUD’ basado en el conjunto de permisos para un grupo o usuario django.contrib.auth particular.

Ver una esencia con un ejemplo.

Puede configurar Django Guardian como su autenticación respaldada http://django-guardian.readthedocs.org/en/latest/installation.html

Las vistas basadas en clase de RestFramework tienen métodos para cada verbo HTTP (es decir, HTTP GET => view.get () etc.). Solo tiene que usar los permisos, usuarios, grupos y decoradores de django.contrib.auth como se documenta.

Personalmente odio este tipo de permisos personalizados de Frankenmonster, en mi opinión, no es muy idiomático cuando se trata del marco de Django; Así que se me ocurrió la siguiente solución: es muy similar a cómo @detail_route decoradores @list_route y @detail_route . Estamos confiando en el hecho de que los métodos / funciones son objetos de primera clase

En primer lugar estoy creando tal decorador:

decorators.py

 def route_action_arguments(**kwargs): """ Add arguments to the action method """ def decorator(func): func.route_action_kwargs = kwargs return func return decorator 

Como puede ver, agrega un diccionario a la función que decora con los parámetros pasados ​​como lista arg

Ahora creé tal mixin: mixins.py

 class RouteActionArgumentsMixin (object): """ Use action specific parameters to provide: - serializer - permissions """ def _get_kwargs(self): action = getattr(self, 'action') if not action: raise AttributeError print('getting route kwargs for action:' + action) action_method = getattr(self, action) kwargs = getattr(action_method, 'route_action_kwargs') print(dir(kwargs)) return kwargs def get_serializer_class(self): try: kwargs = self._get_kwargs() return kwargs['serializer'] except (KeyError, AttributeError): return super(RouteActionArgumentsMixin, self).get_serializer_class() def get_permissions(self): try: kwargs = self._get_kwargs() return kwargs['permission_classes'] except (KeyError, AttributeError): return super(RouteActionArgumentsMixin, self).get_permissions() 

Mixin hace dos cosas; cuando se llama a get_permissions , verifica qué ‘acción’ se ejecuta, y busca la colección permission_classes desde route_action_kwargs asociada con viewset.action_method.route_action_kwargs

cuando se llama a get_serializer_class , hace lo mismo y elige el serializer de route_action_kwargs

Ahora la forma en que podemos usarlo:

 @method_decorator(route_action_arguments(serializer=LoginSerializer), name='create') class UserViewSet (RouteActionArgumentsMixin, RequestContextMixin, viewsets.ModelViewSet): """ User and profile managment viewset """ queryset = User.objects.all() serializer_class = UserSerializer @list_route(methods=['post']) @route_action_arguments(permission_classes=(AllowAny,), serializer=LoginSerializer) def login(self, request): serializer = self.get_serializer_class()(data=request.data) 

Para las rutas personalizadas que definimos explícitamente, podemos establecer los @route_action_arguments explícitamente en el método.

En términos de conjuntos de vistas y métodos generics, todavía podemos agregarlos usando @method_decorator

 @method_decorator(route_action_arguments(serializer=LoginSerializer), name='create') class UserViewSet (RouteActionArgumentsMixin, RequestContextMixin, viewsets.ModelViewSet):