Отправить WTform с динамически генерируемыми полями

1

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

#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,...) эту форму с 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_.

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

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

ОБНОВИТЬ:

# 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 - это form.data, я не могу иметь имена, совпадающие с новыми полями. Но даже если я присваиваю уникальные имена добавленным полям, бэкэнд отображает только начальные поля формы с print(form.data) но если я это сделаю;

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

Я получаю все поля. Не кажется мне правильным. Какие-нибудь мысли?

  • 0
    Вы говорите, что браузер отправляет обратно только исходную форму (в этом случае это проблема с JavaScript, и вам нужно показать js, которая создает дополнительные поля) или что все данные отправляются обратно, но обрабатываются только исходные поля ( в каком случае нам нужно увидеть код вида)?
  • 0
    Вы делаете множественный выбор, добавляя новый тип?
Показать ещё 2 комментария
Теги:
flask
wtforms

1 ответ

1

Вы можете иметь только один результат формы для каждой отправки формы. Чтобы иметь возможность подавать произвольное и неизвестное количество входных данных, вам необходимо реструктурировать форму с помощью полевых приложений 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_name все имена полей с expense_name 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 ужасен. Лучшее, что я могу здесь сделать, это просто вставить все это и надеяться, что оно будет вам полезно. Я знаю, что это сбивает с толку, но я добавил несколько курсов в курс. Для вас вам понадобится cost_item/cost_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'])
  • 0
    Я попробую это как можно скорее. Похоже, мне понадобится часть класса '' 'ExpensesForm (FlaskForm): "" "Коллекция статей расходов." "" Items = FieldList (FormField (ExpenseItem), min_entries = 1)' ''. и я думаю, что мне придется добавить другие поля в этот новый класс. Я знаю, что не .js но могу вас заверить, что все новые поля получают новый / увеличенный id который следует шаблону. Это выглядит многообещающе. Спасибо. примечание: я сосу при именовании вещей.
  • 0
    nwm, вы передали весь класс в новый, а не в поля.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню