validate_on_submit() 在接收带有 AJAX 的 jQuery 扩展表单时失败

validate_on_submit() failing when receiving jQuery extended form with AJAX

我想做什么

我有一个具有部分动态功能的静态来源,您可以在其中添加更多 <input> 字段以在单击按钮时添加标签。

问题

我的表单对所有静态表单字段和 <input> 字段使用 WTForms 模板,根据此 great answer from nsfyn55 使用 jQuery 生成,以添加更多标签。

前端运行良好,但我在 python 的验证中卡住了,其中 validate_on_submit() 由于未知原因不断失败。

我怀疑它与模板和 jQuery 生成的 <input> 字段的混合使用有关,以某种方式破坏了我的验证。 另一个原因可能是我,不理解 how to use AJAX with flask properly 并且以某种方式错误处理 AJAX POST.

MVCE:

app.py

@app.route(BASEURL + '/new', methods=['GET', 'POST'])
def new():

    form = Form()
    global metadata
    data = dict()

    if form.validate_on_submit():
        keyword1 = request.form['keyword-1']
        keyword2 = request.form['keyword-2']
        keyword3 = request.form['keyword-3']

        keywords = []

        if keyword1: keywords.append(keyword1)
        if keyword2: keywords.append(keyword2)
        if keyword3: keywords.append(keyword3)

        data.update({
            'given_name': form.abstract.data,
            'family_name': form.description.data,

            'keywords': keywords,
        })
        filename = 'data.json'
        with open('data/' + filename, 'w') as file:
            file.write(json.dumps(metadata, indent=4, sort_keys=False))


class Form(FlaskForm):

    given_name = StringField()
    family_name = StringField()

new.html

<html>
<head>
    <title>New</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>

    <script src="{{ url_for('static', filename='js/form.js') }}"></script>

</head>
<body>
<form  method='POST' action='{{ url_for('new') }}'>
    {{ form.csrf_token }}

    {% from "_formhelpers.html" import render_field %}
      <dl>
            {{ render_field(form.given_name) }}

            {{ render_field(form.family_name) }}

            <div id="keywordsTest"></div>

            <button onclick="keywordField()">+</button>


      </dl>
    <input type="submit" value="Submit">
</form>




<script>
    var index = 0;
    function keywordField(){
        if(index<3){
            index+=1;

            $('<input>').attr({
                type: 'text',
                id: 'keyword-' + index ,
                name: 'keyword-' + index,
                placeholder: 'keyword' + index
            }).appendTo('#keywordsTest');
        }
        return false
    }
    $(keywordField)
</script>
</body>
</html>

form.js

$(document).ready(function() {
    $('form').on('submit', function(event) {
        $.ajax({
            data : {
                keyword1 : $('#keyword-1').val(),
                keyword2 : $('#keyword-2').val(),
                keyword3 : $('#keyword-3').val()
            },
            type : 'POST',
            url : '/register/new'
        });
        event.preventDefault();
    });
});

@ADyson 的评论解释了您遇到的一个具体问题,即您真的应该将代码更改为:

$.ajax({
        data : {
            "keyword-1" : $('#keyword-1').val(),
            "keyword-2" : $('#keyword-2').val(),
            "keyword-3" : $('#keyword-3').val()
        },

但这并不能真正解决你的核心问题。您正在使用 WTForms,大概是因为您想使用其固有的服务器端表单验证库。目前您的 Form class 未执行任何验证,因此行 form.validate_on_submit() 将不执行任何操作。插入以下内容:

from wtforms.validators import InputRequired
...
given_name = StringField(validators=[InputRequired()])

至少现在您可以测试您的代码,以便它尝试执行一些基本的验证服务。

但是这里还有一个问题。您的 Form class 需要处理两个表单域; given_namefamily_name,但是您通过 AJAX POST 的数据不包含这两个字段中的任何一个,实际上您发布的数据指定为:

    data : {
            "keyword-1" : $('#keyword-1').val(),
            "keyword-2" : $('#keyword-2').val(),
            "keyword-3" : $('#keyword-3').val()
        },

就是这样 - 您不会自动发送其他表单字段 HTML 因为您已经直接明确地在此处指定了数据。

在其 core 级别 Flask 路由接收一个名为 request 的对象。如果插入行

def new():
    print("the data supplied in post request form is: ", request.form)

然后你可以调试你在传输数据中看到的内容。此请求将发生的情况是 form.validate_on_submit() 将失败并显示错误 {'given_name: ['This field is required.']}。即使您在表单中提供此字段,它也会出错,因为您没有明确传递它。当您执行 form = Form() 时,form 会填充来自 request.

的数据

修复此问题后,您的代码也会受到 KeyError 的影响,因为如果某些字段是可选的,或者用户没有添加第二个或第三个可选输入字段,则:

keyword2 = request.form['keyword-2']

将不存在,所以请尝试

keyword2 = request.form.get('keyword-2', None)

因为至少有一个免责条款。

我很感激您以前不一定想使用我建议的方法来使用 Webargs 而不是 WTForms,但是在您的示例中,包含 WTForms 绝对没有任何用处(除了 HTML 呈现客户端可能).当您访问提交的值时,您是在 request.form 中直接访问它们,这完全绕过了服务器端验证并使 form=Form() 完全多余。

简单的解决方案

如果您知道您最多希望获得 3 个关键字,那么您可以使用隐藏字段预填充您的表单:

class Form(FlaskForm):
    given_name = StringField(validators=[InputRequired()], render_kw={'placeholder': 'Given Name'})
    family_name = StringField(render_kw={'placeholder': 'Surname'})
    keyword1 = StringField(validators=[Optional()], render_kw={'placeholder': 'k1'})
    keyword2 = StringField(validators=[Optional()], render_kw={'style': 'display:none;', 'placeholder': 'k2'})
    keyword3 = StringField(validators=[Optional()], render_kw={'style': 'display:none;', 'placeholder': 'k3'})

在您的 HTML 上显示如下表格:

{{ form.given_name }}
{{ form.family_name }}
{{ form.keyword1 }} 
{{ form.keyword2 }} 
{{ form.keyword3 }}

因为 render_kw 只有 Keyword1 是可见的,但是你可以很容易地编写一些 JS 代码来点击一个按钮,并将 Keyword2 和 Keyword3 上的显示从 none 更改为 属性,类似于您在上面所做的,虽然不那么简单,因为它只需要 getElementById 并设置样式 属性.

submit被点击时你不需要拦截它并执行AJAX查询(意思是'form.js'可以完全删除),你可以POST 作为常规形式的动作。 WTForms 将根据您的 class 对其进行验证并将数据填充为 form.keyword2.data