In Django REST Framework you have a Viewsets, which can contain multiple views (actions).
But to use different serializers and permissions for each action you always need to override get_serializer_class
or get_permissions
methods.
Here I've implemented a mixins which allow you to setup your serializers and permissions per action in a dictionaries
class UserViewSet(SerializerPerAction, PermissionPerAction, ModelViewSet):
queryset = User.objects.all()
action_serializers = {
# this default serializer will be for actions without serializers (in this example: list, create, update, partial_update, destroy)
"default": UserListSerializer,
"retrieve": UserRetrieveSerializer,
"verify": UserVerificationSerializer,
}
@action(detail=False, methods=["PATCH"], ...)
def verify(self, request, *args, **kwargs):
""" just an example of extra action """
...
serializer = self.get_serializer(request.data) # here we'll get UserVerificationSerializer
serializer.is_valid(raise_exception=True)
return Response(serializer.data)
from typing import Type
from rest_framework.serializers import BaseSerializer
class SerializerPerAction:
"""
Mixin that allows to use different serializers for different actions.
"""
action: str
serializer_class: Type[BaseSerializer]
action_serializer: dict[str, Type[BaseSerializer]]
def get_serializer_class(self):
assert getattr(self, "action_serializer", None) is not None, (
f"'{self.__class__.__name__}' should either include a `action_serializer` attribute, "
"or override the `get_serializer_class()` method."
)
assert isinstance(self.action_serializer, dict), (
f"'{self.__class__.__name__}' `action_serializer` attribute should be a dict."
)
assert self.action_serializer.get("default", None) is not None, (
f"'{self.__class__.__name__}' `action_serializer` attribute should have a 'default' key."
)
self.serializer_class = self.action_serializer.get(
self.action, self.action_serializer["default"]
)
return super().get_serializer_class()
from typing import Type
from rest_framework.permissions import BasePermission
class PermissionPerActionMixin:
action: str
permission_classes: list[Type[BasePermission]]
action_permissions: dict[str, list[Type[BasePermission]]]
def get_permissions(self):
assert self.action_permissions is not None, (
f"{self.__class__.__name__} needs to define an "
f"`action_permissions` or don't inherit {self.__class__.__name__}"
)
assert self.action_permissions.get("default"), (
f"{self.__class__.__name__} needs to define a "
"`default` in `action_permissions` attribute"
)
self.permission_classes = self.action_permissions.get(
self.action, self.action_permissions["default"]
)
return super(PermissionPerAction, self).get_permissions()