代码之家  ›  专栏  ›  技术社区  ›  Leustad

使用动态生成的字段提交wtform

  •  1
  • Leustad  · 技术社区  · 5 年前

    我有一个表单,用户可以在其中动态添加字段。提交此表单时,后端仅查看后端生成的字段

    #forms.py
    
    class ExpensesForm(FlaskForm):
        expense_name = StringField('Expense_Item', validators=[DataRequired()])
        cost = FloatField('Cost', validators=[DataRequired()])
        due_date = DateField('Due Date', format='%Y-%m-%d', validators=[DataRequired()], default=datetime.datetime.today().date())
        type = SelectField('Role', choices=[('mutual', 'Mutual'),
                                            ('personal#1', 'Personal #1'),
                                            ('personal#2', 'Personal #2')
                                            ])
    

    我把这个表格传给 return render_template('index.html', form=form, ...) main.py index.html

    所有4个字段都是通过生成的;

    <form class="form-horizontal" id="main-form" enctype=multipart/form-data role="form" method="post" action="/">
            <input type="hidden" name="count" value="1"/>
            {{ form.csrf_token }}
    
            {{ form.expense_name(placeholder="Expense Name", id="expense_1", value="") }}
            {{ form.cost(placeholder="Cost", id="cost_1", class="cost", value="") }}
            {{ form.due_date(id="due_date_1") }}
            {{ form.type(placeholder="Type", id="type_1") }}
            <button id="b1" class="btn btn-info add-more" type="button">+</button>
            <small>Press + to add another set of fields.</small>
            <br>
            <hr>
            <button class="btn btn-sm btn-success" type="submit">Post Expense</button>
        </form>
    

    jquery片段在每个按钮按下后生成具有不同(唯一)ID的相同字段,作为最后一个按钮后的一行新字段 #type_ 身份证件。

    当我点击提交按钮时,后端只接收第一行,而不是生成的行。

    我这里缺什么?

    更新:

    # main.py
    @main_blueprint.route('/', methods=['GET', 'POST'])
    def index():
        dates = []
        form = ExpensesForm(request.form)
        if request.method == 'POST':
            print(form.data)
            # prints the following even when the browser sends more than 1 set of data:
            # {'due_date': None, 'csrf_token': long_hash, 'expense_name': 'Electric', 'cost': 13.0, 'type': 'mutual'}
            if form.validate_on_submit():
                for n in range(len(form.expense_name.data)):
                    if form.expense_name.raw_data[n] != '':
                        data = Expenses(form.expense_name.raw_data[n].title(),
                                        form.cost.raw_data[n],
                                        datetime.datetime.strptime(form.due_date.raw_data[n], '%Y-%m-%d').date(),
                                        form.type.raw_data[n].title(),
                                        )
                        print(data)
                        db.session.add(data)
                        db.session.commit()
            return redirect(url_for('main.index'))
        expenses = db.session.query(Expenses).all()
        # expenses_schema = ExpensesSchema()
        # output = expenses_schema.dump(expenses).data
    
        output = []
        for i in expenses:
            output.append(i.__dict__)
        return render_template('index.html', form=form, expenses=output)
    

    更新2

    自从 form.data 是一个dict,我不能让名称与新字段匹配。但是,即使我为添加的字段提供唯一的名称,后端也只显示初始表单字段 print(form.data) 但如果我这样做了;

        for k, v in request.form.items():
            print(k, v)
    

    我得到了所有的田地。对我来说似乎不是正确的方式。有什么想法吗?

    1 回复  |  直到 5 年前
        1
  •  2
  •   Hugo    5 年前

    每个表单提交只能有一个表单结果。为了能够提交任意和未知数量的输入,您需要借助 WTForm's field enclosures .

    表单

    from flask_wtf import FlaskForm
    from wtforms import (
        FieldList, FormField, DateField FloatField, StringField, SelectField)
    from wtforms import Form as NoCsrfForm
    
    
    class ExpenseItem(NoCsrfForm):
        expense_name = StringField('Expense_Item', validators=[DataRequired()])
        cost = FloatField('Cost', validators=[DataRequired()])
        due_date = DateField('Due Date', format='%Y-%m-%d',
                                     validators=[DataRequired()],
                                     default=datetime.datetime.today().date())
        type = SelectField('Role', choices=[
            ('mutual', 'Mutual'),
            ('personal#1', 'Personal #1'),
            ('personal#2', 'Personal #2'),
        ])
    
    class ExpensesForm(FlaskForm):
        """A collection of expense items."""
        items = FieldList(FormField(ExpenseItem), min_entries=1)
    

    我强烈建议您在所有域名前加上 expense 不仅仅是 expense_name 为了理智。

    索引文件

    <form class="form-horizontal" id="main-form" enctype=multipart/form-data role="form" method="post" action="/">
        <input type="hidden" name="count" value="1"/>
        {{ form.hidden_tag() }}
        {% for expense_item in form.items %}
            {{ form.expense_name(placeholder="Expense Name", value="") }}
            {{ form.cost(placeholder="Cost", class="cost", value="") }}
            {{ form.due_date() }}
            {{ form.type(placeholder="Type") }}
        {% endfor %}
    
        <button id="b1" class="btn btn-info add-more" type="button">+</button>
        <small>Press + to add another set of fields.</small>
        <br>
        <hr>
        <button class="btn btn-sm btn-success" type="submit">Post Expense</button>
    </form>
    

    请注意 id HTML输入字段的属性必须遵循特定的模式。因此,对于通过单击 + button,您需要重新给 身份证件 其输入字段的属性。

    JS

    其他的事情都比较容易。现在您需要编写一段.js,它将重新索引 身份证件 每次添加新支出项时,所有输入字段的属性。我使用Javascript的zepto库完成了这项工作。这不好玩,我的.js很糟糕。我在这里能做的最好的就是把整个东西贴上去,希望它能对你有所帮助。我知道这很混乱,但我被加了很多 到A 课程 . 为了你,你会想要 开支项目/开支申请 或者无论你选择什么:

    // append class-box when new class link clicked
    $("#new-class").click(function(event) {
        appendClassBox('#classes', {{ newclass|tojson|safe }});
        reindexNames('.class-box');
        return false;
    })
    
    // remove class box when its "remove" link is clicked
    $(document).on('click', '#remove-class', function(){
        var $toremove = $(this).closest('.class-box');
        $toremove.remove();
        reindexNames('.class-box');
        return false;
    })
    
    // add a new class-box
    function appendClassBox(container, content) {
        $(container).append(content);
        // raise last and hence newest class box
        raiseClassBox($(container).children().last())
        return false;
    }
    
    function isNormalInteger(str) {
        var n = ~~Number(str);
        return String(n) === str && n >= 0;
    }
    
    // re-index class-box names
    function reindexNames(class_name) {
        var $oboxen = $(class_name);
        $oboxen.each(function(index) {
            // Get all the input fields in the class-box.
            var $labels = $oboxen.eq(index).find('label')
            var $inputs = $oboxen.eq(index).find(
                'input, select, textarea')
            // Update the index contained in the name attribute.
            $inputs.each(function(idx) {
                var $name = $inputs.eq(idx).attr('name').split('-');
                // If number in name, grab from number onwards.
                var $has_num = false
                for (var part in $name) {
                    if (isNormalInteger($name[part])) {
                        $has_num = true
                        $name = $name.slice(part)
                        $name[0] = index
                        break
                    }
                }
                // Re-index.
                if ($has_num == false) {
                    $name.unshift(index)
                }
                var $prefix = 'questions'
                if (class_name == '.class-box') {
                    $prefix = 'classes'
                }
                $name.unshift($prefix)
                if (idx > 0) {
                    $labels.eq(idx - 1).attr('for', $name.join('-'));
                }
                $inputs.eq(idx).attr('id', $name.join('-'));
                $inputs.eq(idx).attr('name', $name.join('-'));
            })
        })
    }
    

    VIEW

    @main_blueprint.route('/', methods=['GET', 'POST'])
    def index():
        form = ExpensesForm()
    
        # Iterate over a collection of new expense items.
        if form.validate_on_submit():
            for item in form.items.data:
                print(item['expense_name'])
                print(item['cost'])
                print(item['due_date'])
                print(item['type'])