【问题标题】:wtforms Form class subclassing and field orderingwtforms 表单类子类化和字段排序
【发布时间】:2011-05-01 12:11:10
【问题描述】:

我有一个 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

【问题讨论】:

  • 这个问题解决了吗?你如何渲染表格?您在哪里需要正确的订单?
  • 为什么顺序很重要?你是如何在你的页面中打印出表格的?当涉及到对象属性时,我不认为 python 保证任何特定的顺序(可能是字母顺序除外?)。此外,我发现在制作用户配置文件编辑器时,最好将密码更改表单放在第二页并提供指向它的链接,因为理想情况下,您希望让用户确认他们的密码选择并且您不希望他们在没有明确选项的情况下意外更新密码。

标签: python subclassing wtforms


【解决方案1】:

关于您在遍历表单对象时重新排列字段的第一个问题,这就是我所做的:

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
  • 不确定它是否与 wtforms 上的更改或其他任何内容有关。但似乎 self._unbound_fields 不是需要替换的属性。应该修改 self._fields,因为这些字段已经有界并且我们正处于渲染的中间(可能)。
  • 这在 WTForms 1.x 上有效,但是当 Form.__iter__() 被删除时,内部更改在某些时候停止了它的工作。在调用 super.__init 之前将 _unbound_fields 的顺序移动到 init__() 中似乎可行。
  • 对于较新版本的 WTForms,我通过在以下行之后添加以下行来解决此问题:self._fields = OrderedDict((k[0], self._fields[k[0]]) for k in self._unbound_fields)self._unbound_fields = temp_fields(当然还有适当的集合导入)
【解决方案2】:

我通过在我的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 错误。似乎这会破坏它找到标签的能力?
  • 修复 CSRF 错误:__order = ('csrf_token', 'field1', 'field2', 'field3')
  • 同时使用 wtform-alchemyonly 字段,仅重用,而不是再次编写 __order for field_id in __class__.Meta.only
【解决方案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')

然后,当我实例化用户窗体时,我在编辑时传递 update=True。这似乎对我有用。

【讨论】:

    【解决方案4】:

    这是因为字段排序是由 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 principle

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

    【讨论】:

      【解决方案5】:

      要强制对表单的字段进行排序,您可以使用以下方法:

      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)
      

      【讨论】:

        【解决方案6】:

        我已将两个答案合并为以下 sn-p:

        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 中定义的所有内容都按此顺序排列,其余字段按原样呈现。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-18
          • 1970-01-01
          • 2012-10-10
          • 2014-10-06
          相关资源
          最近更新 更多