diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py index 11518193e7..24e31144b1 100644 --- a/django/contrib/auth/decorators.py +++ b/django/contrib/auth/decorators.py @@ -64,8 +64,12 @@ def permission_required(perm, login_url=None, raise_exception=False): is raised. """ def check_perms(user): + if not isinstance(perm, (list, tuple)): + perms = (perm, ) + else: + perms = perm # First check if the user has the permission (even anon users) - if user.has_perm(perm): + if user.has_perms(perms): return True # In case the 403 handler should be called raise the exception if raise_exception: diff --git a/django/contrib/auth/tests/test_decorators.py b/django/contrib/auth/tests/test_decorators.py index 6d6d335354..22ad933644 100644 --- a/django/contrib/auth/tests/test_decorators.py +++ b/django/contrib/auth/tests/test_decorators.py @@ -1,7 +1,12 @@ from django.conf import settings -from django.contrib.auth.decorators import login_required +from django.contrib.auth import models +from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.tests.test_views import AuthViewsTestCase from django.contrib.auth.tests.utils import skipIfCustomUser +from django.core.exceptions import PermissionDenied +from django.http import HttpResponse +from django.test import TestCase +from django.test.client import RequestFactory @skipIfCustomUser @@ -49,3 +54,54 @@ class LoginRequiredTestCase(AuthViewsTestCase): """ self.testLoginRequired(view_url='/login_required_login_url/', login_url='/somewhere/') + + +class PermissionsRequiredDecoratorTest(TestCase): + """ + Tests for the permission_required decorator + """ + def setUp(self): + self.user = models.User.objects.create(username='joe', password='qwerty') + self.factory = RequestFactory() + # Add permissions auth.add_customuser and auth.change_customuser + perms = models.Permission.objects.filter(codename__in=('add_customuser', 'change_customuser')) + self.user.user_permissions.add(*perms) + + def test_many_permissions_pass(self): + + @permission_required(['auth.add_customuser', 'auth.change_customuser']) + def a_view(request): + return HttpResponse() + request = self.factory.get('/rand') + request.user = self.user + resp = a_view(request) + self.assertEqual(resp.status_code, 200) + + def test_single_permission_pass(self): + + @permission_required('auth.add_customuser') + def a_view(request): + return HttpResponse() + request = self.factory.get('/rand') + request.user = self.user + resp = a_view(request) + self.assertEqual(resp.status_code, 200) + + def test_permissioned_denied_redirect(self): + + @permission_required(['auth.add_customuser', 'auth.change_customuser', 'non-existant-permission']) + def a_view(request): + return HttpResponse() + request = self.factory.get('/rand') + request.user = self.user + resp = a_view(request) + self.assertEqual(resp.status_code, 302) + + def test_permissioned_denied_exception_raised(self): + + @permission_required(['auth.add_customuser', 'auth.change_customuser', 'non-existant-permission'], raise_exception=True) + def a_view(request): + return HttpResponse() + request = self.factory.get('/rand') + request.user = self.user + self.assertRaises(PermissionDenied, a_view, request) diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 6c28d6e1d0..ec37b382d4 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -135,6 +135,9 @@ Minor features ``Meta`` option allows you to customize (or disable) creation of the default add, change, and delete permissions. +* The :func:`~django.contrib.auth.decorators.permission_required` decorator can + take a list of permissions as well as a single permission. + Backwards incompatible changes in 1.7 ===================================== diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt index 7dff9cdca7..78bf820e89 100644 --- a/docs/topics/auth/default.txt +++ b/docs/topics/auth/default.txt @@ -528,6 +528,11 @@ The permission_required decorator (HTTP Forbidden) view` instead of redirecting to the login page. + .. versionchanged:: 1.7 + + The :func:`~django.contrib.auth.decorators.permission_required` + decorator can take a list of permissions as well as a single permission. + Applying permissions to generic views ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~