Skip to content

Permissions Map Mixin

ActionPermissionsMixin

Mixin which allows to define specific permissions per actions.

Attributes

`base_permission_classes` - Base permissions which are supposed to be
    defined in base class and typically should not be overridden.
    If define `base_permission_classes` without any extra permissions
    (`extra_permission_classes`, `extra_permissions_map`) then it acts
    exactly like `permission_classes` from drf.
`extra_permission_classes` - Extra permissions which are supposed to
    be overridden if needed. This attribute allows to set extra
    permissions in child classes.
`extra_permissions_map` - Mapping which allows to set specific
    permissions per action. If permissions for the action were found in
    `extra_permissions_map` then will be returned permissions from
    `base_permission_classes` + specific permission for the action from
    this map.
    If permissions for the action were not found in this map then
    `base_permission_classes` with `extra_permission_classes` will be
    used the same way it would work as if `extra_permissions_map` was
    not provided at all.

It can be used both ways:

1) With providing base_permission_classes + extra_permission_classes:

class BaseViewSet(ActionPermissionsMixin, viewsets.ModelViewSet):
    base_permission_classes = (
        IsAuthenticated,
    )


class DogViewSet(BaseViewset):
    # In order to access this viewset user should have `IsAuthenticated`
    # permission from base viewset and `CanSayWoof` permission
    # from this viewset
    extra_permission_classes = (
        CanSayWoof,
    )

2) With providing extra_permissions_map:

class DogViewSet(BaseViewset):
    # In order to get access to these actions user should also have
    # `IsAuthenticated` permission from
    # `BaseViewset.base_permission_classes`
    extra_permissions_map = {
        "bark": (
            CanSayWoof,
        ),
        # Only permissions from `base_permission_classes` will be used for
        # this action
        "sit": (),
    }
    # For all actions which were not found in `extra_permissions_map`
    # will be used permissions from `base_permission_classes` +
    # `extra_permission_classes`
    extra_permission_classes = (
        CanEatBones,
    )

Source code in saritasa_drf_tools/views/mixins/permissions_map_mixin.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
class ActionPermissionsMixin:
    """Mixin which allows to define specific permissions per actions.

    Attributes
    ----------
        `base_permission_classes` - Base permissions which are supposed to be
            defined in base class and typically should not be overridden.
            If define `base_permission_classes` without any extra permissions
            (`extra_permission_classes`, `extra_permissions_map`) then it acts
            exactly like `permission_classes` from drf.
        `extra_permission_classes` - Extra permissions which are supposed to
            be overridden if needed. This attribute allows to set extra
            permissions in child classes.
        `extra_permissions_map` - Mapping which allows to set specific
            permissions per action. If permissions for the action were found in
            `extra_permissions_map` then will be returned permissions from
            `base_permission_classes` + specific permission for the action from
            this map.
            If permissions for the action were not found in this map then
            `base_permission_classes` with `extra_permission_classes` will be
            used the same way it would work as if `extra_permissions_map` was
            not provided at all.

    It can be used both ways:

    1) With providing `base_permission_classes` + `extra_permission_classes`:
    ```
    class BaseViewSet(ActionPermissionsMixin, viewsets.ModelViewSet):
        base_permission_classes = (
            IsAuthenticated,
        )


    class DogViewSet(BaseViewset):
        # In order to access this viewset user should have `IsAuthenticated`
        # permission from base viewset and `CanSayWoof` permission
        # from this viewset
        extra_permission_classes = (
            CanSayWoof,
        )
    ```

    2) With providing `extra_permissions_map`:
    ```
    class DogViewSet(BaseViewset):
        # In order to get access to these actions user should also have
        # `IsAuthenticated` permission from
        # `BaseViewset.base_permission_classes`
        extra_permissions_map = {
            "bark": (
                CanSayWoof,
            ),
            # Only permissions from `base_permission_classes` will be used for
            # this action
            "sit": (),
        }
        # For all actions which were not found in `extra_permissions_map`
        # will be used permissions from `base_permission_classes` +
        # `extra_permission_classes`
        extra_permission_classes = (
            CanEatBones,
        )
    ```

    """

    action: str

    base_permission_classes: PermissionsTypesSequence = (
        permissions.IsAdminUser,
    )

    extra_permission_classes: PermissionsTypesSequence = ()

    extra_permissions_map: dict[str, PermissionsTypesSequence]

    # `permission_classes` is not supported so declaring as final to provide
    # warnings on adding this attribute to classes
    permission_classes: typing.Final = None

    def get_permissions(self) -> list[permissions.BasePermission]:
        """Return permissions list for current `.action` attribute value.

        It returns permission from `base_permission_classes` +
        permissions list from `extra_permissions_map` using view's action as
        key.
        If view doesn't have `extra_permissions_map` just return
        `base_permission_classes` + `extra_permission_classes`

        Returns
        -------
            All permissions for the action

        """
        if getattr(self, "permission_classes", None) is not None:
            raise exceptions.ImproperlyConfigured(
                "`permission_classes` is not supported.\n"
                "Use `base_permission_classes` + `extra_permission_classes` "
                "or `base_permission_classes` + `extra_permissions_map` "
                "instead.\n"
                "Check `ActionPermissionsMixin` docs for additional "
                "information",
            )

        action = getattr(self, "action", None)
        action_extra_permission_classes = (
            self.get_action_extra_permission_classes(action=action)
        )

        return self.get_unique_permissions(
            permissions=(
                *self.base_permission_classes,
                *action_extra_permission_classes,
            ),
        )

    def get_action_extra_permission_classes(
        self,
        action: str | None,
    ) -> PermissionsTypesSequence:
        """Return extra permissions for the action.

        If the action is provided in `extra_permissions_map` then return
        permissions for this action, otherwise return just all permissions from
        `extra_permission_classes`.

        """
        extra_permissions_map = getattr(self, "extra_permissions_map", {})
        return extra_permissions_map.get(action, self.extra_permission_classes)

    def get_unique_permissions(
        self,
        permissions: PermissionsTypesSequence,
    ) -> list[permissions.BasePermission]:
        """Return list of unique permissions with keeping original order."""
        unique_permissions = list(dict.fromkeys(permissions))
        return [permission() for permission in unique_permissions]

get_action_extra_permission_classes(action)

Return extra permissions for the action.

If the action is provided in extra_permissions_map then return permissions for this action, otherwise return just all permissions from extra_permission_classes.

Source code in saritasa_drf_tools/views/mixins/permissions_map_mixin.py
129
130
131
132
133
134
135
136
137
138
139
140
141
def get_action_extra_permission_classes(
    self,
    action: str | None,
) -> PermissionsTypesSequence:
    """Return extra permissions for the action.

    If the action is provided in `extra_permissions_map` then return
    permissions for this action, otherwise return just all permissions from
    `extra_permission_classes`.

    """
    extra_permissions_map = getattr(self, "extra_permissions_map", {})
    return extra_permissions_map.get(action, self.extra_permission_classes)

get_permissions()

Return permissions list for current .action attribute value.

It returns permission from base_permission_classes + permissions list from extra_permissions_map using view's action as key. If view doesn't have extra_permissions_map just return base_permission_classes + extra_permission_classes

Returns
All permissions for the action
Source code in saritasa_drf_tools/views/mixins/permissions_map_mixin.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def get_permissions(self) -> list[permissions.BasePermission]:
    """Return permissions list for current `.action` attribute value.

    It returns permission from `base_permission_classes` +
    permissions list from `extra_permissions_map` using view's action as
    key.
    If view doesn't have `extra_permissions_map` just return
    `base_permission_classes` + `extra_permission_classes`

    Returns
    -------
        All permissions for the action

    """
    if getattr(self, "permission_classes", None) is not None:
        raise exceptions.ImproperlyConfigured(
            "`permission_classes` is not supported.\n"
            "Use `base_permission_classes` + `extra_permission_classes` "
            "or `base_permission_classes` + `extra_permissions_map` "
            "instead.\n"
            "Check `ActionPermissionsMixin` docs for additional "
            "information",
        )

    action = getattr(self, "action", None)
    action_extra_permission_classes = (
        self.get_action_extra_permission_classes(action=action)
    )

    return self.get_unique_permissions(
        permissions=(
            *self.base_permission_classes,
            *action_extra_permission_classes,
        ),
    )

get_unique_permissions(permissions)

Return list of unique permissions with keeping original order.

Source code in saritasa_drf_tools/views/mixins/permissions_map_mixin.py
143
144
145
146
147
148
149
def get_unique_permissions(
    self,
    permissions: PermissionsTypesSequence,
) -> list[permissions.BasePermission]:
    """Return list of unique permissions with keeping original order."""
    unique_permissions = list(dict.fromkeys(permissions))
    return [permission() for permission in unique_permissions]