Django ChoiceField, ModelChoiceField validation

django, validation

Solution

The validation process starts from form.full_clean() where you have form._clean_fields() and form._clean_form executed in this order.

Now if you take a closer look at what `form._clean_fields()` do, you will probably notice that it only calls `field.clean(value, initial)` and collects the results into a `cleaned_data` dict. So the interesting part is at field.clean, lets see what happens there:

def clean(self, value):
    """
    Validate the given value and return its "cleaned" value as an
    appropriate Python object. Raise ValidationError for any errors.
    """
    value = self.to_python(value)
    self.validate(value)
    self.run_validators(value)
    return value

First, we have a `to_python` call, followed by `validate` and finishing with `run_validators`.

So in terms of `ModelChoiceField` when you reach the `.validate` method, your choice is already a Model instance, thats why, this kind of validation (from Q2) is happening inside the to_python method.

def to_python(self, value):
    if value in self.empty_values:
        return None
    try:
        key = self.to_field_name or 'pk'
        value = self.queryset.get(**{key: value})
    except (ValueError, TypeError, self.queryset.model.DoesNotExist):
        raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')
    return value

Problem

I see that `forms.ChoiceField` is using this code to validate the value: ``` def validate(self, value): """ Validates that the input is in self.choices. """ super(ChoiceField, self).validate(value) if value and not self.valid_value(value): raise ValidationError( self.error_messages['invalid_choice'], code='invalid_choice', params={'value': value}, ) def valid_value(self, value): "Check to see if the provided value is a valid choice" text_value = force_text(value) for k, v in self.choices: if isinstance(v, (list, tuple)): # This is an optgroup, so look inside the group for options for k2, v2 in v: if value == k2 or text_value == force_text(k2): return True else: if value == k or text_value == force_text(k): return True return False ``` and `forms.models.ModelChoiceField` this code: ``` def validate(self, value): return Field.validate(self, value) ``` Q1. Why Django uses validation to check if the selected value (from dropdown) is indeed in the choice list for `forms.ChoiceField`? Q2. When Django uses the validation from Q1, to check if the value is indeed in the choice list, why does not also check if the selected value is in the model records for `forms.models.ModelChoiceField`?

Original source