Mixin which allows to define specific permissions per actions.
`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]
|
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)
|
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
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,
),
)
|
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]
|