wtforms Form class subclassing and field ordering

sector119 picture sector119 · May 1, 2011 · Viewed 8.2k times · Source

I have a UserForm class:

class UserForm(Form):
    first_name = TextField(u'First name', [validators.Required()])
    last_name = TextField(u'Last name', [validators.Required()])
    middle_name = TextField(u'Middle name', [validators.Required()])
    username = TextField(u'Username', [validators.Required()])
    password = TextField(u'Password', [validators.Required()], widget=PasswordInput())
    email = TextField(u'Email', [validators.Optional(), validators.Email()])

and want to make the password field Optional in UpdateUserForm:

class UpdateUserForm(UserForm):
    password = TextField(u'Password', [validators.Optional()], widget=PasswordInput())

But the password field is placed after the email field, not before.

How do I preserve field order when subclassing?

Additionally, when I try to change the password field validators it doesn't work - password still Required :/ Why?

class UpdateUserForm(UserForm):
    def __init__(self, **kwargs):
        self.password.validators = [validators.Optional()]
        super(UpdateUserForm, self).__init__(**kwargs)

or

class UpdateUserForm(UserForm):
    def __init__(self, **kwargs):
        self.password = TextField(u'Password', [validators.Optional()], widget=PasswordInput())
        super(UpdateUserForm, self).__init__(**kwargs)

Some thoughts...

class UpdateUserForm(UserForm):
    def __init__(self, formdata=None, obj=None, prefix='', **kwargs):
        self._unbound_fields[4][1] = TextField(u'Password', [validators.Optional()], widget=PasswordInput())
        UserForm.__init__(self, formdata=None, obj=None, prefix='', **kwargs)

Finally, what I need:

class UpdateUserForm(UserForm):
    def __init__(self, formdata=None, obj=None, prefix='', **kwargs):
        UserForm.__init__(self, formdata, obj, prefix, **kwargs)
        self['password'].validators = [validators.Optional()]
        self['password'].flags.required = False

Answer

rhyek picture rhyek · Aug 27, 2013

In regards to your first question about reording the fields when iterating over the form object, this is what I did:

class BaseForm(Form):
    def __iter__(self):
        field_order = getattr(self, 'field_order', None)
        if field_order:
            temp_fields = []
            for name in field_order:
                if name == '*':
                    temp_fields.extend([f for f in self._unbound_fields if f[0] not in field_order])
                else:
                    temp_fields.append([f for f in self._unbound_fields if f[0] == name][0])
            self._unbound_fields = temp_fields
        return super(BaseForm, self).__iter__()

class BaseUserForm(BaseForm):
    password = PasswordField('Password', [Required()])
    full_name = TextField('Full name', [Required()])

class NewUserForm(BaseUserForm):
    username = Textfield('Username', [Required()])
    field_order = ('username', '*')

That way, when you render NewUserForm (perhaps from a template which iterates over the form rendering field by field), you'll see username, password, full_name. Normally you'd see username last.