Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django ChoiceField, ModelChoiceField validation

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?

like image 397
sergiuz Avatar asked Apr 29 '17 13:04

sergiuz


2 Answers

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
like image 199
Todor Avatar answered Oct 26 '22 17:10

Todor


one thing i can say is for forms.ChoiceField the input are coming from the user perspective means a user can use inspect element and enter a choice which doesnt appear from the backend .

but for models one the choices are directly coming from the backend or the database

like image 39
Exprator Avatar answered Oct 26 '22 19:10

Exprator