mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-02 20:54:42 +03:00
Merge pull request #2905 from ticosax/django-object-perm-get_queryset
Allow DjangoObjectPermissions to use views that define get_queryset
This commit is contained in:
commit
ea1145c5aa
|
@ -150,7 +150,7 @@ Similar to `DjangoModelPermissions`, but also allows unauthenticated users to ha
|
||||||
|
|
||||||
This permission class ties into Django's standard [object permissions framework][objectpermissions] that allows per-object permissions on models. In order to use this permission class, you'll also need to add a permission backend that supports object-level permissions, such as [django-guardian][guardian].
|
This permission class ties into Django's standard [object permissions framework][objectpermissions] that allows per-object permissions on models. In order to use this permission class, you'll also need to add a permission backend that supports object-level permissions, such as [django-guardian][guardian].
|
||||||
|
|
||||||
As with `DjangoModelPermissions`, this permission must only be applied to views that have a `.queryset` property. Authorization will only be granted if the user *is authenticated* and has the *relevant per-object permissions* and *relevant model permissions* assigned.
|
As with `DjangoModelPermissions`, this permission must only be applied to views that have a `.queryset` property or `.get_queryset()` method. Authorization will only be granted if the user *is authenticated* and has the *relevant per-object permissions* and *relevant model permissions* assigned.
|
||||||
|
|
||||||
* `POST` requests require the user to have the `add` permission on the model instance.
|
* `POST` requests require the user to have the `add` permission on the model instance.
|
||||||
* `PUT` and `PATCH` requests require the user to have the `change` permission on the model instance.
|
* `PUT` and `PATCH` requests require the user to have the `change` permission on the model instance.
|
||||||
|
|
|
@ -107,23 +107,20 @@ class DjangoModelPermissions(BasePermission):
|
||||||
return [perm % kwargs for perm in self.perms_map[method]]
|
return [perm % kwargs for perm in self.perms_map[method]]
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
|
# Workaround to ensure DjangoModelPermissions are not applied
|
||||||
|
# to the root view when using DefaultRouter.
|
||||||
|
if getattr(view, '_ignore_model_permissions', False):
|
||||||
|
return True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
queryset = view.get_queryset()
|
queryset = view.get_queryset()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
queryset = getattr(view, 'queryset', None)
|
queryset = getattr(view, 'queryset', None)
|
||||||
except AssertionError:
|
|
||||||
# view.get_queryset() didn't find .queryset
|
|
||||||
queryset = None
|
|
||||||
|
|
||||||
# Workaround to ensure DjangoModelPermissions are not applied
|
|
||||||
# to the root view when using DefaultRouter.
|
|
||||||
if queryset is None and getattr(view, '_ignore_model_permissions', False):
|
|
||||||
return True
|
|
||||||
|
|
||||||
assert queryset is not None, (
|
assert queryset is not None, (
|
||||||
'Cannot apply DjangoModelPermissions on a view that '
|
'Cannot apply DjangoModelPermissions on a view that '
|
||||||
'does not have `.queryset` property nor redefines `.get_queryset()`.'
|
'does not have `.queryset` property or overrides the '
|
||||||
)
|
'`.get_queryset()` method.')
|
||||||
|
|
||||||
perms = self.get_required_permissions(request.method, queryset.model)
|
perms = self.get_required_permissions(request.method, queryset.model)
|
||||||
|
|
||||||
|
@ -172,7 +169,17 @@ class DjangoObjectPermissions(DjangoModelPermissions):
|
||||||
return [perm % kwargs for perm in self.perms_map[method]]
|
return [perm % kwargs for perm in self.perms_map[method]]
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
model_cls = view.queryset.model
|
try:
|
||||||
|
queryset = view.get_queryset()
|
||||||
|
except AttributeError:
|
||||||
|
queryset = getattr(view, 'queryset', None)
|
||||||
|
|
||||||
|
assert queryset is not None, (
|
||||||
|
'Cannot apply DjangoObjectPermissions on a view that '
|
||||||
|
'does not have `.queryset` property or overrides the '
|
||||||
|
'`.get_queryset()` method.')
|
||||||
|
|
||||||
|
model_cls = queryset.model
|
||||||
user = request.user
|
user = request.user
|
||||||
|
|
||||||
perms = self.get_required_object_permissions(request.method, model_cls)
|
perms = self.get_required_object_permissions(request.method, model_cls)
|
||||||
|
|
|
@ -5,7 +5,9 @@ from django.test import TestCase
|
||||||
from django.utils import unittest
|
from django.utils import unittest
|
||||||
from rest_framework import generics, serializers, status, permissions, authentication, HTTP_HEADER_ENCODING
|
from rest_framework import generics, serializers, status, permissions, authentication, HTTP_HEADER_ENCODING
|
||||||
from rest_framework.compat import guardian, get_model_name
|
from rest_framework.compat import guardian, get_model_name
|
||||||
|
from django.core.urlresolvers import ResolverMatch
|
||||||
from rest_framework.filters import DjangoObjectPermissionsFilter
|
from rest_framework.filters import DjangoObjectPermissionsFilter
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
from rest_framework.test import APIRequestFactory
|
from rest_framework.test import APIRequestFactory
|
||||||
from tests.models import BasicModel
|
from tests.models import BasicModel
|
||||||
import base64
|
import base64
|
||||||
|
@ -49,6 +51,7 @@ class EmptyListView(generics.ListCreateAPIView):
|
||||||
|
|
||||||
|
|
||||||
root_view = RootView.as_view()
|
root_view = RootView.as_view()
|
||||||
|
api_root_view = DefaultRouter().get_api_root_view()
|
||||||
instance_view = InstanceView.as_view()
|
instance_view = InstanceView.as_view()
|
||||||
get_queryset_list_view = GetQuerySetListView.as_view()
|
get_queryset_list_view = GetQuerySetListView.as_view()
|
||||||
empty_list_view = EmptyListView.as_view()
|
empty_list_view = EmptyListView.as_view()
|
||||||
|
@ -86,6 +89,18 @@ class ModelPermissionsIntegrationTests(TestCase):
|
||||||
response = root_view(request, pk=1)
|
response = root_view(request, pk=1)
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
def test_api_root_view_discard_default_django_model_permission(self):
|
||||||
|
"""
|
||||||
|
We check that DEFAULT_PERMISSION_CLASSES can
|
||||||
|
apply to APIRoot view. More specifically we check expected behavior of
|
||||||
|
``_ignore_model_permissions`` attribute support.
|
||||||
|
"""
|
||||||
|
request = factory.get('/', format='json',
|
||||||
|
HTTP_AUTHORIZATION=self.permitted_credentials)
|
||||||
|
request.resolver_match = ResolverMatch('get', (), {})
|
||||||
|
response = api_root_view(request)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
def test_get_queryset_has_create_permissions(self):
|
def test_get_queryset_has_create_permissions(self):
|
||||||
request = factory.post('/', {'text': 'foobar'}, format='json',
|
request = factory.post('/', {'text': 'foobar'}, format='json',
|
||||||
HTTP_AUTHORIZATION=self.permitted_credentials)
|
HTTP_AUTHORIZATION=self.permitted_credentials)
|
||||||
|
@ -227,6 +242,18 @@ class ObjectPermissionListView(generics.ListAPIView):
|
||||||
object_permissions_list_view = ObjectPermissionListView.as_view()
|
object_permissions_list_view = ObjectPermissionListView.as_view()
|
||||||
|
|
||||||
|
|
||||||
|
class GetQuerysetObjectPermissionInstanceView(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
serializer_class = BasicPermSerializer
|
||||||
|
authentication_classes = [authentication.BasicAuthentication]
|
||||||
|
permission_classes = [ViewObjectPermissions]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return BasicPermModel.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
get_queryset_object_permissions_view = GetQuerysetObjectPermissionInstanceView.as_view()
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(guardian, 'django-guardian not installed')
|
@unittest.skipUnless(guardian, 'django-guardian not installed')
|
||||||
class ObjectPermissionsIntegrationTests(TestCase):
|
class ObjectPermissionsIntegrationTests(TestCase):
|
||||||
"""
|
"""
|
||||||
|
@ -326,6 +353,15 @@ class ObjectPermissionsIntegrationTests(TestCase):
|
||||||
response = object_permissions_view(request, pk='1')
|
response = object_permissions_view(request, pk='1')
|
||||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
def test_can_read_get_queryset_permissions(self):
|
||||||
|
"""
|
||||||
|
same as ``test_can_read_permissions`` but with a view
|
||||||
|
that rely on ``.get_queryset()`` instead of ``.queryset``.
|
||||||
|
"""
|
||||||
|
request = factory.get('/1', HTTP_AUTHORIZATION=self.credentials['readonly'])
|
||||||
|
response = get_queryset_object_permissions_view(request, pk='1')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
# Read list
|
# Read list
|
||||||
def test_can_read_list_permissions(self):
|
def test_can_read_list_permissions(self):
|
||||||
request = factory.get('/', HTTP_AUTHORIZATION=self.credentials['readonly'])
|
request = factory.get('/', HTTP_AUTHORIZATION=self.credentials['readonly'])
|
||||||
|
|
Loading…
Reference in New Issue
Block a user