From 651a6afad3f372b6bb8a249cc47c456a52bce5d6 Mon Sep 17 00:00:00 2001 From: Igor Nemilentsev Date: Mon, 13 Aug 2018 17:56:57 +0300 Subject: [PATCH] Preserve permission messages. Updated tests for django 2.0. --- .gitignore | 2 ++ Pipfile | 12 ++++++++ Pipfile.lock | 29 ++++++++++++++++++ rest_condition/permissions.py | 23 ++++++++++---- tests/tests/test_permissions.py | 53 ++++++++++++++++++++++++++++++++- tests/urls.py | 10 +++++-- 6 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock diff --git a/.gitignore b/.gitignore index 5163d48..1d64bc8 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,5 @@ venv/ # Some temp files .DS_Store + +.idea/ diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..eaab3cd --- /dev/null +++ b/Pipfile @@ -0,0 +1,12 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +djangorestframework = "==3.8.*" + +[dev-packages] + +[requires] +python_version = "*" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..baa2957 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,29 @@ +{ + "_meta": { + "hash": { + "sha256": "5fd8f3cebbff5897595b76de89469acb544363de360b58de933fae2b1520f302" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "*" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "djangorestframework": { + "hashes": [ + "sha256:b6714c3e4b0f8d524f193c91ecf5f5450092c2145439ac2769711f7eba89a9d9", + "sha256:c375e4f95a3a64fccac412e36fb42ba36881e52313ec021ef410b40f67cddca4" + ], + "index": "pypi", + "version": "==3.8.2" + } + }, + "develop": {} +} diff --git a/rest_condition/permissions.py b/rest_condition/permissions.py index ac07ff1..2a20676 100644 --- a/rest_condition/permissions.py +++ b/rest_condition/permissions.py @@ -15,7 +15,7 @@ def _is_permission_factory(obj): class ConditionalPermission(permissions.BasePermission): - ''' + """ Example of usage: >>> class MyView(GinericView): >>> permission_classes = (ConditionalPermission, ) @@ -24,7 +24,7 @@ class ConditionalPermission(permissions.BasePermission): Or you can just: >>> class MyView(GinericView): >>> permission_classes = (C(Perm1) | ~C(Perm2), ) - ''' + """ permission_condition_field = 'permission_condition' def get_permission_condition(self, view): @@ -47,7 +47,7 @@ def has_permission(self, request, view): class Condition(object): - ''' + """ Provides a simple way to define complex and multi-depth (with logic operators) permissions tree. @@ -61,7 +61,7 @@ class Condition(object): permission will be evaluated to `True`: >>> cond1 = C(Perm1, Perm2, Perm3, Perm4, >>> reduce_op=operator.add, lazy_until=3, negated=True) - ''' + """ @classmethod def And(cls, *perms_or_conds): return cls(reduce_op=operator.and_, lazy_until=False, *perms_or_conds) @@ -79,13 +79,17 @@ def __init__(self, *perms_or_conds, **kwargs): self.reduce_op = kwargs.get('reduce_op', operator.and_) self.lazy_until = kwargs.get('lazy_until', False) self.negated = kwargs.get('negated') + self.message = None def evaluate_permissions(self, permission_name, *args, **kwargs): + reduced_result = _NONE + last_condition = None for condition in self.perms_or_conds: if hasattr(condition, 'evaluate_permissions'): - result = condition.evaluate_permissions(permission_name, *args, **kwargs) + result = condition.evaluate_permissions( + permission_name, *args, **kwargs) else: if _is_permission_factory(condition): condition = condition() @@ -103,9 +107,16 @@ def evaluate_permissions(self, permission_name, *args, **kwargs): else: reduced_result = self.reduce_op(reduced_result, result) - if self.lazy_until is not None and self.lazy_until is reduced_result: + last_condition = condition + + if self.lazy_until is not None and \ + self.lazy_until is reduced_result: break + message = getattr(last_condition, 'message', None) + if message: + self.message = message + if reduced_result is not _NONE: return not reduced_result if self.negated else reduced_result diff --git a/tests/tests/test_permissions.py b/tests/tests/test_permissions.py index 8a60cc6..cf1b230 100644 --- a/tests/tests/test_permissions.py +++ b/tests/tests/test_permissions.py @@ -23,15 +23,34 @@ def test_permission(self, request): return True + def test_message(self, request): + from rest_framework.request import Request + + request = Request(request) + + self.request = request + + last_permission = None + + for permission in self.get_permissions(): + last_permission = permission + if not permission.has_permission(request, self): + return permission.message + return last_permission.message + class TruePermission(BasePermission): + message = "YES" + def has_permission(self, request, view): return True class FalsePermission(BasePermission): + message = "NO" + def has_permission(self, request, view): return False @@ -50,7 +69,13 @@ def assertViewPermission(self, view_class, granted=True): else: self.assertFalse(result) - def test_conditional_permissions_with_assigment(self): + def assertViewPermissionMessage(self, view_class, message): + view = view_class() + request = self.requests.get('/') + permission_message = view.test_message(request) + self.assertEqual(message, permission_message) + + def test_conditional_permissions_with_assignment(self): perm1 = C(TruePermission) perm1 |= ~C(TruePermission) @@ -70,6 +95,32 @@ class View2(TestView): self.assertViewPermission(View2, True) + def test_conditional_permissions_with_message(self): + + perm1 = And(TruePermission, FalsePermission) + + class View1(TestView): + permission_classes = [perm1] + + self.assertViewPermissionMessage(View1, FalsePermission.message) + + perm2 = Or(TruePermission, FalsePermission) + + class View2(TestView): + permission_classes = [perm2] + + self.assertViewPermissionMessage(View2, TruePermission.message) + + perm3 = And( + Or(Not(FalsePermission), FalsePermission), + Or(Not(TruePermission), FalsePermission) + ) + + class View3(TestView): + permission_classes = [perm3] + + self.assertViewPermissionMessage(View3, FalsePermission.message) + def test_single_conditional_permission_true(self): class View1(TestView): diff --git a/tests/urls.py b/tests/urls.py index 2b569f8..7b0a945 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -1,9 +1,15 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +from rest_framework.views import APIView try: - from django.conf.urls import include, patterns, url + from django.conf.urls import patterns except ImportError: - from django.conf.urls.defaults import include, patterns, url + try: + from django.conf.urls.defaults import patterns + except ImportError: + from django.urls import path + patterns = lambda x: [path('', APIView.as_view())] + urlpatterns = patterns('', )