Динамически добавлять новые записи WTForms FieldList из пользовательского интерфейса

У меня есть приложение flask + wtforms, в котором я хочу, чтобы пользователи могли вводить как родительский объект, так и произвольное количество дочерних объектов. Я не уверен, как лучше всего динамически создавать новые поля ввода дочерней формы из пользовательского интерфейса.

Что у меня есть до сих пор

Ниже приведен полный рабочий пример. (Примечание: это искусственный пример выделения всех рабочих частей в одном файле .py, что делает код довольно беспорядочным. Извините.)

from flask import Flask, render_template_string
from flask_wtf import FlaskForm
from wtforms import FieldList, FormField, StringField, SubmitField
from wtforms.validators import InputRequired


# Here I'm defining a parent form, AuthorForm, and a child form, BookForm.
# I'm using the FieldList and FormField features of WTForms to allow for multiple
# nested child forms (BookForms) to be attached to the parent (AuthorForm).
class BookForm(FlaskForm):
    title = StringField('title', validators=[InputRequired()])
    genre = StringField('genre', validators=[InputRequired()])

# I'm defining a min_entry of 1 so that new forms contain a blank BookForm entry
class AuthorForm(FlaskForm):
    name = StringField('name', validators=[InputRequired()])
    books = FieldList(FormField(BookForm), min_entries=1)
    submit = SubmitField('Save')


# Here's my Jinja template
html_template_string = """
<html>
    <head><title>stackoverflow example</title></head>
    <body>
        <form action="" method="post" role="form">
            {{ form.hidden_tag() }}
            {{ form.name.label }} {{ form.name() }}
            {% for book in form.books %}
                <p>
                {{ book.title.label }} {{ book.title() }}
                {{ book.genre.label }} {{ book.genre() }}
                {{ book.hidden_tag() }}
                </p>
            {% endfor %}
            {{ form.submit() }}
        </form>
    </body>
</html>
"""

# Alright, let's start the app and try out the forms
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'

@app.route('/', methods=['GET', 'POST'])
def index():
    form = AuthorForm()
    if form.validate_on_submit():
        for book in form.books.data:
            print(book)
    return render_template_string(html_template_string, form=form)

if __name__ == '__main__':
    app.run()

Где я застрял

Я знаю, как создавать новые дочерние записи (записи BookForm) на стороне сервера. Я мог бы просто передать пустые словари в свою форму, и WTForms сгенерировал бы для меня входные данные:

...
form = AuthorForm()
form.books.append_child({})
form.books.append_child({})
form.books.append_child({})
...

Бам, теперь на моей странице есть поля ввода для трех книг, и я мог бы предварительно заполнить их данные. WTForms генерирует содержимое ввода формы перед отображением страницы. Он обрабатывает все идентификаторы, необходимые для каждого входа и т. д.

См. также:  Невозможно обучить CNN на основе внимания

Если я хочу, чтобы пользователь мог щелкнуть кнопку и добавить новые экземпляры ввода BookForm со стороны пользователя после того, как страница была отрисована… как мне это сделать? Должен ли я сам вручную создавать поля ввода в JavaScript, используя в качестве эталона то, что было сгенерировано WTForms? Это кажется грязным и склонным к поломке или, по крайней мере, к уродливому коду.

Есть ли способ для WTForms отображать HTML для новых входных данных по мере необходимости, управляя уникальностью идентификаторов входных тегов и т. Д.? Я мог бы отправить что-то обратно на сервер, чтобы добавить пустые записи в форму и повторно отобразить страницу, но это лишило бы меня всего моего существующего пользовательского ввода, поэтому на самом деле это не работает. Этот вопрос Stackoverflow предполагает то же самое в комментариях, и то же самое возражение поднятый.

Если это должно быть сделано уродливым ручным способом, то я могу это сделать. Я просто не хочу использовать плохой подход, когда есть лучшее (и, возможно, даже официальное) решение этой проблемы.

Это в основном то, для чего был создан jquery.   —  person daveruinseverything    schedule 29.04.2019

Этот ответ делает что-то вроде того, что требуется. Клонирование соответствующих полей в js, увеличение идентификаторов и рендеринг «новой» разметки — единственный разумный способ сделать это, я думаю.   —  person daveruinseverything    schedule 29.04.2019

Понравилась статья? Поделиться с друзьями:
IT Шеф
Комментарии: 1
  1. daveruinseverything

    Я ПРЕКРАСНО понимаю, в чем ваш вопрос, чтобы использовать чистый Flask для достижения этой цели. Но я был в такой же ситуации пару месяцев назад. И после многих исследований и странных и избыточных методов я обнаружил, что реализация этого будет не только кропотливой, но долгой и трудной, и любое небольшое изменение или усилия по отладке просто сломают все это.

    Я знаю, что это не совсем то, о чем вы просили. Но если вы хотите сделать это с помощью Flask+ jQuery. Я покажу вам часть работы, которую я проделал для динамической загрузки данных из интерфейса HTML через представления, надеюсь, что это может быть вам полезно, ЕСЛИ вы передумаете и решите включить jQuery.

    Это моя форма, использующая HTML и Jinja2:

    <form method="POST" action="">
                {{ form.hidden_tag() }}
                <fieldset class="form-group">
                    <legend class="border-bottom mb-4">XYZ</legend>
    
                    <div>
                        {{ form.standard.label(class="form-control-label") }}
                        {{ form.standard(class="form-control form-control-lg") }}
                    </div>
    
                    <div>
                        {{ form.wps.label(class="form-control-label") }}
                        {{ form.wps(class="form-control form-control-lg") }}
                    </div>
                ... 
                </fieldset>
                <div class="form-group">
                    {{ form.submit(class='btn btn-outline-success') }}
                </div>
    
            </form>
    

    Это представление, которое вызывает эту форму:

    @app.route("/newqualification/<welder_id>", methods=['GET', 'POST'])
    def newqualification(welder_id=None):
        form = #passformhere
    
            #writemethod.
    
        return render_template('xyz.html', title='xyz', form=form)
    

    И это небольшой полезный скрипт jQuery, который я написал, который вы можете включить в HTML с помощью тега <script>.

    <script>
     $(document).ready(function(){
    
                $("#standard").change(function(){
                    var std = $(this).find('option:selected').val(); //capture value from form.
    
                    $.getJSON("{{url_for('modifywps')}}",{'std':std}, function(result){ //use captured value to sent to view via a dictionary {'key':val}, via url_for('view_name')
    
                        $.each(result, function(i, field){ //value you want will be back here and gone through..
                            $.each(field, function(j,k){
                                $("#wps").append('<option value="'+j+'">'+k+'</option>'); // use jQuery to insert it back into the form, via the form name you gave in jinja templating
                            });
                        });
                    });
                });
    
    
                $("#wps").change(function(){
    
                        var std = $('#wps').find('option:selected').val(); // capture value from form.
                        $.getJSON("{{url_for('modifyprocess')}}",{'std':std}, function(result)
    
                            $.each(result, function(i, field){
                                $.each(field, function(j,k){
                                    $("#processes").append('<option value="'+j+'">'+k+'</option>');
                                });
                            });
                        });
                });
    </script>
    

    Используя jQuery и метод getJSON(), я могу отправить запрос в это представление:

    @app.route("/modifywps", methods=['POST', 'GET'])
    def modifywps():
        application_standard_id = request.args.get('std') # gets value from the getJson()
    
       # process it however you want.. 
    
        return #whatever you want.
    

    Таким образом, каждый раз, когда происходит изменение, данные динамически считываются с сайта, отправляются в представление Flask, обрабатываются там тем, что вы хотите, отправляются обратно тем же самым getJSON() и вставляются прямо в форму!! Вуаля, вы динамически использовали jQuery+Flask для манипулирования тем, что хотите сделать, используя своего нового лучшего друга JSON.

    После долгих исследований я нашел самый простой способ сделать то, что вы хотите, через jQuery. Изучите методы getJSON() и post() в jQuery, включите их в свой HTML, и вы получите ответ. А также jsonify(), который, как мне кажется, можно импортировать в Python через import json.

    Передайте список в jsonify(), который вы можете использовать для возврата в качестве представления в том же getJSON(), который вы использовали для отправки данных.

    Я приму это как лучший ответ — @dustin-ingram, если вы согласны, вы можете присудить награду person daveruinseverything; 02.05.2019

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: