Skip to content

OPTIONS on any BaseModelViewSet endpoint returns 500 (SerializerFactory returns None for the metadata` action) #4653

Description

@marinosedda

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

  1. Authenticate against the API.
  2. Send OPTIONS to any collection or detail endpoint, e.g.
    OPTIONS /api/folders/, OPTIONS /api/perimeters/, OPTIONS /api/assets/.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions