diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index a797da9ace..6a1297e60f 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -134,7 +134,7 @@ Provided they inherit from `rest_framework.permissions.BasePermission`, permissi } return Response(content) -__Note:__ it only supports & -and- and | -or-. +__Note:__ it supports & (and), | (or) and ~ (not). --- diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 69432d79ab..5d75f54bad 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -24,6 +24,19 @@ def __rand__(self, other): def __ror__(self, other): return OperandHolder(OR, other, self) + def __invert__(self): + return SingleOperandHolder(NOT, self) + + +class SingleOperandHolder(OperationHolderMixin): + def __init__(self, operator_class, op1_class): + self.operator_class = operator_class + self.op1_class = op1_class + + def __call__(self, *args, **kwargs): + op1 = self.op1_class(*args, **kwargs) + return self.operator_class(op1) + class OperandHolder(OperationHolderMixin): def __init__(self, operator_class, op1_class, op2_class): @@ -73,6 +86,17 @@ def has_object_permission(self, request, view, obj): ) +class NOT: + def __init__(self, op1): + self.op1 = op1 + + def has_permission(self, request, view): + return not self.op1.has_permission(request, view) + + def has_object_permission(self, request, view, obj): + return not self.op1.has_object_permission(request, view, obj) + + class BasePermissionMetaclass(OperationHolderMixin, type): pass diff --git a/tests/test_permissions.py b/tests/test_permissions.py index f9d53430fd..8070068586 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -580,7 +580,19 @@ def test_or_true(self): composed_perm = permissions.IsAuthenticated | permissions.AllowAny assert composed_perm().has_permission(request, None) is True - def test_several_levels(self): + def test_not_false(self): + request = factory.get('/1', format='json') + request.user = AnonymousUser() + composed_perm = ~permissions.IsAuthenticated + assert composed_perm().has_permission(request, None) is True + + def test_not_true(self): + request = factory.get('/1', format='json') + request.user = self.user + composed_perm = ~permissions.AllowAny + assert composed_perm().has_permission(request, None) is False + + def test_several_levels_without_negation(self): request = factory.get('/1', format='json') request.user = self.user composed_perm = ( @@ -591,6 +603,17 @@ def test_several_levels(self): ) assert composed_perm().has_permission(request, None) is True + def test_several_levels_and_precedence_with_negation(self): + request = factory.get('/1', format='json') + request.user = self.user + composed_perm = ( + permissions.IsAuthenticated & + ~ permissions.IsAdminUser & + permissions.IsAuthenticated & + ~(permissions.IsAdminUser & permissions.IsAdminUser) + ) + assert composed_perm().has_permission(request, None) is True + def test_several_levels_and_precedence(self): request = factory.get('/1', format='json') request.user = self.user