提交带有动态生成字段的WTform [英] Submit WTform with dynamically generated fields

查看:31
本文介绍了提交带有动态生成字段的WTform的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a form where users can dynamically add fields to it. While submitting this form, backend only sees the fields which backend generated

#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')
                                        ])

我正在通过此表格将render_template('index.html',form = form,...) main.py 返回到 index.html

I'm passing this form with return render_template('index.html', form=form, ...) from the main.py to index.html

所有4个字段都是通过以下方式生成的:

All 4 fields are generated via;

<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 _ ID之后的一行新字段.

A JQuery snippet generates the same fields with different(unique) id's after each button press as a row of new fields after the last #type_ id.

当我按下提交"按钮时,后端仅接收第一行,而不接收生成的行.

When I hit the submit button, the backend only receives the first row not the generated ones.

我在这里想念什么?

更新:

# 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 是字典,因此我无法使用与新字段匹配的名称.但是,即使我为添加的字段指定了唯一的名称,后端也会仅显示带有 print(form.data)的初始表单字段,但是如果我这样做;

Since form.data is a dict I can't have the names matching with the new fields. But even if I give unique names to the added fields the backend only displays the initial form fields with print(form.data) but if I do;

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

我得到所有领域.对我来说似乎不是正确的方法.有什么想法吗?

I get all the fields. Doesn't seem the right way to me. Any thoughts?

推荐答案

每个表单提交只能有一个表单结果.为了能够提交任意数量和未知数量的输入,您需要借助 WTForm的现场附件.

You can only have a single form result per form submission. To be able to submit an arbitrary and unknown number of inputs, you need to restructure your form with the help of WTForm's field enclosures.

forms.py

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 .

I'd strongly recommend that you preface all your field names with expense, not just expense_name for sanity's sake.

index.html

<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>

请注意,HTML输入字段的 id 属性必须遵循特定的模式.因此,对于通过单击 + 按钮添加的每个新费用项目字段,您需要重新编号其输入字段的 id 属性.

Note that the id attribute of the HTML input fields must follow a particular pattern. So for every new expense item field which you add by clicking on the + button, you need to re-number the id attribute of its input fields.

something.js

其他一切都比较容易.现在,您需要编写一段.js,以便在每次添加新费用项目时重新索引所有输入字段的 id 属性.我使用Java的Zepto库完成了此任务.这不好玩,我的.js很糟糕.我在这里能做的最好的事情就是粘贴整个内容,希望对您有所帮助.我知道这很令人困惑,但是我在一个课程中添加了多个 classes .对于您来说,您需要 expense_item/expense_request 或其他配套工具:

Everything else was comparatively easy. You now need to write a piece of .js which will re-index the id attributes of all the input fields every time a new expense item is added. I accomplished this using the Zepto library for Javascript. It wasn't fun, and my .js is terrible. The best I can do here is just paste the whole thing and hope it'll be of service to you. I know it's confusing, but I was added multiple classes to a course. For you, you'll want expense_item/expense_request or whatever you go with:

// 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('-'));
        })
    })
}

views.py

@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'])

这篇关于提交带有动态生成字段的WTform的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆