#34156 closed Bug (invalid)

TypedChoiceField not compatible with IntegerChoices

Reported by: Yoshio Hasegawa Owned by: nobody
Component: Forms Version: 4.1
Severity: Normal Keywords: Form, TypedChoiceField, IntegerChoices, Coercion
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Coercion always fails when using IntegerChoices with a TypedChoiceField in a Django form.

When a value is cleaned in a TypedChoiceField, the inherited ChoiceField's to_python() method will return the value casted as a string. This means that when coercion with an IntegerChoices object is attempted, it will always fail as IntegerChoices will expect an int type when instantiated.

Example

class SomeIntegerChoice(models.IntegerChoices):
    VAL_2350 = (2350, "value 2350")
    VAL_4100 = (4100, "value 4100")
    VAL_8760 = (8760, "value 8760")
form.fields["Integer_choice"] = forms.TypedChoiceField(
    required=required,
    choices=SomeIntegerChoice.choices,
    coerce=SomeIntegerChoice,
)

This field will never pass validation since ChoiceField (inherited by TypedChoiceField) has a to_python() method that casts the provided value as a string:

# class ChoiceField:
# ...
def to_python(self, value):
    """Return a string."""
    if value in self.empty_values:
        return ""
    return str(value)

To explain further... ChoiceField.to_python() will be called when TypedChoiceField attempts to clean() a value. After the value is cleaned, it is coerced using the provided class definition via the coerce property.

Here is example code from Django to show how this happens:

# class TypedChoiceField:
# ...
def _coerce(self, value):
    """
    Validate that the value can be coerced to the right type (if not empty).
    """
    if value == self.empty_value or value in self.empty_values:
        return self.empty_value
    try:
        value = self.coerce(value)
    except (ValueError, TypeError, ValidationError):
        raise ValidationError(
            self.error_messages["invalid_choice"],
            code="invalid_choice",
            params={"value": value},
        )
    return value

def clean(self, value):
    value = super().clean(value)
    return self._coerce(value)

Change History (1)

comment:1 by Mariusz Felisiak, 18 months ago

Resolution: invalid
Status: newclosed

As far as I'm aware, it's an issue in your code. For IntegerChoices you should pass coerce=int and everything works fine.

Note: See TracTickets for help on using tickets.
Back to Top