Type validation on not required field

In Django admin interface, when submitting a form which has an optional integer field named “ordering”, I want to show a validation error message when user enters an incorrect type (string). However when user enters a string into this field and submits the form, Django recognizes it as an incorrect type and changes the value to None and successfully validates the form. Therefore there is no way to find out if the field was left blank or if the user entered incorrect type. Even when I tried to get clean data from custom form (clean_<field_name>) it always returns None. I also tried to do custom validator but no success with that either.

Is there a way to show a validation error for an optional field? Or can I somehow get the value that was actually entered into the field?

Thank you for your help.

Here is the model:

class Condition(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)
    code = models.CharField()
    common_name_en = models.CharField()
    ordering = models.IntegerField(blank=True, null=True)
    common_name_cs = models.CharField()
    eval_time_frame = models.ForeignKey('ConditionEvalTime', models.DO_NOTHING, db_column='eval_time_frame')
    unit = models.CharField(blank=True, null=True)
    metric_extreme_low = models.FloatField(blank=True, null=True)
    metric_extreme_high = models.FloatField(blank=True, null=True)
    metric_scale = models.FloatField(blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'planner"."condition'

    def __str__(self) -> str:
        return self.common_name_en

Here is my admin file:

class ConditionForm(forms.ModelForm):
    class Meta:
        model = Condition
        exclude = []


@admin.register(Condition)
class ConditionAdmin(admin.ModelAdmin):
    def get_related_models(self, obj):
        """
        Return all models that have 'one-to-many' relation to Condition model
        """
        related_models = []
        related_fields = obj._meta.get_fields()

        for field in related_fields:
            if field.is_relation and field.one_to_many:
                related_model = field.related_model
                related_models.append(related_model._meta.object_name.lower())

        return related_models

    def has_delete_permission(self, request, obj=None):
        """
        Overwrite delete permission if object has relations
        """
        if obj:
            for related_model in self.get_related_models(obj):
                if getattr(obj, f"{related_model}_set").exists():
                    messages.warning(request, f"Can't delete: {obj.common_name_en} - {obj.id}")
                    return False
        return super().has_delete_permission(request, obj)

    form = ConditionForm
    fields = [field.name for field in Condition._meta.fields]
    readonly_fields = ["id"]

This field is the one I can see as IntegerField and it has blank=True, null=True attributes which will save your data without giving you validation and sets None if given different type to this field. You can remove blank=True and then try to create new instance.

If I remove blank=True the field becomes required and that is not what I want.

Okay, in that case see Validators | Django documentation | Django this from official docs.

As I stated in the initial issue overview, I tried to do custom validation but with no success. The function which validates the input doesn’t run for some reason.

Here is my validator:

def validate_integer(value):
    print("Inside validator ", value)
    if not isinstance(value, int):
        print("Inside validator condition ", value)
        raise ValidationError("This field must be an integer.")

And the model again:

class Condition(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)
    code = models.CharField()
    common_name_en = models.CharField()
    ordering = models.IntegerField(blank=True, null=True, validators=[validate_integer])
    common_name_cs = models.CharField()
    eval_time_frame = models.ForeignKey('ConditionEvalTime', models.DO_NOTHING, db_column='eval_time_frame')
    unit = models.CharField(blank=True, null=True)
    metric_extreme_low = models.FloatField(blank=True, null=True)
    metric_extreme_high = models.FloatField(blank=True, null=True)
    metric_scale = models.FloatField(blank=True, null=True)

    class Meta:
        managed = False
        db_table = 'planner"."condition'

    def __str__(self) -> str:
        return self.common_name_en

One thing I don’t understand is that, if you are using Django’s admin panel than the IntegerField i.e ordering will not accept any characters expect number as it’s input type will be number by default in admin panel. If you are not using Django’s admin panel than you can give your validation within form or view.

I am using the admin panel and it does accept string characters though:

There might be some other issue as I’ve seen in my current projects I’ve these two fields

    float = models.FloatField()
    int = models.IntegerField()

and in admin I’m not able to enter any character in both fields except number values.
Is ordering field always a IntegerField or you have changed it to IntegerField, if so re-run the makemigrations and migrate commands

I have rerun migrations couple times already, no change. :confused:

This fields also accepting any characters or just number.

Those fields also accept any character, just as the integer field and validation doesn’t work for those fields either.

There might be some other issue, can you share your working environment like OS, Browser, etc

MacOS Ventura 13.5.2 , currently in Firefox 117.0.1, Django 4.2.3 with PostgreSQL on the backend

After trying it in Firefox it seems it is indeed accepting any characters so I’ve searched and found this 1466793 - Firefox allows you to write letters inside input type="number" and css - Input type="number" with pattern="[0-9]*" allows letters in firefox - Stack Overflow.
Try to use it in another browser, as stated in stackoverflow it still will validate the input upon submitting your form.

Also if this fields did not have blank=True, null=True you would have got the error This field is required as submitting the form will send the null values to the respective fields.

It does indeed not let me input string chars in Chrome. Still don’t get why the validation doesn’t work though.

Because None value is sent to field and blank=True, null=True is set to field so it will not go to validator i.e validate_integer. Try to give number and you will see your print statement will be executed print("Inside validator", value).

Yes, the print does work when I enter a number. So I guess there is no way to show a validation error for a not required field then, right?

Well there might be, if someone knows it better but on the other side why do you really want to build that much logic to Django admin site as official docs itself states
The admin’s recommended use is limited to an organization’s internal management tool. It’s not intended for building your entire front end around. The Django admin site | Django documentation | Django.

I know but it’s a requirement of my work ticket, that’s why I’m trying to figure it out. Thanks for your help though, appreciete it.