The browsable API can crash while rendering an extra action page when the response data comes from a serializer whose instance is not a normal model object for the current view's permission class.
The failing path is in the browsable API renderer, not in view dispatch:
- The extra action returns
Response(serializer.data). serializer.datais aReturnDictorReturnListthat keeps a backlink to the serializer.BrowsableAPIRendererinspects that serializer during HTML rendering.- While building placeholder UI for other HTTP methods, it calls
view.check_object_permissions(request, instance). instancemay be a non-model object from the serializer, which can be incompatible with the permission class and crash withAttributeError.
This is the main issue location.
-
BrowsableAPIRenderer.get_context()- Always asks for
put_form,post_form,delete_form, andoptions_form. - This means a normal
GETrender also evaluates synthetic UI for other methods.
- Always asks for
-
BrowsableAPIRenderer.get_rendered_html_form()- Pulls
serializer = getattr(data, 'serializer', None). - Extracts
instance = getattr(serializer, 'instance', None)whenmany=False. - Calls
show_form_for_method()before the currentDELETE/OPTIONSbailout. - This ordering is what exposes the bug.
- Pulls
-
BrowsableAPIRenderer.show_form_for_method()- Calls
view.check_permissions(request). - If
obj is not None, also callsview.check_object_permissions(request, obj). - Only catches
APIException, so an unexpectedAttributeErrorfrom a permission class bubbles up and crashes rendering.
- Calls
ReturnDictReturnList
These wrappers preserve data.serializer, which is why renderers can see the original serializer and its .instance.
APIView.check_object_permissions()
This loops through permission classes and directly passes the provided object to has_object_permission(). It assumes the caller supplied the correct domain object.
override_method
get_rendered_html_form() uses this context manager while probing alternate HTTP methods such as OPTIONS and DELETE.
options_formdelete_form
These booleans control whether the browsable API shows the OPTIONS button and DELETE button/modal.
delete_form
The admin renderer also consumes the truthiness of delete_form.
Contains BrowsableAPIRendererTests, which is the closest existing unit/integration coverage for renderer behavior and extra actions.
Already has regression coverage around browsable API form generation and serializer shapes, including many=True.
Contains an example object-permission class that accesses nested attributes on the object and is useful as a pattern for reproducing this class of failure.
The renderer is mixing two different concepts:
- the object that was serialized for display
- the object that should be used for permission checks when deciding whether to show action UI
For issue #6855, the immediate crash is triggered by the synthetic OPTIONS UI path, where no object-level edit form is actually rendered, so reusing serializer.instance is not appropriate.