Fix mapping for choice values (#8968)

* fix mapping for choice values

* fix tests for ChoiceField IntegerChoices

* fix imports

* fix imports in tests

* Check for multiple choice enums

* fix formatting

* add tests for text choices class
This commit is contained in:
Saad Aleem 2023-05-03 12:08:07 +05:00 committed by GitHub
parent d14eb7555d
commit e08e606c82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 62 additions and 1 deletions

View File

@ -16,6 +16,7 @@ from django.core.validators import (
MinValueValidator, ProhibitNullCharactersValidator, RegexValidator, MinValueValidator, ProhibitNullCharactersValidator, RegexValidator,
URLValidator, ip_address_validators URLValidator, ip_address_validators
) )
from django.db.models import IntegerChoices, TextChoices
from django.forms import FilePathField as DjangoFilePathField from django.forms import FilePathField as DjangoFilePathField
from django.forms import ImageField as DjangoImageField from django.forms import ImageField as DjangoImageField
from django.utils import timezone from django.utils import timezone
@ -1397,6 +1398,10 @@ class ChoiceField(Field):
if data == '' and self.allow_blank: if data == '' and self.allow_blank:
return '' return ''
if isinstance(data, (IntegerChoices, TextChoices)) and str(data) != \
str(data.value):
data = data.value
try: try:
return self.choice_strings_to_values[str(data)] return self.choice_strings_to_values[str(data)]
except KeyError: except KeyError:
@ -1405,6 +1410,11 @@ class ChoiceField(Field):
def to_representation(self, value): def to_representation(self, value):
if value in ('', None): if value in ('', None):
return value return value
if isinstance(value, (IntegerChoices, TextChoices)) and str(value) != \
str(value.value):
value = value.value
return self.choice_strings_to_values.get(str(value), value) return self.choice_strings_to_values.get(str(value), value)
def iter_options(self): def iter_options(self):
@ -1428,7 +1438,8 @@ class ChoiceField(Field):
# Allows us to deal with eg. integer choices while supporting either # Allows us to deal with eg. integer choices while supporting either
# integer or string input, but still get the correct datatype out. # integer or string input, but still get the correct datatype out.
self.choice_strings_to_values = { self.choice_strings_to_values = {
str(key): key for key in self.choices str(key.value) if isinstance(key, (IntegerChoices, TextChoices))
and str(key) != str(key.value) else str(key): key for key in self.choices
} }
choices = property(_get_choices, _set_choices) choices = property(_get_choices, _set_choices)

View File

@ -5,11 +5,13 @@ import re
import sys import sys
import uuid import uuid
from decimal import ROUND_DOWN, ROUND_UP, Decimal from decimal import ROUND_DOWN, ROUND_UP, Decimal
from enum import auto
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest
import pytz import pytz
from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ValidationError as DjangoValidationError
from django.db.models import IntegerChoices, TextChoices
from django.http import QueryDict from django.http import QueryDict
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from django.utils.timezone import activate, deactivate, override from django.utils.timezone import activate, deactivate, override
@ -1824,6 +1826,54 @@ class TestChoiceField(FieldValues):
field.run_validation(2) field.run_validation(2)
assert exc_info.value.detail == ['"2" is not a valid choice.'] assert exc_info.value.detail == ['"2" is not a valid choice.']
def test_integer_choices(self):
class ChoiceCase(IntegerChoices):
first = auto()
second = auto()
# Enum validate
choices = [
(ChoiceCase.first, "1"),
(ChoiceCase.second, "2")
]
field = serializers.ChoiceField(choices=choices)
assert field.run_validation(1) == 1
assert field.run_validation(ChoiceCase.first) == 1
assert field.run_validation("1") == 1
choices = [
(ChoiceCase.first.value, "1"),
(ChoiceCase.second.value, "2")
]
field = serializers.ChoiceField(choices=choices)
assert field.run_validation(1) == 1
assert field.run_validation(ChoiceCase.first) == 1
assert field.run_validation("1") == 1
def test_text_choices(self):
class ChoiceCase(TextChoices):
first = auto()
second = auto()
# Enum validate
choices = [
(ChoiceCase.first, "first"),
(ChoiceCase.second, "second")
]
field = serializers.ChoiceField(choices=choices)
assert field.run_validation(ChoiceCase.first) == "first"
assert field.run_validation("first") == "first"
choices = [
(ChoiceCase.first.value, "first"),
(ChoiceCase.second.value, "second")
]
field = serializers.ChoiceField(choices=choices)
assert field.run_validation(ChoiceCase.first) == "first"
assert field.run_validation("first") == "first"
class TestChoiceFieldWithType(FieldValues): class TestChoiceFieldWithType(FieldValues):
""" """