diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 26728b2d6..dee0032f9 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -114,6 +114,21 @@ class DjangoModelPermissions(BasePermission): return [perm % kwargs for perm in self.perms_map[method]] + def _queryset(self, view): + assert hasattr(view, 'get_queryset') \ + or getattr(view, 'queryset', None) is not None, ( + 'Cannot apply {} on a view that does not set ' + '`.queryset` or have a `.get_queryset()` method.' + ).format(self.__class__.__name__) + + if hasattr(view, 'get_queryset'): + queryset = view.get_queryset() + assert queryset is not None, ( + '{}.get_queryset() returned None'.format(view.__class__.__name__) + ) + return queryset + return view.queryset + def has_permission(self, request, view): # Workaround to ensure DjangoModelPermissions are not applied # to the root view when using DefaultRouter. @@ -124,19 +139,7 @@ class DjangoModelPermissions(BasePermission): not is_authenticated(request.user) and self.authenticated_users_only): return False - if hasattr(view, 'get_queryset'): - queryset = view.get_queryset() - assert queryset is not None, ( - '{}.get_queryset() returned None'.format(view.__class__.__name__) - ) - else: - queryset = getattr(view, 'queryset', None) - - assert queryset is not None, ( - 'Cannot apply DjangoModelPermissions on a view that ' - 'does not set `.queryset` or have a `.get_queryset()` method.' - ) - + queryset = self._queryset(view) perms = self.get_required_permissions(request.method, queryset.model) return request.user.has_perms(perms) @@ -183,16 +186,8 @@ class DjangoObjectPermissions(DjangoModelPermissions): return [perm % kwargs for perm in self.perms_map[method]] def has_object_permission(self, request, view, obj): - if hasattr(view, 'get_queryset'): - queryset = view.get_queryset() - else: - queryset = getattr(view, 'queryset', None) - - assert queryset is not None, ( - 'Cannot apply DjangoObjectPermissions on a view that ' - 'does not set `.queryset` or have a `.get_queryset()` method.' - ) - + # authentication checks have already executed via has_permission + queryset = self._queryset(view) model_cls = queryset.model user = request.user diff --git a/tests/test_permissions.py b/tests/test_permissions.py index ed5c7af7a..f673c3671 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -9,7 +9,7 @@ from django.test import TestCase from rest_framework import ( HTTP_HEADER_ENCODING, authentication, generics, permissions, serializers, - status + status, views ) from rest_framework.compat import ResolverMatch, guardian, set_many from rest_framework.filters import DjangoObjectPermissionsFilter @@ -219,6 +219,27 @@ class ModelPermissionsIntegrationTests(TestCase): response = view(request) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + def test_queryset_assertions(self): + class View(views.APIView): + authentication_classes = [authentication.BasicAuthentication] + permission_classes = [permissions.DjangoModelPermissions] + view = View.as_view() + + request = factory.get('/', HTTP_AUTHORIZATION=self.permitted_credentials) + msg = 'Cannot apply DjangoModelPermissions on a view that does not set `.queryset` or have a `.get_queryset()` method.' + with self.assertRaisesMessage(AssertionError, msg): + view(request) + + # Faulty `get_queryset()` methods should trigger the above "view does not have a queryset" assertion. + class View(RootView): + def get_queryset(self): + return None + view = View.as_view() + + request = factory.get('/', HTTP_AUTHORIZATION=self.permitted_credentials) + with self.assertRaisesMessage(AssertionError, 'View.get_queryset() returned None'): + view(request) + class BasicPermModel(models.Model): text = models.CharField(max_length=100)