wtforms表单类的子类化和字段排序

问题描述

| 我有一个UserForm类:
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()])
并希望在UpdateUserForm中将密码字段设为Optional:
class UpdateUserForm(UserForm):
    password = TextField(u\'Password\',[validators.Optional()],widget=Passwordinput())
但是密码字段放置在电子邮件字段之后,而不是之前。 子类化时如何保留字段顺序? 另外,当我尝试更改密码字段验证器时,它不起作用-仍然需要密码:/为什么?
class UpdateUserForm(UserForm):
    def __init__(self,**kwargs):
        self.password.validators = [validators.Optional()]
        super(UpdateUserForm,self).__init__(**kwargs)
要么
class UpdateUserForm(UserForm):
    def __init__(self,**kwargs):
        self.password = TextField(u\'Password\',widget=Passwordinput())
        super(UpdateUserForm,self).__init__(**kwargs)
一些想法...
class UpdateUserForm(UserForm):
    def __init__(self,formdata=None,obj=None,prefix=\'\',**kwargs):
        self._unbound_fields[4][1] = TextField(u\'Password\',widget=Passwordinput())
        UserForm.__init__(self,**kwargs)
最后,我需要什么:
class UpdateUserForm(UserForm):
    def __init__(self,**kwargs):
        UserForm.__init__(self,formdata,obj,prefix,**kwargs)
        self[\'password\'].validators = [validators.Optional()]
        self[\'password\'].flags.required = False
    

解决方法

关于您在遍历表单对象时如何协调字段的第一个问题,这是我所做的:
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\',\'*\')
这样,当您渲染NewUserForm时(也许是从一个模板逐字段遍历表单渲染的模板中),您将看到
username
password
full_name
。通常,您最后会看到
username
。     ,我通过在
Form
类上定义一个附加的
__order
属性并覆盖
__iter__
方法来解决此问题,以便根据定义首先对返回的迭代器的数据进行排序。它可能效率不高,但是表单上没有太多字段,可能会引起任何问题。它也适用于子类形式的字段。
class MyForm(Form):
    field3 = TextField()
    field1 = TextField()
    field2 = TextField()

    __order = (\'field1\',\'field2\',\'field3\')

    def __iter__(self):
        fields = list(super(MyForm,self).__iter__())
        get_field = lambda field_id: next((fld for fld in fields
                                           if fld.id == field_id))
        return (get_field(field_id) for field_id in self.__order)
    ,这是我如何完成您想做的事情:
class UserForm(wtforms.Form):                                                   
    def __init__(self,*args,**kwargs):                                        
        super(UserForm,self).__init__(*args,**kwargs)                          

        if kwargs.get(\'update\',None):                                          
            self[\'passwd\'].validators.append(wtforms.validators.Optional())
            self[\'passwd\'].flags.required = False     
        else:                                                                   
            self[\'passwd\'].validators.append(wtforms.validators.Required()) 

    passwd = UnicodeField(                                                      
        u\'Password\',[                                                                       
            wtforms.validators.length(max=50),wtforms.validators.EqualTo(                                         
                \'confirm\',message=\'Passwords must match\'                                  
                )                                                               
            ],widget = wtforms.widgets.PasswordInput()                                
        )                                                                       

    confirm = wtforms.PasswordField(u\'Password Verify\')
然后,当我实例化用户窗体时,在编辑时传递update = True。这似乎为我工作。     ,发生这种情况是因为字段排序是由UnboundField.creation_counter类定义的,该类使用Field类在代码中出现的顺序。
>>> x1 = UserForm()
>>> x2 = UpdateUserForm()
>>> [(f[0],f[1].creation_counter) for f in x1._unbound_fields]
[(\'first_name\',22),(\'last_name\',23),(\'middle_name\',24),(\'username\',25),(\'password\',26),(\'email\',27)]
>>> [(f[0],f[1].creation_counter) for f in x2._unbound_fields]
[(\'first_name\',27),28)]
>>> 
由于这很难解决(因为wtforms尝试使用这种方法变得神奇),因此解决此问题的最佳方法是按所需顺序定义字段。
class BaseForm(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()])

class UserForm(BaseForm):
    password = TextField(u\'Password\',[validators.Required()],widget=PasswordInput())
    email = TextField(u\'Email\',[validators.Optional(),validators.Email()])

class UpdateUserForm(BaseForm):
    password = TextField(u\'Password\',[validators.Optional()],validators.Email()])
但是,如果您是完美主义者或需要遵循DRY原则:
class BaseForm(Form):
    first_name = TextField(u\'First name\',widget=PasswordInput())

class UpdateUserForm(BaseForm):
    password = TextField(u\'Password\',widget=PasswordInput())

BaseForm.email = TextField(u\'Email\',validators.Email()])
    ,要强制对表单字段进行排序,可以使用以下方法:
from collections import OrderedDict

def order_fields(fields,order):
    return OrderedDict((k,fields[k]) for k in order)
并在您的表单构造函数中调用它,如下所示:
class FancyForm(Form,ParentClass1,ParentClass2...):
    x = TextField()
    y = TextField()
    z = TextField()

    _order = \'x y z\'.split()


    def __init__(self,**kwargs):
        super(FancyForm,**kwargs)
        self._fields = order_fields(self._fields,self._order + ParentClass1._order + ParentClass2._order)
    ,我已将以下两个片段合并为两个答案:
def __iter__(self):
    ordered_fields = collections.OrderedDict()

    for name in getattr(self,[]):
        ordered_fields[name] = self._fields.pop(name)

    ordered_fields.update(self._fields)

    self._fields = ordered_fields

    return super(BaseForm,self).__iter__()
在BaseForm上,我的每个表单都是其子级。基本上,在field_order中定义的所有内容均按该顺序进行,其余字段按原样呈现。