forms & forms_meta¶
Two static-ish checks on every first-party Form / ModelForm — no HTTP,
no .save(), fast.
forms¶
Walks every app's modules, collects every forms.BaseForm subclass, and
instantiates each one twice:
FormCls()— the blank GET path a Django view uses to render the form.FormCls(data={})— the empty-POST path.
Any exception becomes an ERROR finding at forms.blank_init /
forms.empty_data.
Forms that legitimately require kwargs¶
Django ships SetPasswordForm(user, …), AuthenticationForm(request, …),
and many codebases layer their own. A TypeError matching
missing N required … arguments is downgraded to INFO with rule
forms.blank_init.needs_args — don't fail CI on them.
Real bug this caught on altiusone¶
class ContactPrincipalForm(forms.ModelForm):
class Meta:
model = Contact
fields = ["fonction", ...]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["fonction"].choices = [...] # AttributeError
Contact.fonction is a bare CharField, no choices=. The form raised
AttributeError on every GET. ./manage.py check passed. The view test
passed because it prepared the form with valid data. Doctor caught it in
half a second.
forms_meta¶
Static AST analysis, no instantiation. Finds the anti-pattern:
class OperationTVAForm(forms.ModelForm):
class Meta:
model = OperationTVA
fields = ["montant_ht", "taux_tva", ...] # no montant_ttc
def clean(self):
cleaned_data = super().clean()
cleaned_data["montant_ttc"] = ... # orphan!
return cleaned_data
The flow:
- AST-parse each
ModelForm's source. - Collect every
cleaned_data[<str>] = …insideclean()/clean_*(). - For each key:
- Skip if it's in
Meta.fieldsor a declared form field. - Skip if the form defines a
save()override (author handles it). - Skip if the model field is nullable or has a default.
- Otherwise → ERROR.
- Skip if it's in
Finding shape¶
[ERROR:forms.cleaned_data.orphan]
myapp.forms.OperationTVAForm:67
cleaned_data['montant_ttc'] is assigned in clean() but 'montant_ttc'
is not in Meta.fields; OperationTVA.montant_ttc is NOT NULL without
default. form.save() will silently drop the value and raise
IntegrityError.
Fixes¶
Two canonical fixes the check is happy with: