Describe the bug
Any OPTIONS request to an endpoint served by BaseModelViewSet returns
500 Internal Server Error with TypeError: 'NoneType' object is not callable.
Root cause: SerializerFactory.get_serializer() in backend/core/serializers.py
maps a serializer only for a fixed set of actions and returns None for
anything else:
def get_serializer(self, base_name: str, action: str):
if action in ["list", "retrieve"]:
serializer_name = f"{base_name}ReadSerializer"
elif action in ["create", "update", "partial_update", "destroy"]:
serializer_name = f"{base_name}WriteSerializer"
else:
return None
return self._get_serializer_class(serializer_name)
When DRF handles OPTIONS, SimpleMetadata.determine_actions() calls
view.get_serializer() while view.action == "metadata". That action is not in
either branch, so get_serializer_class() returns None and DRF then calls
None(...).
Because the affected code is in the shared base viewset, every resource is
affected, not one in particular.
To Reproduce
- Authenticate against the API.
- Send
OPTIONS to any collection or detail endpoint, e.g.
OPTIONS /api/folders/, OPTIONS /api/perimeters/, OPTIONS /api/assets/.
- The response is
500. GET on the very same URL returns 200.
Backend traceback (abridged):
rest_framework/metadata.py determine_actions()
locals: actions={}, method='PUT', self=<SimpleMetadata>, view=<FolderViewSet>
rest_framework/generics.py:114 in get_serializer
return serializer_class(*args, **kwargs)
locals: serializer_class = None, context={'action': 'metadata'}
TypeError: 'NoneType' object is not callable
Expected behavior
OPTIONS should return 200 with the resource metadata, as DRF does by
default. At minimum it should not raise: an unmapped action should fall back to
a sensible serializer rather than None.
Actual impact on the UI
This is not only an API-level annoyance. In our deployment the web UI could not
delete a domain: clicking the delete (trash) control produced no request at
all — verified in the backend logs, where no DELETE was ever received. The
deletion succeeded immediately when issued directly against the API
(DELETE /api/folders/<id>/ → 204), which shows the write path itself is
healthy and only the metadata step is broken.
Environment
- CISO Assistant Community Edition v3.19.2 (backend and frontend images)
- PostgreSQL 16, Docker Compose deployment, self-hosted
- Browser: Firefox (also reproducible headlessly, straight against the API)
- Also present in
main at the time of writing: the get_serializer body
quoted above is unchanged, so upgrading to v3.21.x does not fix it.
Additional context
We could not find an existing issue mentioning SerializerFactory,
get_serializer_class or a 500 on the metadata endpoint.
A possible fix, keeping the current design: return the read serializer (or a
plain ModelSerializer on the viewset's model) for unmapped actions, instead of
None. Alternatively, override metadata_class so determine_actions() does
not need a serializer for actions the factory does not know about.
Describe the bug
Any
OPTIONSrequest to an endpoint served byBaseModelViewSetreturns500 Internal Server Error with
TypeError: 'NoneType' object is not callable.Root cause:
SerializerFactory.get_serializer()inbackend/core/serializers.pymaps a serializer only for a fixed set of actions and returns
Noneforanything else:
When DRF handles
OPTIONS,SimpleMetadata.determine_actions()callsview.get_serializer()whileview.action == "metadata". That action is not ineither branch, so
get_serializer_class()returnsNoneand DRF then callsNone(...).Because the affected code is in the shared base viewset, every resource is
affected, not one in particular.
To Reproduce
OPTIONSto any collection or detail endpoint, e.g.OPTIONS /api/folders/,OPTIONS /api/perimeters/,OPTIONS /api/assets/.500.GETon the very same URL returns200.Backend traceback (abridged):
Expected behavior
OPTIONSshould return200with the resource metadata, as DRF does bydefault. At minimum it should not raise: an unmapped action should fall back to
a sensible serializer rather than
None.Actual impact on the UI
This is not only an API-level annoyance. In our deployment the web UI could not
delete a domain: clicking the delete (trash) control produced no request at
all — verified in the backend logs, where no
DELETEwas ever received. Thedeletion succeeded immediately when issued directly against the API
(
DELETE /api/folders/<id>/→204), which shows the write path itself ishealthy and only the metadata step is broken.
Environment
mainat the time of writing: theget_serializerbodyquoted above is unchanged, so upgrading to v3.21.x does not fix it.
Additional context
We could not find an existing issue mentioning
SerializerFactory,get_serializer_classor a 500 on the metadata endpoint.A possible fix, keeping the current design: return the read serializer (or a
plain
ModelSerializeron the viewset's model) for unmapped actions, instead ofNone. Alternatively, overridemetadata_classsodetermine_actions()doesnot need a serializer for actions the factory does not know about.