I personally hate this kind of Frankenstein's monster custom permissions, in my opinion, it's not very idiomatic when it comes to the Django framework.
So I came up with the following solution - it's very similar to how @list_route
and @detail_route
decorators work.
We are relying on the fact that the methods/functions are first-class objects
First of all, I'm creating such decorator:
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
As you can see it adds a dictionary to the function it decorates with parameters passed as arg list
Now I created such 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 does two things;
when get_permissions
is called, it checks which 'action' is executed, and looksup the permission_classes collection from the route_action_kwargs
associated with the viewset.action_method.route_action_kwargs
when get_serializer_class
is called, it does the same and picks the serializer
from route_action_kwargs
Now the way we can use it:
@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)
For custom routs we define explicitly we can just set the @route_action_arguments
explicitly on the method.
In terms of the generic viewsets and methods, we can still add them using the
@method_decorator
@method_decorator(route_action_arguments(serializer=LoginSerializer), name='create')
class UserViewSet (RouteActionArgumentsMixin, RequestContextMixin, viewsets.ModelViewSet):
view.action
seems so much more intuitive thanrequest.method
. +1 – Abstractionism