Skip to content

Commit 2d0d7ff

Browse files
adrienbrunetsigvef
authored andcommitted
Add negation ~ operator to permissions composition (encode#6361)
1 parent 73adeee commit 2d0d7ff

File tree

3 files changed

+49
-2
lines changed

3 files changed

+49
-2
lines changed

docs/api-guide/permissions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ Provided they inherit from `rest_framework.permissions.BasePermission`, permissi
134134
}
135135
return Response(content)
136136

137-
__Note:__ it only supports & -and- and | -or-.
137+
__Note:__ it supports & (and), | (or) and ~ (not).
138138

139139
---
140140

rest_framework/permissions.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ def __rand__(self, other):
2424
def __ror__(self, other):
2525
return OperandHolder(OR, other, self)
2626

27+
def __invert__(self):
28+
return SingleOperandHolder(NOT, self)
29+
30+
31+
class SingleOperandHolder(OperationHolderMixin):
32+
def __init__(self, operator_class, op1_class):
33+
self.operator_class = operator_class
34+
self.op1_class = op1_class
35+
36+
def __call__(self, *args, **kwargs):
37+
op1 = self.op1_class(*args, **kwargs)
38+
return self.operator_class(op1)
39+
2740

2841
class OperandHolder(OperationHolderMixin):
2942
def __init__(self, operator_class, op1_class, op2_class):
@@ -73,6 +86,17 @@ def has_object_permission(self, request, view, obj):
7386
)
7487

7588

89+
class NOT:
90+
def __init__(self, op1):
91+
self.op1 = op1
92+
93+
def has_permission(self, request, view):
94+
return not self.op1.has_permission(request, view)
95+
96+
def has_object_permission(self, request, view, obj):
97+
return not self.op1.has_object_permission(request, view, obj)
98+
99+
76100
class BasePermissionMetaclass(OperationHolderMixin, type):
77101
pass
78102

tests/test_permissions.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,19 @@ def test_or_true(self):
580580
composed_perm = permissions.IsAuthenticated | permissions.AllowAny
581581
assert composed_perm().has_permission(request, None) is True
582582

583-
def test_several_levels(self):
583+
def test_not_false(self):
584+
request = factory.get('/1', format='json')
585+
request.user = AnonymousUser()
586+
composed_perm = ~permissions.IsAuthenticated
587+
assert composed_perm().has_permission(request, None) is True
588+
589+
def test_not_true(self):
590+
request = factory.get('/1', format='json')
591+
request.user = self.user
592+
composed_perm = ~permissions.AllowAny
593+
assert composed_perm().has_permission(request, None) is False
594+
595+
def test_several_levels_without_negation(self):
584596
request = factory.get('/1', format='json')
585597
request.user = self.user
586598
composed_perm = (
@@ -591,6 +603,17 @@ def test_several_levels(self):
591603
)
592604
assert composed_perm().has_permission(request, None) is True
593605

606+
def test_several_levels_and_precedence_with_negation(self):
607+
request = factory.get('/1', format='json')
608+
request.user = self.user
609+
composed_perm = (
610+
permissions.IsAuthenticated &
611+
~ permissions.IsAdminUser &
612+
permissions.IsAuthenticated &
613+
~(permissions.IsAdminUser & permissions.IsAdminUser)
614+
)
615+
assert composed_perm().has_permission(request, None) is True
616+
594617
def test_several_levels_and_precedence(self):
595618
request = factory.get('/1', format='json')
596619
request.user = self.user

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy