【问题标题】:Creating Custom Flask WTForms Widgets创建自定义 Flask WTForms 小部件
【发布时间】:2021-08-16 19:58:15
【问题描述】:

我有一个自定义 Flask WTForm,我希望该表单的一部分包含一个按钮类型输入列表,这些输入是根据表中的条目数创建的,但在显示时遇到了困难我想要的方式并通过表单验证。我对该字段外观的目标是让它显示为Inline Button Group with a Checkbox type input。下面是我的路由方法的一个例子。

@bp.route('/new_channel', methods=['GET', 'POST'])
def new_channel():

    # Pre-populate the NewChannelForm 
    newChannelForm = NewChannelForm()
    newChannelForm.required_test_equipment.choices =  [(equip.id, equip.name) for equip in TestEquipmentType.query.order_by('name')]
    test_equipment_types = TestEquipmentType.query.all()

return render_template('new_channel.html', title='Add New Channel', form=newChannelForm,
                            test_equipment_types=test_equipment_types)

我尝试将FieldListFormField 一起使用,其中包含带有BooleanField 的自定义表单,并设法使样式正确,但表单验证不起作用。通过进一步研究,BooleanFieldFieldList 不兼容。

我的下一步是使用 MultiSelectField 的 Flask WTForm 示例,其中包含用于字段的自定义小部件和用于选项的自定义小部件。默认如下图:

class MultiCheckboxField(SelectMultipleField):
    """
    A multiple-select, except displays a list of checkboxes.

    Iterating the field will produce subfields, allowing custom rendering of
    the enclosed checkbox fields.
    """
    widget = widgets.ListWidget(prefix_label=False)
    option_widget = widgets.CheckboxInput()

我的目标是修改它以制作一个名为InLineButtonGroupWidget 的自定义小部件,它将使用内嵌按钮列表的样式,就像我之前包含的图片一样。此外,我希望创建一个名为 CheckboxButtonInput 的自定义 option_widget 来获取每个单独按钮的样式,我可以在其中将信息传递到该字段。这就是我对两者的目标:

InLineButtonGroupWidget

<div class="btn-group-toggle" role="group" data-toggle="buttons"></div>

复选框按钮输入

<label class="btn btn-outline-info" for="check-1">Calibrator
     <input type="checkbox" id="check-1">
</label> 

关于如何创建自定义小部件的文档有点让我头晕目眩,并没有给出最好的解释,所以我正在寻找一些

编辑: 使用了 Andrew Clark 的建议,这是我的最终实现:

routes.py

@bp.route('/new_channel', methods=['GET', 'POST'])
def new_channel():

    class NewChannelForm(FlaskForm):
        pass
    
    test_equipment_types = TestEquipmentType.query.all()
    for test_equipment_type in test_equipment_types:
        # Create field(s) for each query result
        setattr(NewChannelForm, f'checkbox_{test_equipment_type.name}', BooleanField(label=test_equipment_type.name, id=f'checkbox-{test_equipment_type.id}'))

    newChannelForm = NewChannelForm()

    if newChannelForm.validate_on_submit():
        print('Form has been validated')

        for test_equipment_type in test_equipment_types:
            if newChannelForm.data[f'checkbox_{test_equipment_type.name}']:
                channel.add_test_equipment_type(test_equipment_type)
        return redirect(url_for('main.index'))    

    print(newChannelForm.errors.items())

    return render_template('new_channel.html', title='Add New Channel', form=newChannelForm, units_dict=ENG_UNITS,
                            test_equipment_types=test_equipment_types)

new_channel.html

    <!-- Test Equipment Selection -->
        <div class="row">  
            <legend>Test Equipment Selection:</legend>           
            <div class="col-md-12">
                <div class="btn-group-toggle mb-3" role="group" data-toggle="buttons">
                    {% for test_equipment_type in test_equipment_types %}
                    <label class="btn btn-outline-info" for="checkbox-{{ test_equipment_type.id }}">
                        {{ test_equipment_type.name }}
                        {{ form['checkbox_{}'.format(test_equipment_type.name)] }}
                    </label>                    
                    {% endfor %}
                </div>
            </div>
        </div>

【问题讨论】:

  • 您能详细说明这部分吗 -> “我有一部分表单包含一些复选框类型的输入,这些输入是根据表中的条目数(~5-10)创建的”你想要基于给定值在屏幕截图中显示相同的 3 个按钮 5 到 10 次?或者有许多不同的按钮,如果 value == 5,您将始终包含这 5 个按钮,如果 value 为 6,您将提供相同的 5 加上一个额外的按钮?
  • 我已经编辑了我的帖子,以便在我的路线方法中包含一些代码。我的意思是我查询一个返回许多项目的表,这些项目在填充时应该只有大约 5-10 个项目。我将为每个 TestEquipmentType 使用 id 和名称来填充标签和输入字段的 id,并将名称用于标签文本。

标签: python forms flask widget wtforms


【解决方案1】:

我通常会做这样的事情来处理表单构建:

def buildNewChannelForm():
    class NewChannelForm(FlaskForm):
        # put any non dynamic fields here
        pass

    test_equipment_types = TestEquipmentType.query.all()
    for test_equipment_object in test_equipment_types:
        # create field(s) for each query result
        setattr(NewChannelForm, f'field_name_{test_equipment_object.id}', SelectField(label='label name', choices=[(equip.id, equip.name) for equip in TestEquipmentType.query.order_by('name')]))

    return NewChannelForm()

编辑 1:

我不确定是否有更好的方法来做,但我通常会做这样的事情来处理数据提交

def buildNewChannelForm():
    new_channel_form_variable_list = []
    class NewChannelForm(FlaskForm):
        # put any non dynamic fields here
        pass

    test_equipment_types = TestEquipmentType.query.all()
    for test_equipment_object in test_equipment_types:
        # create field(s) for each query result
        setattr(NewChannelForm, f'field_name_{test_equipment_object.id}', SelectField(label='label name', choices=[(equip.id, equip.name) for equip in TestEquipmentType.query.order_by('name')]))

        # append variable name
        new_channel_form_variable_list.append(f'field_name_{test_equipment_object.id}')

    return NewChannelForm(), new_channel_form_variable_list

然后你可以使用你的变量列表来渲染你的表单,只需包含在你的 render_template 语句中

{% for variable_name in new_channel_form_variable_list %}
    {{ form[variable_name] }}
{% endfor %}

然后在路由中提交表单时,它只是一个字典。所以你可以做这样的事情

result_dictionary = form.data

# either loop through your variable list or handle each one individually
for variable_name in new_channel_form_variable_list:
    print(f'variable name: {variable_name}, value: {result_dictionary[variable_name]}')

【讨论】:

  • 动态字段名实现只是一个建议。我建议做一些事情让它与众不同。
  • 所以这会在表单中添加新字段,然后您可以在模板文件中指定它们的位置/格式?另外,在新字段通过 form.validate_on_submit 后,您如何建议从新字段中提取数据?
猜你喜欢
  • 2013-01-08
  • 1970-01-01
  • 2018-09-03
  • 1970-01-01
  • 2013-12-24
  • 2023-03-20
  • 2019-11-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多