从用户界面动态添加新的WTForms FieldList条目

时间:2018-08-13 07:24:21

标签: javascript python flask jinja2 wtforms

我有一个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({})
...

Bam,现在我的页面上有三本书的输入字段,我可以预先填充它们的数据。 WTForms在呈现页面之前生成表单输入内容。它计算出每个输入等所需的所有ID。

如果我希望用户能够单击按钮并在呈现页面后从用户端添加BookForm输入的新实例,该如何处理?我是否必须使用WTForms生成的内容作为参考,自己在JavaScript中手动构造输入字段?这似乎很杂乱,容易破损,或者至少是丑陋的代码。

WTForms是否可以根据需要呈现新输入的HTML,管理输入标签ID等的唯一性?我可以将某些内容回发到服务器,以将空白条目添加到表单中并重新呈现页面,但这会使我失去所有现有的用户输入,因此它实际上不起作用。 This Stackoverflow问题在注释中暗示了相同的观点,并且提出了相同的反对意见。

如果必须通过丑陋的手动方式完成操作,那么我可以进行管理。当有更好的(甚至可能是官方的)解决方案时,我只是不想采取糟糕的方法。

1 个答案:

答案 0 :(得分:6)

我完全理解您的问题,使用纯Flask来实现这一目标。 但是几个月前,我处在同样的境地。经过大量的研究和奇怪而多余的方法,我发现,实施它不仅是艰苦的,而且是漫长而艰巨的,任何小的更改或调试工作都只会破坏整个事情。

我知道这不是您所要的。但是,如果您希望通过Flask + jQuery做到这一点。我将向您展示一些我通过视图从HTML前端动态加载数据的工作,如果您改变主意并决定合并一些jQuery,希望对您有所帮助。

这是我的表单,使用HTML和Jinja2:

def mnist_blackbox(train_start=0, train_end=1000, test_start=0,
                   test_end=200, nb_classes=NB_CLASSES,
                   batch_size=BATCH_SIZE, learning_rate=LEARNING_RATE,
                   nb_epochs=NB_EPOCHS, holdout=HOLDOUT, data_aug=DATA_AUG,
                   nb_epochs_s=NB_EPOCHS_S, lmbda=LMBDA,
                   aug_batch_size=AUG_BATCH_SIZE):
  """
  MNIST tutorial for the black-box attack from arxiv.org/abs/1602.02697
  :param train_start: index of first training set example
  :param train_end: index of last training set example
  :param test_start: index of first test set example
  :param test_end: index of last test set example
  :return: a dictionary with:
           * black-box model accuracy on test set
           * substitute model accuracy on test set
           * black-box model accuracy on adversarial examples transferred
             from the substitute model
  """

  # Set logging level to see debug information
  set_log_level(logging.DEBUG)

  # Dictionary used to keep track and return key accuracies
  accuracies = {}

  # Perform tutorial setup
  assert setup_tutorial()

  # Create TF session
  sess = tf.Session()

  # Get data

  with open('X.pickle','rb') as pickle_in:
     x_all= pickle.load(pickle_in)
     x_all=np.divide(x_all, 255)

  with open('y.pickle','rb') as pickle_in:
     y_all= pickle.load(pickle_in)

  # Convert to float 32
     x_all= np.float32(x_all)

     y_all= np.float32(y_all)


  num_class=3
  class_lables=np.zeros((len(y_all),num_class)) 


  # make y dataset a matrix (each row shows the class lable)
  for index in range(len(y_all)):
     if y_all[index]==0:
        class_lables[index][0]=1
     elif y_all[index]==1:
        class_lables[index][1]=1
     elif y_all[index]==2:
        class_lables[index][2]=1

  y_all=class_lables

  #splitting data set to train/test randomly
  x_train, x_test, y_train, y_test = train_test_split(x_all, y_all, test_size=0.2)


  """
  # Get MNIST data
  mnist = MNIST(train_start=train_start, train_end=train_end,
                test_start=test_start, test_end=test_end)
  x_train, y_train = mnist.get_set('train')
  x_test, y_test = mnist.get_set('test')
  """

  # Initialize substitute training set reserved for adversary
  x_sub = x_test[:holdout]
  y_sub = y_test[:holdout]

  x_sub_1=x_sub[:,:1,:,:]


  # Redefine test set as remaining samples unavailable to adversaries
  x_test = x_test[holdout:]
  y_test = y_test[holdout:]
  x_test_1=x_test[:,:1,:,:]

  # Obtain Image parameters
  img_rows, img_cols, nchannels = x_train.shape[1:4]
  nb_classes = y_train.shape[1]

  # Define input TF placeholder
  x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols,
                                        nchannels))
  y = tf.placeholder(tf.float32, shape=(None, nb_classes))

  # Define input TF placeholder for X-sub Vulnerability Analysis 

  x_vul = tf.placeholder(tf.float32, shape=(None, 1, img_cols,
                                        nchannels))

  # Seed random number generator so tutorial is reproducible
  rng = np.random.RandomState([2017, 8, 30])

  # Simulate the black-box model locally
  # You could replace this by a remote labeling API for instance
  print("Preparing the black-box model.")
  prep_bbox_out = prep_bbox(sess, x, y, x_train, y_train, x_test, y_test,
                            nb_epochs, batch_size, learning_rate,
                            rng, nb_classes, img_rows, img_cols, nchannels)
  model, bbox_preds, accuracies['bbox'] = prep_bbox_out

  # Train substitute using method from https://arxiv.org/abs/1602.02697
  print("Training the substitute model.")
  train_sub_out = train_sub(sess, x_vul, y, x_sub_1, y_sub, x_test_1, y_test,
                            nb_epochs, batch_size, learning_rate,
                            rng, nb_classes, 1, img_cols, nchannels)
  model_sub, preds_sub, accuracies['sub'] = train_sub_out

  # Initialize the Fast Gradient Sign Method (FGSM) attack object.
  fgsm_par = {'eps': 0.1, 'ord': np.inf, 'clip_min': 0., 'clip_max': 1.}
  fgsm = FastGradientMethod(model_sub, sess=sess)

  # Craft adversarial examples using the substitute
  eval_params = {'batch_size': batch_size}
  x_adv_sub = fgsm.generate(x_vul, **fgsm_par)

  print(x_adv_sub.get_shape())
  print(type(x_adv_sub))
  x_test_rest=x_test[:,1:,:,:]
  x_test_rest_tf=tf.convert_to_tensor(x_test_rest)
  concat_adv=tf.concat([x_adv_sub, x_test_rest_tf], 1)
  print(concat_adv.get_shape())
  print(type(concat_adv))


  # Evaluate the accuracy of the "black-box" model on adversarial examples
  accuracy = model_eval(sess, x, y, model.get_logits(concat_adv),
                        x_test, y_test, args=eval_params)
  print('Test accuracy of oracle on adversarial examples generated '
        'using the substitute: ' + str(accuracy))
  accuracies['bbox_on_sub_adv_ex'] = accuracy

  return accuracies

此视图称为此表单:

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

这是我编写的几乎没有帮助的jQuery脚本,您可以通过@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) 标签将其包含在HTML中。

<script>

使用jQuery和<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> 方法,我可以向该视图发送请求:

getJSON()

因此,每次进行更改时,都会从站点动态读取数据,将其发送到Flask视图,并在其中进行所需的处理,然后将其发送回相同的@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. 并直接插入表单中! 瞧,您已经动态地使用jQuery + Flask,并使用了您最好的新朋友JSON来进行您想做的事情。

经过大量研究,我发现最简单的方法是通过jQuery完成您想要的操作。 在jQuery中学习getJSON()getJSON()方法,并将其合并到HTML中,您将得到答案。 还有post(),我相信您可以通过jsonify()在Python中导入。

将列表发送到import json,您可以使用该列表作为发送数据的同一jsonify()的视图返回。