No need to build anything (at least on the server side) - WTForms already supports what you need - it calls them "field enclosures" , The behavior you are looking for is in wtforms.fields.FormField
and wtforms.fields.FieldList
class ContactForm(Form): name = TextField("Name", validators=[Required()]) contact_type = SelectField("Contact Type", validators=[Required()], choices=[ ("email", "Email"), ("phone", "Phone Number"), ("im", "Instant Message") ]) # `If` is a custom validator - see below email_address = TextField("Email", validators=[If("contact_type", "email", [Required(), Email()]) ]) phone_number = TextField("Phone #", validators=[If("contact_type", "phone", [Required()]) ]) im_handle = TextField("IM Handle", validators=[If("contact_type", "im", [Required()]) ]) class SignUpForm(Form): # Other fields go here contacts = FieldList(FormField(ContactForm))
You will also need a custom validator to check the correct field, given the user's choice:
# CAUTION: Untested code ahead class If(object): def __init__(self, parent, run_validation=None, extra_validators=None, msg=None): self.parent = parent self.msg = msg if msg is not None else u"Invalid" if callable(run_validation): self.run_validation = run_validation else: _run_validation = lambda self, parent, form: parent.data == run_validation self.run_validation = _run_validation self.extra_validators = extra_validators if extra_validators is not None \ else [] def __call__(self, field, form): parent = getattr(form, self.parent) if self.run_validation(parent, form): return field.validate(form, extra_validators=self.extra_validators)
When you call form.validate()
on the server side, the fields will be automatically checked for compliance and errors will be filled out accordingly so that you can return them on the client side.
Creating new fields on the client side is simple, and WTForms will pick them up on the back, while you name using the same naming convention that it uses - namely field.short_name + '-' + index
.