Skip to content

RepresenterError when providing choices with instances derived of a base type #257

@lerela

Description

@lerela

Describe the bug
I have a model with a field that has a choices parameter. This parameter is not a standard list of tuples but a django-extended-choices Choices class:

from django.db import models
from extended_choices import Choices

choices = Choices(
    ("C1", 0, "Choice 1"),
    ("C2", 1, "Choice 2")
)

class TestModel(models.Model):
    test_field = models.IntegerField(
        choices = choices, default = choices.C1
    )

drf-spectacular is unable to serialize the schema of this model because the choices are of type extended_choices.helpers.IntChoiceAttribute and not raw integers.

Schema generation fails as follow:

➜ ./manage.py spectacular --file schema.yml
Traceback (most recent call last):
  File "./manage.py", line 22, in <module>
    main()
  File "./manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "test_spectacular/venv/lib/python3.8/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "test_spectacular/venv/lib/python3.8/site-packages/django/core/management/__init__.py", line 395, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "test_spectacular/venv/lib/python3.8/site-packages/django/core/management/base.py", line 330, in run_from_argv
    self.execute(*args, **cmd_options)
  File "test_spectacular/venv/lib/python3.8/site-packages/django/core/management/base.py", line 371, in execute
    output = self.handle(*args, **options)
  File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/management/commands/spectacular.py", line 60, in handle
    output = renderer.render(schema, renderer_context={})
  File "test_spectacular/venv/lib/python3.8/site-packages/drf_spectacular/renderers.py", line 26, in render
    return yaml.dump(
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/__init__.py", line 290, in dump
    return dump_all([data], stream, Dumper=Dumper, **kwds)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/__init__.py", line 278, in dump_all
    dumper.represent(data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 27, in represent
    node = self.represent_data(data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 48, in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 207, in represent_dict
    return self.represent_mapping('tag:yaml.org,2002:map', data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 118, in represent_mapping
    node_value = self.represent_data(item_value)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 48, in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 207, in represent_dict
    return self.represent_mapping('tag:yaml.org,2002:map', data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 118, in represent_mapping
    node_value = self.represent_data(item_value)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 48, in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 207, in represent_dict
    return self.represent_mapping('tag:yaml.org,2002:map', data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 118, in represent_mapping
    node_value = self.represent_data(item_value)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 48, in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 207, in represent_dict
    return self.represent_mapping('tag:yaml.org,2002:map', data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 118, in represent_mapping
    node_value = self.represent_data(item_value)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 48, in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 199, in represent_list
    return self.represent_sequence('tag:yaml.org,2002:seq', data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 92, in represent_sequence
    node_item = self.represent_data(item)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 58, in represent_data
    node = self.yaml_representers[None](self, data)
  File "test_spectacular/venv/lib/python3.8/site-packages/yaml/representer.py", line 231, in represent_undefined
    raise RepresenterError("cannot represent an object", data)
yaml.representer.RepresenterError: ('cannot represent an object', 0)

This is the value of data_types in last frame: (<class 'extended_choices.helpers.IntChoiceAttribute'>, <class 'extended_choices.helpers.ChoiceAttributeMixin'>, <class 'int'>, <class 'object'>)

As this is not a custom field, I cannot use OpenApiSerializerFieldExtension.

I can solve that by overriding the serializer field and providing a standard default by casting to int, but it's a little annoying to do that for each ChoiceField.
Django/DRF are able to work that out (that it's in fact an int, above code works out of the box in the admin and in serializers) so maybe drf-spectacular could as well?

To Reproduce
In addition to above snippet, you can use this view:

from .models import TestModel
from rest_framework import viewsets
from rest_framework import serializers

class TestSerializer(serializers.ModelSerializer):
    class Meta:
        model = TestModel
        fields = ['id', 'test_field']

class TestViewSet(viewsets.ModelViewSet):
    queryset = TestModel.objects.all()
    serializer_class = TestSerializer

Expected behavior
I want the extended choices object to be properly serialized as an Enum.

Metadata

Metadata

Assignees

No one assigned

    Labels

    fix confirmation pendingissue has been fixed and confirmation from issue reporter is pending

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions