繁体   English   中英

从用户界面动态添加新的 WTForms FieldList 条目

[英]Dynamically add new WTForms FieldList entries from user interface

我有一个 flask + wtforms 应用程序,我希望用户能够输入父 object 和任意数量的子对象。 我不确定从用户界面动态创建新的子表单输入字段的最佳方法是什么。

到目前为止我所拥有的

下面是一个完整的工作示例。 (注意:这是一个人为的示例,用于突出显示单个 .py 文件中的所有工作部分,这会导致一些非常混乱的代码。抱歉。)

from flask import Flask, render_template_string
from flask_wtf import FlaskForm
from wtforms import FieldList, FormField, StringField, SubmitField
from wtforms.validators import InputRequired


# Here I'm defining a parent form, AuthorForm, and a child form, BookForm.
# I'm using the FieldList and FormField features of WTForms to allow for multiple
# nested child forms (BookForms) to be attached to the parent (AuthorForm).
class BookForm(FlaskForm):
    title = StringField('title', validators=[InputRequired()])
    genre = StringField('genre', validators=[InputRequired()])

# I'm defining a min_entry of 1 so that new forms contain a blank BookForm entry
class AuthorForm(FlaskForm):
    name = StringField('name', validators=[InputRequired()])
    books = FieldList(FormField(BookForm), min_entries=1)
    submit = SubmitField('Save')


# Here's my Jinja template
html_template_string = """
<html>
    <head><title>stackoverflow example</title></head>
    <body>
        <form action="" method="post" role="form">
            {{ form.hidden_tag() }}
            {{ form.name.label }} {{ form.name() }}
            {% for book in form.books %}
                <p>
                {{ book.title.label }} {{ book.title() }}
                {{ book.genre.label }} {{ book.genre() }}
                {{ book.hidden_tag() }}
                </p>
            {% endfor %}
            {{ form.submit() }}
        </form>
    </body>
</html>
"""

# Alright, let's start the app and try out the forms
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'

@app.route('/', methods=['GET', 'POST'])
def index():
    form = AuthorForm()
    if form.validate_on_submit():
        for book in form.books.data:
            print(book)
    return render_template_string(html_template_string, form=form)

if __name__ == '__main__':
    app.run()

我卡住的地方

我知道如何在服务器端创建新的子条目(BookForm 条目)。 我可以将空字典传递给我的表单,WTForms 会为我生成输入:

...
form = AuthorForm()
form.books.append_child({})
form.books.append_child({})
form.books.append_child({})
...

Bam,现在我的页面有三本书的输入字段,我可以预先填充他们的数据。 WTForms 在页面呈现之前生成表单输入内容。 它计算出每个输入所需的所有 ID,等等。

如果我希望用户能够单击一个按钮并从用户端添加 BookForm 输入的新实例,在页面呈现之后......我该如何 go? 我是否必须使用 WTForms 生成的内容作为参考,自己在 JavaScript 中手动构建输入字段? 这看起来很混乱并且容易损坏,或者至少是丑陋的代码。

WTForms 有没有办法根据需要为新输入呈现 HTML,管理输入标签 ID 的唯一性等? 我可以将一些内容发回服务器到 append 表单的空白条目并重新呈现页面,但这会丢失我所有现有的用户输入,所以它并没有真正起作用。 这个Stackoverflow 问题在评论中暗示了同样的事情,并且提出了同样的反对意见。

如果这必须以丑陋的手动方式完成,那么我可以做到。 当有更好的(甚至可能是官方的)解决方案时,我只是不想采取糟糕的方法。

我完全理解您的问题,使用纯Flask来实现这一目标。 但是几个月前,我处在同样的境地。 经过大量的研究和奇怪而多余的方法,我发现,实施它不仅是艰苦的,而且是漫长而艰巨的,任何小的更改或调试工作都只会破坏整个事情。

我知道这不是您所要的。 但是,如果您希望通过Flask + jQuery做到这一点。 我将向您展示一些我通过视图从HTML前端动态加载数据的工作,如果您改变主意并决定合并一些jQuery,希望对您有所帮助。

这是我的表单,使用HTML和Jinja2:

<form method="POST" action="">
            {{ form.hidden_tag() }}
            <fieldset class="form-group">
                <legend class="border-bottom mb-4">XYZ</legend>

                <div>
                    {{ form.standard.label(class="form-control-label") }}
                    {{ form.standard(class="form-control form-control-lg") }}
                </div>

                <div>
                    {{ form.wps.label(class="form-control-label") }}
                    {{ form.wps(class="form-control form-control-lg") }}
                </div>
            ... 
            </fieldset>
            <div class="form-group">
                {{ form.submit(class='btn btn-outline-success') }}
            </div>

        </form>

这是这种形式的视图:

@app.route("/newqualification/<welder_id>", methods=['GET', 'POST'])
def newqualification(welder_id=None):
    form = #passformhere

        #writemethod.

    return render_template('xyz.html', title='xyz', form=form)

这是我编写的有用的jQuery脚本,您可以通过<script>标记将其包含在HTML中。

<script>
 $(document).ready(function(){

            $("#standard").change(function(){
                var std = $(this).find('option:selected').val(); //capture value from form.

                $.getJSON("{{url_for('modifywps')}}",{'std':std}, function(result){ //use captured value to sent to view via a dictionary {'key':val}, via url_for('view_name')

                    $.each(result, function(i, field){ //value you want will be back here and gone through..
                        $.each(field, function(j,k){
                            $("#wps").append('<option value="'+j+'">'+k+'</option>'); // use jQuery to insert it back into the form, via the form name you gave in jinja templating
                        });
                    });
                });
            });


            $("#wps").change(function(){

                    var std = $('#wps').find('option:selected').val(); // capture value from form.
                    $.getJSON("{{url_for('modifyprocess')}}",{'std':std}, function(result)

                        $.each(result, function(i, field){
                            $.each(field, function(j,k){
                                $("#processes").append('<option value="'+j+'">'+k+'</option>');
                            });
                        });
                    });
            });
</script>

使用jQuery和getJSON()方法,我能够向该视图发送请求:

@app.route("/modifywps", methods=['POST', 'GET'])
def modifywps():
    application_standard_id = request.args.get('std') # gets value from the getJson()

   # process it however you want.. 

    return #whatever you want.

因此,每次发生更改时,都会从站点动态读取数据,将其发送到Flask视图,然后使用所需的内容进行处理,再发送相同的getJSON()并直接插入表单中! 瞧,您已经动态地使用jQuery + Flask,并使用了您最好的新朋友JSON来进行您想做的事情。

经过大量研究,我发现最简单的方法是通过jQuery完成您想要的操作。 学习jQuery中的getJSON()post()方法,将其合并到您的HTML中,然后您便有了答案。 还有jsonify() ,我相信您可以通过import json在Python中import json

将列表提供给jsonify() ,您可以使用该列表以与发送数据相同的getJSON()作为视图返回的视图。

很抱歉再次提出这个问题,但我想指出,可以在不使用 javascript 的情况下实施类似的策略。 这个想法是在提交时预处理表单数据,然后使用处理后的数据重新加载表单(使用 wtforms 的内置功能从其数据重新创建表单)。

一种方法可以如下。 在表单中添加一个额外的输入字段并定义一个update_self方法:

class AuthorForm(FlaskForm):
    name = StringField('name', validators=[InputRequired()])
    books = FieldList(FormField(BookForm), min_entries=1)
    submit = SubmitField('Save')
    addline = SubmitField('Add new line')  # This is the new input

    def update_self(self):
        # read the data in the form
        read_form_data = self.data

        # modify the data as you see fit:
        updated_list = read_form_data['books']
        if read_form_data['addline']:
            updated_list.append({})
        read_form_data['books'] = updated_list

        # reload the form from the modified data
        self.__init__(formdata=None, **read_form_data)

注意: formdata=None是防止 FlaskForm 自动读取表单数据的必要条件。 如果您省略它,表单将在运行.__init__ formdata时接收表单数据,因此忽略您新处理的数据。

在模板中,显示新输入:

{{ form.addline() }}

在定义路由中,调用update_self方法作为提交时的第一件事:

@app.route('/', methods=['GET', 'POST'])
def index():
    form = AuthorForm()

    if form.validate_on_submit():
        form.update_self()  # This reloads the form with the processed data
        for book in form.books.data:
            print(book)

    return render_template_string(html_template_string, form=form)

我希望这有帮助!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM