Skip to content

Commit 2daf6f1

Browse files
adrienbrunetcarltongibson
authored andcommitted
Add negation ~ operator to permissions composition (#6361)
1 parent 739b0a2 commit 2daf6f1

File tree

3 files changed

+49
-2
lines changed

3 files changed

+49
-2
lines changed

docs/api-guide/permissions.md

+1-1
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

+24
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

+24-1
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)