提交带有动态生成字段的WTform

时间:2019-01-06 08:02:46

标签: python flask wtforms

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

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

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

我在这里想念什么?

更新:

# 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)的初始表单字段,但是如果我这样做的话,

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

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

1 个答案:

答案 0 :(得分:2)

每个表单提交只能有一个表单结果。为了能够提交任意数量和未知数量的输入,您需要在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

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属性重新编号。

something.js

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

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