DjangoRestFramework ChoiceField fails float value validation

558 Views Asked by At

I have a question regarding validation logic of serializers.ChoiceField. As I've seen in the code, here is to_internal_value() of ChoiceField that is used to validate client inputs:

def to_internal_value(self, data):
    if data == '' and self.allow_blank:
        return ''

    try:
        return self.choice_strings_to_values[six.text_type(data)]
    except KeyError:
        self.fail('invalid_choice', input=data)

Here is my declaration of Django model:

class MyModel(BaseModel, ScopedModelMixin):
    my_field = models.FloatField(choices=MY_FLOAT_CHOICES, default=MY_FLOAT_CHOICES.default_value)

And here is my declaration of choices object from django-model-utils==2.6.1:

from model_utils import Choices

MY_FLOAT_CHOICES = Choices(
    (1.0, 'choice1', 'Choice1'),
    (1.5, 'default_value', 'Choice2'),
    (2.0, 'choice3', 'Choice3')
)

So, the generated field in a model serializer is a serializers.ChoiceField with choices provided at the previously declared model level.

When it comes to validation user input from the client, DRF fails to validate float values correctly. For example, when I send:

{
    "myField": 1
}

it throws "\"1\" is not a valid choice.". So when I come with debugger to the line

return self.choice_strings_to_values[six.text_type(data)] from previously shown to_internal() of ChoiceField, I see that it literally tries to find '1' in choices list. When I update my choices with:

MY_FLOAT_CHOICES = Choices(
    (1, 'choice1', 'Choice1'),
    (1.5, 'default_value', 'Choice2'),
    (2, 'choice3', 'Choice3')
)

it doesn't fail, but then I have the same problem with sending 1.0 instead from 1 and it goes round and round. Should I raise an issue on the DRF repository, or there is some better option?

1

There are 1 best solutions below

1
On

I guess you have to convert the int key to float so it will be able to find it in the choices tuple.

def to_internal_value(self, data):
    if data == '' and self.allow_blank:
        return ''

    try:
        key = six.text_type(float(data))
        return self.choice_strings_to_values[key]
    except (ValueError, KeyError) as e:
        self.fail('invalid_choice', input=data)