简体   繁体   English

如何让 wtforms 获取 json 并将数据插入到表单 object 中?

[英]How to get wtforms to take json and insert data into the form object?

The situation:情况:

I am using React in the front-end and a Flask api server.我在前端使用 React 和 Flask api 服务器。 I am wanting to send the data from React to the api and once I have done this I would like to use WTForms to run validations on the data before handling it.我想将数据从 React 发送到 api,完成后我想使用 WTForms 在处理数据之前对数据运行验证。 The question may seem similar to CSRF Protection with Flask/WTForms and React , but this does not answer the question, please take a look through I have put a lot of effort in writing a good question.这个问题可能看起来类似于CSRF Protection with Flask/WTForms and React ,但这并没有回答问题,请看一下我已经付出了很多努力来写一个好问题。

What I have我有的

Currently the data is being sent successfully as a json object, where the keys match the names within the wtform structure, the aim is to get wtforms to take that json data and insert it into the object and and handle from there as normal目前,数据作为 json object 成功发送,其中键与 wtform 结构中的名称匹配,目的是让 wtforms 获取该 json 数据并将其插入 object 并从那里正常处理

The JSON object being sent JSON object 正在发送

{'username': 'tster', 'fullname': 'Tester test', 'phone': '038287827216', 'email': 'test@example.com', 'password': 'Tester1010', 'repeatPassword': 'Tester1010'}

The entire concept is to sent this from the React component and add it to wtforms in order to validate accordingly and work with.整个概念是从 React 组件发送它并将其添加到 wtforms 以便进行相应的验证和使用。

Python code: Python 代码:

I have tried multple different approaches, this being the latest:我尝试了多种不同的方法,这是最新的:


    @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"}

The concept was that if I couldn't get it to work with wtforms I could generate the csrf_token manually, validate it and once thats validated run manual validations against the data instead of relying on wtforms, however it doesn't seem to validate accordingly.这个概念是,如果我不能让它与 wtforms 一起工作,我可以手动生成 csrf_token,验证它,一旦验证通过,就对数据运行手动验证,而不是依赖 wtforms,但是它似乎没有相应地验证。 This taken from https://flask-wtf.readthedocs.io/en/stable/api.html#flask_wtf.csrf.generate_csrf这取自https://flask-wtf.readthedocs.io/en/stable/api.html#flask_wtf.csrf.generate_csrf

Prior to this I tried:在此之前我试过:


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

The idea behind this was to send the csrf_token as with other cases, due to the fact that in the prior link I read:这背后的想法是像其他情况一样发送 csrf_token,因为在之前的链接中我读到:

Generate a CSRF token.生成 CSRF 令牌。 The token is cached for a request, so multiple calls to this function will generate the same token.令牌是为请求缓存的,因此多次调用此 function 将生成相同的令牌。

time_limit -- Number of seconds that the token is valid. time_limit -- 令牌有效的秒数。 Default is WTF_CSRF_TIME_LIMIT or 3600 seconds (60 minutes).默认值为 WTF_CSRF_TIME_LIMIT 或 3600 秒(60 分钟)。

After reading this I logically thought that this would make sense then that I could essentially get the csrf_token from a standard form object, as you would normally operate with them and then when the request method is POST I could rerender the form and insert the data from the JSON within the request, I got the idea from https://wtforms-json.readthedocs.io/en/latest/读完这篇文章后,我从逻辑上认为这是有道理的,因为我基本上可以从标准表单 object 中获取 csrf_token,因为您通常会使用它们进行操作,然后当请求方法为 POST 时,我可以重新呈现表单并从中插入数据请求中的 JSON,我从https://wtforms-json.readthedocs.io/en/latest/得到了这个想法

This however is met with the same behavior of not being able to access the data in the form object receiving multiple errors from:然而,这会遇到相同的行为,即无法访问 object 形式的数据,从以下位置接收到多个错误:

print(form.username.data) AttributeError: 'tuple' object has no attribute 'data' print(form.username.data) AttributeError: 'tuple' object 没有属性 'data'

print(form.username) (<UnboundField(StringField, ('username',), {'validators': [<wtforms.validators.DataRequired object at 0x000002A3B600A6D0>]})>,) print(form.username) (<UnboundField(StringField, ('username',), {'validators': [<wtforms.validators.DataRequired object at 0x000002A3B600A6D0>]})>,)

Thus I couldn't even access the data I was inserting into the form object. (If it even inserted as I assummed it would.)因此,我什至无法访问我插入到表单 object 中的数据。(如果它像我假设的那样插入的话。)

Prior to this:在此之前:

I tried the standard way of operating with wtforms:我尝试了使用 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 }

Now it never got past validation, which is strange because the validators are simply DataRequired().现在它从未通过验证,这很奇怪,因为验证器只是 DataRequired()。 However it did get past the form.is_submitted() when tested as such, but I got the same errors when printing:然而,当这样测试时它确实通过了 form.is_submitted() ,但我在打印时遇到了同样的错误:

print(form.username.data) AttributeError: 'tuple' object has no attribute 'data' print(form.username.data) AttributeError: 'tuple' object 没有属性 'data'

print(form.username) (<UnboundField(StringField, ('username',), {'validators': [<wtforms.validators.DataRequired object at 0x000002A3B600A6D0>]})>,) print(form.username) (<UnboundField(StringField, ('username',), {'validators': [<wtforms.validators.DataRequired object at 0x000002A3B600A6D0>]})>,)

Also with this the react code had to change slightly in order to work, or else it send back bad request the request had to be:同样,反应代码必须稍微改变才能工作,否则它会发回错误的请求,请求必须是:


     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));
        }

The Question问题

So after all this what I am basically asking is:所以毕竟我基本上要问的是:

  • How can I get wtforms to take the data sent as a JSON object?如何让 wtforms 将发送的数据作为 JSON object?
  • Would it be better to operate separately from WTForms, if so how?与 WTForms 分开操作会更好吗?如果是的话,如何操作?
  • If I do the above how can I set up CSRF security, as I tried recently, and it isn't working as needed.如果我执行上述操作,我该如何设置 CSRF 安全性,就像我最近尝试的那样,但它没有按需要工作。

Extra Needed:额外需要:

The JSON that is sent by the component is in the form of:组件发送的JSON形式为:


    {'csrf_token': 'ImQ5MjhlY2VlYzM5Zjg0NmY4ZTg0NDk5ZjNlMjlkNzVlZGM4OGZhY2Ui.YBPbuw.D6BW8XpwEyXySCaBBeS0jIKYabU', 'username': 'charlie', 'fullname': 'charlie char', 'phone': '344444444', 'email': 'cindy@example.com', 'password': 'charlieandcindy', 'repeatPassword': 'charlieandcindy'}

The WTForm that I am using:我正在使用的 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")

I have also consulted the following websites looking for a solution: Flask wtforms - 'UnboundField' object is not callable, dynamic field won't init properly 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?我还咨询了以下网站寻找解决方案: Flask wtforms - 'UnboundField' object 不可调用,动态字段无法正确初始化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 怎么做我从带有 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 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 保护使用 Flask/WTForms 和 React

Once more I have tried my best finding the solution and put a lot of effort into writing a good question, if you need any more just ask.我再一次尽我最大的努力找到解决方案,并付出了很多努力来写一个好问题,如果你需要更多,请问。

EDIT编辑

Working on this some more I have played around with wtforms_json some more and have got it to poulate slightly, however it only populates the repeatPassword field, which I am unsure why.在这方面做了更多工作,我又尝试了一些 wtforms_json 并让它稍微填充,但它只填充 repeatPassword 字段,我不确定为什么。

In order to do this I have had to turn off csrf validation and using the following code in the route:为此,我不得不关闭 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 }

The result when printing form.data is coming with the repeatPassword being set but nothing else and not sure why... source to this advancement is here WTForms-JSON not working with FormFields打印 form.data 时的结果是设置了 repeatPassword 但没有别的,也不知道为什么......这个进步的来源在这里WTForms-JSON not working with FormFields

I actually tried out your solution without realizing that you had used the wtforms_json extension.实际上,我在没有意识到您使用了wtforms_json扩展的情况下尝试了您的解决方案。 I wanted to see if I could find a solution without having to add an extra flask extension.我想看看是否可以找到解决方案而无需添加额外的 flask 扩展名。

My version:我的版本:

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 }

Breaking it down:打破它:

I found some examples of using flask-login where clients send form data as a form rather than JSON data.我发现了一些使用flask-login的示例,其中客户端将表单数据作为表单而不是 JSON 数据发送。 The code looked something like this:代码看起来像这样:

form = RegistrationForm(request.form)

I continued to dig into the source code and realized that the data structure for the request.form input (into the RegistrationForm constructor) is ImmutableMultiDict .我继续深入研究源代码并意识到request.form输入(到RegistrationForm构造函数中)的数据结构是ImmutableMultiDict

https://pythonise.com/series/learning-flask/the-flask-request-object (Scroll down to query strings) https://pythonise.com/series/learning-flask/the-flask-request-object (向下滚动到查询字符串)

ImmutableMultiDict can be found within werkzeug extension that is conveniently installed as a result of installing flask . ImmutableMultiDict可以在werkzeug扩展中找到,该扩展可以通过安装flask方便地安装。 So basically, I created a ImmutableMultiDict object by passing data from the request:所以基本上,我通过从请求中传递数据创建了一个ImmutableMultiDict object:

reg_json = request.get_json()
...
form_input = ImmutableMultiDict(reg_json)

And used that as the input to RegistrationForm :并将其用作RegistrationForm的输入:

form = RegistrationForm(form_input)

Now you can receive JSON data from different clients such as a frontend developed with React via POST requests.现在您可以通过POST请求从不同的客户端(例如使用 React 开发的前端)接收 JSON 数据。 You also don't have to worry about keeping up-to-date with outdated extensions.您也不必担心与过时的扩展保持同步。

Hope this helps!希望这可以帮助!

Cheers.干杯。

I found the answer too this.我也找到了答案。

In order to do this I ended up using the wtforms_json from json methodas below:为了做到这一点,我最终使用了 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 }

As well as had to adjust the forms:以及不得不调整forms:


    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.')

The only downside to this is I had to turn off the csrf_token within the config with:唯一的缺点是我不得不关闭配置中的 csrf_token:

WTF_CSRF_ENABLED = False

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM