Отправить WTform с динамически генерируемыми полями - PullRequest
0 голосов
/ 06 января 2019

У меня есть форма, в которой пользователи могут динамически добавлять поля. При отправке этой формы бэкэнд видит только те поля, которые сгенерировал бэкэнд

#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 генерирует те же поля с разными (уникальными) идентификаторами после каждого нажатия кнопки в виде ряда новых полей после последнего #type_ идентификатора.

Когда я нажимаю кнопку отправки, бэкэнд получает только первую строку, а не сгенерированные.

Что мне здесь не хватает?

UPDATE:

# 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 голосов
/ 09 января 2019

Вы можете иметь только один результат формы для каждой отправки формы. Чтобы иметь возможность подавать произвольное и неизвестное количество входных данных, вам необходимо реструктурировать форму с помощью WTForm полевых приложений .

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>

Обратите внимание, что атрибут id полей ввода HTML должен соответствовать определенному шаблону. Таким образом, для каждого нового поля статьи расходов, которое вы добавляете, нажимая кнопку +, необходимо изменить нумерацию атрибута id его полей ввода.

something.js

Все остальное было сравнительно легко. Теперь вам нужно написать фрагмент .js, который будет переиндексировать атрибуты id всех полей ввода каждый раз, когда добавляется новая статья расходов. Я сделал это, используя библиотеку Zepto для Javascript. Это было не весело, а мой .js ужасен. Лучшее, что я могу здесь сделать, это просто вставить все это и надеяться, что оно будет вам полезно. Я знаю, что это сбивает с толку, но я добавил несколько классов в курс . Для вас вам понадобится расход_элемент / расход_запрос или все, что вы пошли с:

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