Validation¶
rg.forms extends Django's form validation to respect reactive rules.
How validation works¶
ReactiveForm overrides _clean_fields() to add three behaviors:
- Hidden fields are skipped — if
visible_whenevaluates toFalse, the field is set toNoneincleaned_dataand no validation runs - Dynamic requirements are enforced — if
required_whenevaluates toTrueand the field is empty, aValidationErroris raised - Computed values are recalculated — the server ignores the submitted value and recomputes from the expression
Visibility skipping¶
class OrderForm(ReactiveForm):
order_type = ReactiveChoiceField(choices=[
('standard', 'Standard'),
('urgent', 'Urgent'),
])
priority = ReactiveChoiceField(
choices=[('high', 'High'), ('critical', 'Critical')],
visible_when="$order_type == 'urgent'",
required=True, # Required, but only when visible
)
If order_type is "standard":
priorityis not visiblecleaned_data['priority']isNone- No validation error, even though
required=True
Dynamic requirements¶
class ContactForm(ReactiveForm):
method = ReactiveChoiceField(choices=[('email', 'Email'), ('phone', 'Phone')])
email = ReactiveEmailField(
required=False,
required_when="$method == 'email'",
)
If method is "email" and email is empty:
required_whenevaluates toTrue- A
ValidationErrorwith code"required"is raised
Computed recalculation¶
class PriceForm(ReactiveForm):
quantity = ReactiveIntegerField(initial=1)
price = ReactiveDecimalField(initial="10.00")
total = ReactiveDecimalField(
computed="$quantity * $price",
required=False,
)
On submit, even if the client sends total=999999, the backend recalculates:
Custom validation¶
Standard Django clean_<fieldname>() and clean() methods work as expected:
class OrderForm(ReactiveForm):
# ... fields ...
def clean_quantity(self):
qty = self.cleaned_data.get('quantity')
if qty and qty > 10000:
raise ValidationError("Maximum 10,000 units per order.")
return qty
def clean(self):
cleaned_data = super().clean()
# Cross-field validation here
return cleaned_data
Note
clean_<fieldname>() is only called for visible fields. If a field is hidden by visible_when, its clean method is skipped.
Validation order¶
- For each field (in declaration order):
- Check
visible_when— skip if hidden - Run
field.clean()(Django's standard field validation) - If
computed, recalculate and override the value - Check
required_when— raise if required and empty - Call
clean_<fieldname>()if it exists
- Check
- Call
clean()for cross-field validation