1. 概述
查看wtforms代码树fields目录的core.py,会发现在文件开头有这样的语句:
__all__ = ( ‘BooleanField‘, ‘DecimalField‘, ‘DateField‘, ‘DateTimeField‘, ‘FieldList‘, ‘FloatField‘, ‘FormField‘, ‘IntegerField‘, ‘RadioField‘, ‘SelectField‘, ‘SelectMultipleField‘, ‘StringField‘, )
这个表示当前文件在被Import的时候,能够导入的所有方法。上面的这些,除了FieldList和FormField这两个表单字段我们平时使用得比较少以外,
其他的我们或多或少都使用过了。而且通常情况下,上面的那些基本的字段已经完全符合我们要求了,除非特殊情况,否则,我们根本没有机会使用到。
关于FormField的使用,估计大家在使用的时候就挺郁闷的。要么就是报错,要么就是渲染出来的页面出现格式问题,根本没法使用。看官网或许帮助不大,
查找中文资料又很少有帮助的,所以就会很困惑。
不过,我最近遇到的一个问题发现用FormField非常的炫酷,而且特别特别有意思。
2. 遇到的问题
最近遇到日常工单系统,大概有13中常规的工单,每种都是不一样的表单。表单创建之后还有对应List、Get以及处理。所以后面大概有30中不同的视图。
如果像之前那种方式,每个表单对应一个HTML,那样会导致页面太多。
另外就是,主页面空间有限,不能让这些表单页面堆积在一起。所以,还是希望这13种表单服用同一个页面,根据用户选择的类型的不同来做不同的展示。
但是,怎样把这13种表单融合在一个HTML文件中,并且不影响到每个表单每个字段的检查。
这块可能要多说下,大家知道我们在使用wtforms Form表单元类派生出自己的表单类的时候,可能对一些必须的字段定义些validator方法,这样在
用户提交表单的时候wtforms自身为我们做了对应的检查。
很显然,我们在做到这点肯定是使用某种隐藏继续,在需要显示的时候才显示对应的页面,其他情况下都是隐藏的。但是,这块又有一个问题就是,
如果隐藏了,哪些隐藏了的定义了validator的表单字段会在表单提交之后直接报错。
解决了这些问题,还有一个问题就是查看页面详情,用户创建一个表单,期待的时候查看详情也看到这块的内容,而不希望看到其他的表单。所以,
这块又需要对应13种不同的页面来展示不同的表单类型的数据。这块能不能用一种更优雅的方式解决?
概括下遇到的问题,大致如下:
- 需要展示的表单类型过多,而且后面可能会有新的需求加入,所以需要尽可能的简洁以及可扩展的方式来呈现表单,尽可能把所有表单结构放在一个页面中。
- 为了可扩展性,表单类需要继承自wtforms Form类,但是如何确保隐藏的表单中必选字段对当前表单的影响。
- 给用户展示对应创建的表单时,能否用一种通用的方式来展示这13种字段完全不太相同的表单结构,并且代码尽可能具有良好的可扩展性。
3. 大致的解法
由于现在比较晚,所以就不给出详细的解决方案,这块会大致讲下解决方法。不过,相信大家能够从中找到灵感。
针对问题1:
使用wtforms 的FormField,对13种不同方格的表单,创建13种派生自wtforms Form基类的派生类。定义一个功能类,功能类中有13个字段都是FormField类型,
分别对应这13个表单结构。这样就能够做到对单个表单结构的精确控制,即使某个表单内容做了更改也不会影响到页面的布局以及其他模块。
页面的显示,需要重构flask admin系统默认模板admin/file/form.html的内容。具体,对于整个表单调用lib.render_form_fields(form),改为是
对每个表单结构调用lib.render_form_fields(form.XXX_field)。
这样,能够确保渲染出来的FormField字段不会出现格式有问题或者乱码。
针对问题2:
重载派生类表单的validate方法,这块有些技巧,具体可以看下我这块使用的代码:
def validate(self): monitor_type = self.monitor_type.data hidden_field_val = str(monitor_type.get(‘hidden_field‘, ‘‘)) fields = self._fields extra = {} success = True for name, field in iteritems(fields): if name == hidden_field_val: for _name, _field in iteritems(field._fields): if not _field.validate(field._fields): success = False return success
这块有个技巧,就是在表单中使用隐藏表单来表征当前用户选择的表单类型。这样我们在后端做验证的时候,只
针对当前表单元素做验证,而不是表单功能类的13中表单结构。这样,隐藏的表单中的必选字段就不会影响到当前表单,因为我们只会检查当前显示表单的结构。
上述接粗的代码就是取出隐藏的表单字段的值。不过有一点,由于隐藏表单字段的值是不能用户输入的,只能我们自己用jQuery代码对它进行设置。
这样,这块的问题就解决了。
针对问题3:
这块有个比较有意思的技巧,定义数据库表结构的时候要尽可能的与表单结构无关,这样的话可扩展性更好些。否则,一个是表结构字段太多,太庞大了,
另外就是可扩展性不太好。所以,我这块是使用data字段直接表征整个表单所有数据,每次提交表单的时候,直接获取表单的字典结构:
这块使用了表单的data属性,data属性会直接返回一个字典结构。我们在存储到数据库中直接使用json.dumps(form.data),把表单值序列化成字符串并存到数据库表结构data
字段中。用户读取的时候,在把对应字段json.loads回来,并展示给用户就可以了。
如何给用户展示的时候,不需要定义很多HTML页面,而只使用一个,这块的关键是巧用Form类,我们可以根据表单类型获取到对应的表单派生类,并使用它来定义表单对象,
这块可以使用预定义的map就可以了,而且可扩展性非常好。
定义好表单对象之后,从数据库中load数据,把data字段的值json化之后传给表单。然后,我们直接使用admin/file/form.html 模板渲染页面问题就完全解决了。
4. 小结
当使用一种方法复杂到想让你放弃的时候,一定要坚持,因为一定还有其他路要走。因为,你可能之前走错了方向,换个角度思考,你会获得很多意想不到的收获。