Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions rest_framework/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,17 @@ def get_rendered_html_form(self, data, view, method, request):
existing_serializer = None

with override_method(view, request, method) as request:
if method == 'OPTIONS':
# The browsable API only needs a placeholder for OPTIONS, so
# avoid object-level permission checks against serializer.instance.
if method not in view.allowed_methods:
return
try:
view.check_permissions(request)
except exceptions.APIException:
return
return True

if not self.show_form_for_method(view, method, request, instance):
return

Expand Down
53 changes: 53 additions & 0 deletions tests/test_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,9 +731,34 @@ class AuthExampleViewSet(ExampleViewSet):
class SimpleSerializer(serializers.Serializer):
name = serializers.CharField()

class CrashOnObjectPermission(permissions.BasePermission):
def has_permission(self, request, view):
return True

def has_object_permission(self, request, view, obj):
return obj.user.is_staff

class Issue6855Serializer(serializers.Serializer):
name = serializers.CharField()

class Issue6855Object:
def __init__(self, name):
self.name = name

class Issue6855ViewSet(ViewSet):
@action(detail=True)
def extra_action(self, request, pk=None):
serializer = BrowsableAPIRendererTests.Issue6855Serializer(
BrowsableAPIRendererTests.Issue6855Object(name='demo')
)
return Response(serializer.data)

Issue6855ViewSet.permission_classes = [CrashOnObjectPermission]

router = SimpleRouter()
router.register('examples', ExampleViewSet, basename='example')
router.register('auth-examples', AuthExampleViewSet, basename='auth-example')
router.register('issue-6855', Issue6855ViewSet, basename='issue-6855')
urlpatterns = [path('api/', include(router.urls))]

def setUp(self):
Expand Down Expand Up @@ -820,6 +845,34 @@ def test_extra_actions_dropdown_not_authed(self):
assert '/api/examples/list_action/' not in resp.content.decode()
assert '>Extra list action<' not in resp.content.decode()

def test_options_form_does_not_check_object_permissions_for_extra_action(self):
resp = self.client.get('/api/issue-6855/1/extra_action/', HTTP_ACCEPT='text/html')
assert resp.status_code == status.HTTP_200_OK

def test_delete_form_still_checks_object_permissions(self):
class ObjectPermissionDenied(permissions.BasePermission):
def has_permission(self, request, view):
return True

def has_object_permission(self, request, view, obj):
return False

class DummyObject:
name = 'Name'

class DummyDeleteView(APIView):
permission_classes = [ObjectPermissionDenied]

def delete(self, request):
return Response()

request = Request(APIRequestFactory().get('/'))
serializer = BrowsableAPIRendererTests.SimpleSerializer(instance=DummyObject())
delete_form = self.renderer.get_rendered_html_form(
serializer.data, DummyDeleteView(), 'DELETE', request
)
assert delete_form is None


class AdminRendererTests(TestCase):

Expand Down
Loading