2025, Nov 09 23:00

Django ModelForm custom validators not running? Declare file fields on the form, not in Meta

Why Django ModelForm validators don’t run when fields sit in Meta. Declare file fields correctly so validation runs and ValidationError appears in templates.

When a custom validator in a Django ModelForm doesn’t fire and the template shows no error, the root cause is often not in the validator itself but in how the form fields are declared. A frequent pitfall is placing form fields inside the Meta class, which silently disables validation and field rendering.

Problem example

The validator checks the uploaded file’s extension and raises ValidationError for unsupported types.

from django.core.exceptions import ValidationError
import os

def restrict_to_images(file_obj):
    suffix = os.path.splitext(file_obj.name)[1]
    print(suffix)
    permitted_exts = ['.png', '.jpg', '.jpeg']
    if suffix.lower() not in permitted_exts:
        raise ValidationError("Unsupported file extension. Allowed extensions: " + str(permitted_exts))

The issue emerges when form fields are defined inside Meta. In that case, the fields aren’t actually created on the form, and the validator never runs.

class AccountProfileForm(forms.ModelForm):
    class Meta:
        avatar = forms.FileField(widget=forms.FileInput(attrs={'class': 'btn btn-info'}), validators=[restrict_to_images])
        banner = forms.FileField(widget=forms.FileInput(attrs={'class': 'btn btn-info'}), validators=[restrict_to_images])

What’s actually wrong and why

In Django, the Meta class of a ModelForm is reserved for metadata: which model to bind and which fields to include. It is not a container for field definitions. Fields must be declared directly on the form class. If they sit inside Meta, Django ignores them as real fields, which means no widget, no validation, and no error messages in the template.

Fix and working version

Move the field declarations out of Meta and keep only model binding and the list of fields inside it. The validator will then run as expected and any ValidationError will appear in form.errors and in the template when rendered properly.

class AccountProfileForm(forms.ModelForm):
    avatar = forms.FileField(
        widget=forms.FileInput(attrs={'class': 'btn btn-info'}),
        validators=[restrict_to_images]
    )
    banner = forms.FileField(
        widget=forms.FileInput(attrs={'class': 'btn btn-info'}),
        validators=[restrict_to_images]
    )

    class Meta:
        model = MemberProfile  # your model
        fields = ['avatar', 'banner']

Why this matters

File validation is a critical layer of defense and user experience. If fields are misplaced in Meta, validation code never executes, users receive no feedback, and the form behaves unpredictably. Correct placement ensures that Django builds the form, attaches widgets, runs validators, and exposes errors back to the template.

Conclusion and practical advice

Always declare form fields at the class level of a ModelForm and reserve Meta for model binding and field lists. Keep your validators attached directly to the fields, and verify errors in form.errors when debugging. This straightforward separation ensures your custom validators trigger reliably and users see accurate, timely error messages.

The article is based on a question from StackOverflow by ADAN SHAHID and an answer by Uchenna Adubasim.