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_name
和 family_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
等
我想做什么
我有一个具有部分动态功能的静态来源,您可以在其中添加更多 <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_name
和 family_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
等