WTForms如何使用DecimalField在错误输入上生成正确的字段错误

时间:2013-03-12 16:15:54

标签: python wtforms type-coercion

看一下这个例子

class MyForm(ForM):
    ...snip...
    quantity = DecimalField(u'Quantity', [NumberRange(1, 8)])
    ...snip...

如果用户合作并输入可以强制转换为数字类型的内容,则可以很好地工作。但是,如果用户在浏览器的此字段中输入“asdf”,那么当我尝试在浏览器中呈现DecimalField时,Type Error会抛出Traceback (most recent call last): File "/path/to/app/venv/local/lib/python2.7/site-packages/jinja2/environment.py", line 894, in render return self.environment.handle_exception(exc_info, True) File "/path/to/app/www/templates/submodule/user/submission.html", line 26, in block "submodule_content" {{form.quantity}} File "/path/to/app/venv/local/lib/python2.7/site-packages/wtforms/fields/core.py", line 139, in __call__ return self.widget(self, **kwargs) File "/path/to/app/venv/local/lib/python2.7/site-packages/wtforms/widgets/core.py", line 123, in __call__ kwargs['value'] = field._value() File "/path/to/app/venv/local/lib/python2.7/site-packages/wtforms/fields/core.py", line 542, in _value return format % self.data TypeError: a float is required

以下是追溯的相关部分:

TextField

而不是这种行为,我希望该字段有一个错误添加到自身,以便我仍然可以渲染t。

我目前的解决方案是使用DecimalField代替IsNumeric并使用以下调用方法提供def __call__(self, form, field): if field.data: try: field.data = float(field.data) except ValueError as e: raise ValidationError(self.message) 验证程序:

{{1}}

这几乎完美无缺,并且是我目前的解决方案,但肯定必须有一种重量级的惯用方法来实现这一目标。

1 个答案:

答案 0 :(得分:2)

我不得不处理类似的问题。 SelectMultipleField coerce=text_type(默认为kwarg)会将输入数据转换为str / unicode。这意味着它被强迫了#39; None(当用户没有选择任何内容时,我的字段中输入的值)到"None" - 这是调试的巨大痛苦。你的问题清楚地表明,不仅是我参与了整个内部验证过程。

我找到的正确解决方案是,您编写StringField(textfield)的自定义子类,并覆盖__call__process_dataprocess_formdata方法以满足您的需求。 __call__用于将它们呈现为html(应该由另一个widget可调用来完成),process_data通常用于从python对象(这意味着服务器端)获取数据,process_formdata 1}}与process_data类似,但它处理用户提交的表单。或者,如果你想要干(不要自己重复),你可以覆盖__init__并使自定义验证器成为默认值。

编辑:

DecimalField的愚蠢代码是导致TypeError的原因。查看_value()方法,DecimalField的渲染小部件使用该方法获取<input type="text" ... value=X>的X

def _value(self):
    if self.raw_data:
        return self.raw_data[0]
    elif self.data is not None:
        if self.places is not None: # <-- this line brings you the pain
            if hasattr(self.data, 'quantize'):
                exp = decimal.Decimal('.1') ** self.places
                if self.rounding is None:
                    quantized = self.data.quantize(exp)
                else:
                    quantized = self.data.quantize(exp, rounding=self.rounding)
                return text_type(quantized)
            else:
                # If for some reason, data is a float or int, then format
                # as we would for floats using string formatting.
                format = '%%0.%df' % self.places
                return format % self.data
        else:
            return text_type(self.data)
    else:
        return ''

它在创建字段时声明field.places - field.data的类型之前检查field.places,并且只是说你需要N位精度;它并不表示您有Decimal的N位数作为fields.data,但wtforms devs以某种方式决定检查field.places足以假设该字段包含相应的浮点数。 (代码检查quantize上是否有field.data属性,但仅适用于小数)

我认为你必须编写自己的DecimalField子类。这是我尝试编写DecimalField的非白痴版本

from wtforms.fields.core import DecimalField as _DecimalField

class DecimalField(_DecimalField):
  def _value(self):
    try:
      float(self.data) #check whether you have a 'number'
      return super(DeciamlField, self)._value()
    except (TypeError, ValueError): #self.data is 'None', 'asdf' ...
      return text_type(self.data) if self.data else ''