如何让 wtforms 获取 json 并将数据插入表单对象?
How to get wtforms to take json and insert data into the form object?
情况:
我在前端使用 React 和 Flask api 服务器。我想将数据从 React 发送到 api,完成后我想在处理数据之前使用 WTForms 对数据进行 运行 验证。这个问题可能看起来类似于 CSRF Protection with Flask/WTForms and React ,但这并没有回答问题,请看一下我已经付出了很多努力来写一个好问题。
我有什么
目前,数据作为 json 对象成功发送,其中键与 wtform 结构中的名称匹配,目的是让 wtforms 获取 json 数据并将其插入进入对象并从那里正常处理
正在发送的JSON对象
{'username': 'tster', 'fullname': 'Tester test', 'phone': '038287827216', 'email': 'test@example.com', 'password': 'Tester1010', 'repeatPassword': 'Tester1010'}
整个概念是从 React 组件发送它并将其添加到 wtforms 以便进行相应的验证和使用。
Python代码:
我尝试了多种不同的方法,这是最新的:
@bp.route('/api/register_user', methods=['POST', 'GET'])
def register():
""" End-point to register users """
if request.method == 'GET':
return ('', {'csrf_token': generate_csrf()})
elif request.method == 'POST':
req = request.get_json(force=True)
if validate_csrf(request.headers['X-Csrftoken']):
print('validated')
return{"message": "posted"}
这个概念是,如果我不能让它与 wtforms 一起工作,我可以手动生成 csrf_token,验证它,一旦验证 运行 手动验证数据而不是依赖在 wtforms 上,但是它似乎没有相应地验证。这取自 https://flask-wtf.readthedocs.io/en/stable/api.html#flask_wtf.csrf.generate_csrf
在此之前我试过:
@bp.route('/api/register_user', methods=['POST', 'GET'])
def register():
""" End-point to register users """
form = RegistrationForm()
print(request.headers)
if request.method == 'GET':
print(form.csrf_token._value())
return ('', {'csrf_token': form.csrf_token._value()})
elif request.method == 'POST':
req = request.get_json(force=True)
form = RegistrationForm.from_json(req)
print(form.username)
return{"message": "posted"}
else:
return { 'errors': form.errors }
这背后的想法是像其他情况一样发送 csrf_token,因为在之前的 link 中我读到:
Generate a CSRF token. The token is cached for a request, so multiple calls to this function will generate the same token.
time_limit -- Number of seconds that the token is valid. Default is WTF_CSRF_TIME_LIMIT or 3600 seconds (60 minutes).
读完这篇文章后,我从逻辑上认为这很有意义,因为我基本上可以从标准表单对象中获取 csrf_token,因为您通常会使用它们进行操作,然后当请求方法是 POST 我可以重新呈现表单并在请求中插入来自 JSON 的数据,我的想法来自 https://wtforms-json.readthedocs.io/en/latest/
然而,这会遇到相同的行为,即无法访问表单对象中的数据,并从以下位置收到多个错误:
print(form.username.data)
AttributeError: 'tuple' object has no attribute 'data'
print(form.username)
(<UnboundField(StringField, ('username',), {'validators': [<wtforms.validators.DataRequired object at 0x000002A3B600A6D0>]})>,)
因此我什至无法访问我插入到表单对象中的数据。 (如果它像我假设的那样插入的话。)
在此之前:
我尝试了使用 wtforms 的标准操作方式:
@bp.route('/api/register_user', methods=['POST', 'GET'])
def register():
""" End-point to register users """
form = RegistrationForm()
print(request.headers)
if request.method == 'GET':
print(form.csrf_token._value())
return ('', {'csrf_token': form.csrf_token._value()})
elif form.validate_on_submit():
req = request.get_json(force=True)
return{"message": "posted"}
else:
return { 'errors': form.errors }
现在它从未通过验证,这很奇怪,因为验证器只是 DataRequired()。
然而,它确实通过了 form.is_submitted() 这样的测试,但我在打印时遇到了同样的错误:
print(form.username.data)
AttributeError: 'tuple' object has no attribute 'data'
print(form.username)
(<UnboundField(StringField, ('username',), {'validators': [<wtforms.validators.DataRequired object at 0x000002A3B600A6D0>]})>,)
此外,反应代码必须稍微改变才能工作,否则它会发回错误的请求,请求必须是:
handleSubmit = (details) => {
const finalSend = {
'csrf_token': this.state.csrf_token,
'username': this.state.usernameInput,
'fullname': this.state.fullnameInput,
'phone': this.state.phoneInput,
'email': this.state.emailInput,
'password': this.state.passwordInput,
'repeatPassword': this.state.repeatPasswordInput
}
axios({
method: 'post',
url: '/api/register_user',
data: finalSend,
headers: {
'content-type': 'application/json'
}
})
.then(res => res.json()).catch(e => console.log(e));
}
问题
所以毕竟我要问的是:
- 如何让 wtforms 将发送的数据作为 JSON 对象?
- 与 WTForms 分开操作会不会更好,如果是的话如何?
- 如果我按照上面的方法设置 CSRF 安全性,就像我最近尝试的那样,但它没有按需要工作。
额外需要:
组件发送的JSON形式为:
{'csrf_token': 'ImQ5MjhlY2VlYzM5Zjg0NmY4ZTg0NDk5ZjNlMjlkNzVlZGM4OGZhY2Ui.YBPbuw.D6BW8XpwEyXySCaBBeS0jIKYabU', 'username': 'charlie', 'fullname': 'charlie char', 'phone': '344444444', 'email': 'cindy@example.com', 'password': 'charlieandcindy', 'repeatPassword': 'charlieandcindy'}
我正在使用的WTForm:
class RegistrationForm(FlaskForm):
username = StringField('username', validators=[DataRequired()]),
fullname = StringField('fullname', validators=[DataRequired()]),
email = BooleanField('email', validators=[DataRequired(), Email()]),
phone = StringField('phone', validators=[DataRequired()]),
password = StringField('password', validators=[DataRequired()]),
repeatPassword = StringField('repeatPassword', validators=[DataRequired(), EqualTo('password')])
def validate_username(self, username):
print("validating username")
我也参考了以下网站寻找解决方案:
https://wtforms.readthedocs.io/en/2.3.x/forms/
https://wtforms-json.readthedocs.io/en/latest/
Flask-WTF set time limit on CSRF token
https://flask-wtf.readthedocs.io/en/stable/api.html#flask_wtf.csrf.generate_csrf
How do I get the information from a meta tag with JavaScript?
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#uploading_json_data
https://flask-wtf.readthedocs.io/en/stable/csrf.html#javascript-requests
CSRF Protection with Flask/WTForms and React
再一次,我尽我最大的努力找到了解决方案,并付出了很多努力来写一个好问题,如果你需要更多,请尽管问。
编辑
我在这方面做了更多工作,我又尝试了 wtforms_json 一些,让它稍微填充了一些,但它只填充了 repeatPassword 字段,我不确定为什么。
为了做到这一点,我不得不关闭 csrf 验证并在路由中使用以下代码:
@bp.route('/api/register_user', methods=['POST'])
def register():
""" End-point to register users """
json = request.get_json()
form = RegistrationForm.from_json(json)
print(request.get_json())
print(form.data)
if form.validate():
print("validated")
return{"message": "posted"}
else:
return { 'errors': form.errors }
打印 form.data 时的结果是设置了 repeatPassword 但没有别的,也不知道为什么......这个进步的来源在这里
我也找到了答案
为了做到这一点,我最终使用了 json 中的 wtforms_json 方法,如下所示:
@bp.route('/api/register_user', methods=['GET','POST'])
def register():
""" End-point to register users """
reg_json = request.get_json()
form = RegistrationForm.from_json(reg_json)
if form.validate():
user = Users(
username = form.username.data,
name = form.fullname.data,
phone = form.phone.data,
email = form.email.data,
password = guard.hash_password(form.password.data),
roles = 'User'
)
user.save()
return{"message": "inserted successfully"}
else:
return { 'errors': form.errors }
以及不得不调整表格:
class RegistrationForm(FlaskForm):
username = StringField('username', validators=[DataRequired()])
fullname = StringField('fullname', validators=[DataRequired()])
email = StringField('email', validators=[DataRequired()])
phone = StringField('phone', validators=[DataRequired()])
password = StringField('password', validators=[DataRequired()])
repeatPassword = StringField('repeatPassword', validators=[DataRequired(), EqualTo('password')])
def validate_username(self, username):
""" Ensure email isn't in use """
user = Users.objects(username=username.data).first()
if user != None:
raise ValidationError('Please use a different username.')
唯一的缺点是我不得不关闭配置中的 csrf_token:
WTF_CSRF_ENABLED = False
我实际上尝试了您的解决方案,但没有意识到您使用了 wtforms_json
扩展。我想看看是否可以找到解决方案而无需添加额外的烧瓶扩展。
我的版本:
from werkzeug.datastructures import ImmutableMultiDict
@bp.route('/api/register_user', methods=['GET','POST'])
def register():
""" End-point to register users """
reg_json = request.get_json()
form_input = ImmutableMultiDict(reg_json)
form = RegistrationForm(form_input)
if form.validate():
user = Users(
username = form.username.data,
name = form.fullname.data,
phone = form.phone.data,
email = form.email.data,
password = guard.hash_password(form.password.data),
roles = 'User'
)
user.save()
return{"message": "inserted successfully"}
else:
return { 'errors': form.errors }
分解:
我找到了一些使用 flask-login
的示例,其中客户端将表单数据作为表单而不是 JSON 数据发送。代码看起来像这样:
form = RegistrationForm(request.form)
我继续深入研究源代码,发现 request.form
输入(进入 RegistrationForm
构造函数)的数据结构是 ImmutableMultiDict
.
https://pythonise.com/series/learning-flask/the-flask-request-object(向下滚动到查询字符串)
ImmutableMultiDict
可以在安装 flask
后方便安装的 werkzeug
扩展中找到。所以基本上,我通过从请求传递数据创建了一个 ImmutableMultiDict
对象:
reg_json = request.get_json()
...
form_input = ImmutableMultiDict(reg_json)
并将其用作 RegistrationForm
的输入:
form = RegistrationForm(form_input)
现在您可以通过 POST
请求接收来自不同客户端的数据,例如使用 React 开发的前端。您也不必担心保留 up-to-date 过时的扩展名。
希望对您有所帮助!
干杯。
情况:
我在前端使用 React 和 Flask api 服务器。我想将数据从 React 发送到 api,完成后我想在处理数据之前使用 WTForms 对数据进行 运行 验证。这个问题可能看起来类似于 CSRF Protection with Flask/WTForms and React ,但这并没有回答问题,请看一下我已经付出了很多努力来写一个好问题。
我有什么
目前,数据作为 json 对象成功发送,其中键与 wtform 结构中的名称匹配,目的是让 wtforms 获取 json 数据并将其插入进入对象并从那里正常处理
正在发送的JSON对象
{'username': 'tster', 'fullname': 'Tester test', 'phone': '038287827216', 'email': 'test@example.com', 'password': 'Tester1010', 'repeatPassword': 'Tester1010'}
整个概念是从 React 组件发送它并将其添加到 wtforms 以便进行相应的验证和使用。
Python代码:
我尝试了多种不同的方法,这是最新的:
@bp.route('/api/register_user', methods=['POST', 'GET'])
def register():
""" End-point to register users """
if request.method == 'GET':
return ('', {'csrf_token': generate_csrf()})
elif request.method == 'POST':
req = request.get_json(force=True)
if validate_csrf(request.headers['X-Csrftoken']):
print('validated')
return{"message": "posted"}
这个概念是,如果我不能让它与 wtforms 一起工作,我可以手动生成 csrf_token,验证它,一旦验证 运行 手动验证数据而不是依赖在 wtforms 上,但是它似乎没有相应地验证。这取自 https://flask-wtf.readthedocs.io/en/stable/api.html#flask_wtf.csrf.generate_csrf
在此之前我试过:
@bp.route('/api/register_user', methods=['POST', 'GET'])
def register():
""" End-point to register users """
form = RegistrationForm()
print(request.headers)
if request.method == 'GET':
print(form.csrf_token._value())
return ('', {'csrf_token': form.csrf_token._value()})
elif request.method == 'POST':
req = request.get_json(force=True)
form = RegistrationForm.from_json(req)
print(form.username)
return{"message": "posted"}
else:
return { 'errors': form.errors }
这背后的想法是像其他情况一样发送 csrf_token,因为在之前的 link 中我读到:
Generate a CSRF token. The token is cached for a request, so multiple calls to this function will generate the same token.
time_limit -- Number of seconds that the token is valid. Default is WTF_CSRF_TIME_LIMIT or 3600 seconds (60 minutes).
读完这篇文章后,我从逻辑上认为这很有意义,因为我基本上可以从标准表单对象中获取 csrf_token,因为您通常会使用它们进行操作,然后当请求方法是 POST 我可以重新呈现表单并在请求中插入来自 JSON 的数据,我的想法来自 https://wtforms-json.readthedocs.io/en/latest/
然而,这会遇到相同的行为,即无法访问表单对象中的数据,并从以下位置收到多个错误:
print(form.username.data)
AttributeError: 'tuple' object has no attribute 'data'
print(form.username)
(<UnboundField(StringField, ('username',), {'validators': [<wtforms.validators.DataRequired object at 0x000002A3B600A6D0>]})>,)
因此我什至无法访问我插入到表单对象中的数据。 (如果它像我假设的那样插入的话。)
在此之前:
我尝试了使用 wtforms 的标准操作方式:
@bp.route('/api/register_user', methods=['POST', 'GET'])
def register():
""" End-point to register users """
form = RegistrationForm()
print(request.headers)
if request.method == 'GET':
print(form.csrf_token._value())
return ('', {'csrf_token': form.csrf_token._value()})
elif form.validate_on_submit():
req = request.get_json(force=True)
return{"message": "posted"}
else:
return { 'errors': form.errors }
现在它从未通过验证,这很奇怪,因为验证器只是 DataRequired()。 然而,它确实通过了 form.is_submitted() 这样的测试,但我在打印时遇到了同样的错误:
print(form.username.data)
AttributeError: 'tuple' object has no attribute 'data'
print(form.username)
(<UnboundField(StringField, ('username',), {'validators': [<wtforms.validators.DataRequired object at 0x000002A3B600A6D0>]})>,)
此外,反应代码必须稍微改变才能工作,否则它会发回错误的请求,请求必须是:
handleSubmit = (details) => {
const finalSend = {
'csrf_token': this.state.csrf_token,
'username': this.state.usernameInput,
'fullname': this.state.fullnameInput,
'phone': this.state.phoneInput,
'email': this.state.emailInput,
'password': this.state.passwordInput,
'repeatPassword': this.state.repeatPasswordInput
}
axios({
method: 'post',
url: '/api/register_user',
data: finalSend,
headers: {
'content-type': 'application/json'
}
})
.then(res => res.json()).catch(e => console.log(e));
}
问题
所以毕竟我要问的是:
- 如何让 wtforms 将发送的数据作为 JSON 对象?
- 与 WTForms 分开操作会不会更好,如果是的话如何?
- 如果我按照上面的方法设置 CSRF 安全性,就像我最近尝试的那样,但它没有按需要工作。
额外需要:
组件发送的JSON形式为:
{'csrf_token': 'ImQ5MjhlY2VlYzM5Zjg0NmY4ZTg0NDk5ZjNlMjlkNzVlZGM4OGZhY2Ui.YBPbuw.D6BW8XpwEyXySCaBBeS0jIKYabU', 'username': 'charlie', 'fullname': 'charlie char', 'phone': '344444444', 'email': 'cindy@example.com', 'password': 'charlieandcindy', 'repeatPassword': 'charlieandcindy'}
我正在使用的WTForm:
class RegistrationForm(FlaskForm):
username = StringField('username', validators=[DataRequired()]),
fullname = StringField('fullname', validators=[DataRequired()]),
email = BooleanField('email', validators=[DataRequired(), Email()]),
phone = StringField('phone', validators=[DataRequired()]),
password = StringField('password', validators=[DataRequired()]),
repeatPassword = StringField('repeatPassword', validators=[DataRequired(), EqualTo('password')])
def validate_username(self, username):
print("validating username")
我也参考了以下网站寻找解决方案:
再一次,我尽我最大的努力找到了解决方案,并付出了很多努力来写一个好问题,如果你需要更多,请尽管问。
编辑
我在这方面做了更多工作,我又尝试了 wtforms_json 一些,让它稍微填充了一些,但它只填充了 repeatPassword 字段,我不确定为什么。
为了做到这一点,我不得不关闭 csrf 验证并在路由中使用以下代码:
@bp.route('/api/register_user', methods=['POST'])
def register():
""" End-point to register users """
json = request.get_json()
form = RegistrationForm.from_json(json)
print(request.get_json())
print(form.data)
if form.validate():
print("validated")
return{"message": "posted"}
else:
return { 'errors': form.errors }
打印 form.data 时的结果是设置了 repeatPassword 但没有别的,也不知道为什么......这个进步的来源在这里
我也找到了答案
为了做到这一点,我最终使用了 json 中的 wtforms_json 方法,如下所示:
@bp.route('/api/register_user', methods=['GET','POST'])
def register():
""" End-point to register users """
reg_json = request.get_json()
form = RegistrationForm.from_json(reg_json)
if form.validate():
user = Users(
username = form.username.data,
name = form.fullname.data,
phone = form.phone.data,
email = form.email.data,
password = guard.hash_password(form.password.data),
roles = 'User'
)
user.save()
return{"message": "inserted successfully"}
else:
return { 'errors': form.errors }
以及不得不调整表格:
class RegistrationForm(FlaskForm):
username = StringField('username', validators=[DataRequired()])
fullname = StringField('fullname', validators=[DataRequired()])
email = StringField('email', validators=[DataRequired()])
phone = StringField('phone', validators=[DataRequired()])
password = StringField('password', validators=[DataRequired()])
repeatPassword = StringField('repeatPassword', validators=[DataRequired(), EqualTo('password')])
def validate_username(self, username):
""" Ensure email isn't in use """
user = Users.objects(username=username.data).first()
if user != None:
raise ValidationError('Please use a different username.')
唯一的缺点是我不得不关闭配置中的 csrf_token:
WTF_CSRF_ENABLED = False
我实际上尝试了您的解决方案,但没有意识到您使用了 wtforms_json
扩展。我想看看是否可以找到解决方案而无需添加额外的烧瓶扩展。
我的版本:
from werkzeug.datastructures import ImmutableMultiDict
@bp.route('/api/register_user', methods=['GET','POST'])
def register():
""" End-point to register users """
reg_json = request.get_json()
form_input = ImmutableMultiDict(reg_json)
form = RegistrationForm(form_input)
if form.validate():
user = Users(
username = form.username.data,
name = form.fullname.data,
phone = form.phone.data,
email = form.email.data,
password = guard.hash_password(form.password.data),
roles = 'User'
)
user.save()
return{"message": "inserted successfully"}
else:
return { 'errors': form.errors }
分解:
我找到了一些使用 flask-login
的示例,其中客户端将表单数据作为表单而不是 JSON 数据发送。代码看起来像这样:
form = RegistrationForm(request.form)
我继续深入研究源代码,发现 request.form
输入(进入 RegistrationForm
构造函数)的数据结构是 ImmutableMultiDict
.
https://pythonise.com/series/learning-flask/the-flask-request-object(向下滚动到查询字符串)
ImmutableMultiDict
可以在安装 flask
后方便安装的 werkzeug
扩展中找到。所以基本上,我通过从请求传递数据创建了一个 ImmutableMultiDict
对象:
reg_json = request.get_json()
...
form_input = ImmutableMultiDict(reg_json)
并将其用作 RegistrationForm
的输入:
form = RegistrationForm(form_input)
现在您可以通过 POST
请求接收来自不同客户端的数据,例如使用 React 开发的前端。您也不必担心保留 up-to-date 过时的扩展名。
希望对您有所帮助!
干杯。