diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index b426546a7..dc4a3f0b8 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -119,6 +119,7 @@ class OR: class NOT: def __init__(self, op1): self.op1 = op1 + self.message = getattr(self.op1, 'message_inverted', None) def has_permission(self, request, view): return not self.op1.has_permission(request, view) diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 59009de68..fbe11970e 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -20,6 +20,7 @@ factory = APIRequestFactory() CUSTOM_MESSAGE_1 = 'Custom: You cannot access this resource' CUSTOM_MESSAGE_2 = 'Custom: You do not have permission to view this resource' +INVERTED_MESSAGE = 'Inverted: Your account already active' class BasicSerializer(serializers.ModelSerializer): @@ -458,6 +459,7 @@ class BasicPerm(permissions.BasePermission): class BasicPermWithDetail(permissions.BasePermission): message = CUSTOM_MESSAGE_1 + message_inverted = INVERTED_MESSAGE code = 'permission_denied_custom' def has_permission(self, request, view): @@ -478,6 +480,7 @@ class BasicObjectPerm(permissions.BasePermission): class BasicObjectPermWithDetail(permissions.BasePermission): message = CUSTOM_MESSAGE_1 + message_inverted = INVERTED_MESSAGE code = 'permission_denied_custom' def has_object_permission(self, request, view, obj): @@ -528,6 +531,10 @@ class DeniedViewWithDetailOR3(PermissionInstanceView): permission_classes = (BasicPermWithDetail | AnotherBasicPermWithDetail,) +class DeniedViewWithDetailNOT(PermissionInstanceView): + permission_classes = (~BasicPermWithDetail,) + + class DeniedObjectView(PermissionInstanceView): permission_classes = (BasicObjectPerm,) @@ -560,6 +567,10 @@ class DeniedObjectViewWithDetailOR3(PermissionInstanceView): permission_classes = (BasicObjectPermWithDetail | AnotherBasicObjectPermWithDetail,) +class DeniedObjectViewWithDetailNOT(PermissionInstanceView): + permission_classes = (~BasicObjectPermWithDetail,) + + denied_view = DeniedView.as_view() denied_view_with_detail = DeniedViewWithDetail.as_view() @@ -572,6 +583,8 @@ denied_view_with_detail_or_1 = DeniedViewWithDetailOR1.as_view() denied_view_with_detail_or_2 = DeniedViewWithDetailOR2.as_view() denied_view_with_detail_or_3 = DeniedViewWithDetailOR3.as_view() +denied_view_with_detail_not = DeniedObjectViewWithDetailNOT.as_view() + denied_object_view = DeniedObjectView.as_view() denied_object_view_with_detail = DeniedObjectViewWithDetail.as_view() @@ -584,6 +597,8 @@ denied_object_view_with_detail_or_1 = DeniedObjectViewWithDetailOR1.as_view() denied_object_view_with_detail_or_2 = DeniedObjectViewWithDetailOR2.as_view() denied_object_view_with_detail_or_3 = DeniedObjectViewWithDetailOR3.as_view() +denied_object_view_with_detail_not = DeniedObjectViewWithDetailNOT.as_view() + class CustomPermissionsTests(TestCase): def setUp(self): @@ -644,6 +659,12 @@ class CustomPermissionsTests(TestCase): expected_message = '"{0}" OR "{1}"'.format(CUSTOM_MESSAGE_1, CUSTOM_MESSAGE_2) self.assertEqual(detail, expected_message) + def test_permission_denied_with_custom_detail_not(self): + response = denied_view_with_detail_not(self.request, pk=1) + detail = response.data.get('detail') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(detail, INVERTED_MESSAGE) + def test_permission_denied_for_object(self): response = denied_object_view(self.request, pk=1) detail = response.data.get('detail') @@ -695,6 +716,12 @@ class CustomPermissionsTests(TestCase): expected_message = '"{0}" OR "{1}"'.format(CUSTOM_MESSAGE_1, CUSTOM_MESSAGE_2) self.assertEqual(detail, expected_message) + def test_permission_denied_for_object_with_custom_detail_not(self): + response = denied_object_view_with_detail_not(self.request, pk=1) + detail = response.data.get('detail') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(detail, INVERTED_MESSAGE) + class PermissionsCompositionTests(TestCase):