Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ruslan-korneev/59077cdbc5b3ec296e11843a27c102f4 to your computer and use it in GitHub Desktop.
Save ruslan-korneev/59077cdbc5b3ec296e11843a27c102f4 to your computer and use it in GitHub Desktop.

Serializer and Permission per action in Django REST Framework

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

Usage Example

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)

Mixins Implementation

Serializer Per Action

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

Permission Per Action

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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment