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

19

我有一个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中将密码字段设为可选项:

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', [validators.Optional()], 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', [validators.Optional()], widget=PasswordInput())
        UserForm.__init__(self, formdata=None, obj=None, prefix='', **kwargs)

最终,我需要的是:

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

1
这个问题解决了吗?你如何渲染表单?你在哪里需要正确的顺序? - Danny Navarro
为什么顺序很重要?你是如何在页面上打印表格的?我认为Python在处理对象属性时不保证任何特定的顺序(除了可能是按字母顺序?)。此外,当制作用户配置文件编辑器时,最好将密码更改表单放在第二个页面上,并提供链接,因为理想情况下,您希望用户确认其密码选择,而且您不希望他们在没有明确选项的情况下意外更新密码。 - tkone
6个回答

13

关于你第一个问题中提到的在迭代表单对象时重新排列字段的问题,这是我所做的:

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时(可能是从模板迭代表单渲染字段而来),您将看到usernamepasswordfull_name。通常,您最后会看到username

这正是我所寻找的,感谢 rhyek! - squeegee
不确定是否与wtforms的更改或其他任何事情有关。但似乎self._unbound_fields不是需要替换的属性。应该修改self._fields,因为这些字段已经绑定,并且我们正在渲染中(可能)。 - Drachenfels
这在WTForms 1.x上有效,但是当Form.iter()被移除时,内部更改导致它在某个时候停止工作。将_unbound_fields的排序移动到__init__()中,在调用super.__init__之前似乎可以解决问题。 - Mark
1
对于WTForms的新版本,我通过在以下行后添加以下代码来解决此问题:self._fields = OrderedDict((k[0], self._fields[k[0]]) for k in self._unbound_fields)self._unbound_fields = temp_fields(当然,需要适当导入collections)。 - Attack68

4

我通过在我的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)

我喜欢这个的简单易用性,但是当我在模板中使用{{form.hidden_tag}} 时,出现了CSRF错误。似乎这会破坏它查找标签的能力? - jwdink
修复CSRF错误:__order = ('csrf_token', 'field1', 'field2', 'field3') - Elmer Thomas
当使用_wtform-alchemy_与only字段时,可以重复使用它而不必再次编写*__order*。for field_id in __class__.Meta.only - rho

3
这是我完成你想做的事情的方法:
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')

然后,当我实例化UserForm时,在编辑时传递update=True参数。这对我来说似乎有效。


3

要在表单字段上强制执行顺序,您可以使用以下方法:

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, *args, **kwargs):
        super(FancyForm, self).__init__(*args, **kwargs)
        self._fields = order_fields(self._fields, 
                                    self._order + ParentClass1._order + ParentClass2._order)

2
这是因为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', 22), ('last_name', 23), ('middle_name', 24), ('username', 25), ('email', 27), ('password', 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()], widget=PasswordInput())
    email = TextField(u'Email', [validators.Optional(), validators.Email()])

但是如果你是一个完美主义者或需要遵循DRY原则

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())

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

BaseForm.email = TextField(u'Email', [validators.Optional(), validators.Email()])

1

我将两个答案合并成下面的代码片段:

def __iter__(self):
    ordered_fields = collections.OrderedDict()

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

    ordered_fields.update(self._fields)

    self._fields = ordered_fields

    return super(BaseForm, self).__iter__()

BaseForm__iter__方法是我每个表单的父类。基本上,field_order中定义的所有内容都按照该顺序排列,其余字段按原样呈现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接