miguelgrinberg / turbo-flask

Integration of Hotwire's Turbo library with Flask.
MIT License
301 stars 35 forks source link

Cannot generate form with Flask-WTForms in turbo post [somewhat SOLVED] #30

Closed janpeterka closed 2 years ago

janpeterka commented 2 years ago

I somewhat solved this while writing this issue, but it seems like possibly it can be kept here for anyone stumbling upon this error. Also, there's error in how turbo fetch request sends JSON, so that's worth looking into.


So, I'm using turbo-flask for some time, but it seems like I never tried this:

# app/controllers/comments.py

@route("show_new/<object_class>/<object_id>", methods=["POST"])
def show_new(self, object_class, object_id):
    self.form = CommentsForm()

This method is accessed using this form: Screenshot from 2022-04-16 20-14-32

And this is response I get:

Screenshot from 2022-04-16 20-17-43 Screenshot from 2022-04-16 20-16-52 Screenshot from 2022-04-16 20-17-08

After some trying, I was fairly confident problem is somewhere in Form contructor. I'm using Flask-WTF, looking into sourcecode it looks (line 62) it may try to do request.get_json(), and that's what fails. This helps me to bypass this:

Explicitly pass formdata=None to prevent this.

So, I do that (self.form = CommentsForm(formdata=None)), and I'm able to use form!

Still, it seems like request created by turbo has some invalid JSON, which it shouldn't have. I guess thats problem with turbo.js, not turbo-flask. But I didn't find anything on this, so I would like to consult it with you.

Thanks!


here's request.__dict__:

{'method': 'POST', 'scheme': 'http', 'server': ('127.0.0.1', 5000), 'root_path': '', 'path': '/comments/show_new/Reaction/258', 'query_string': b'', 'headers': EnvironHeaders([('Host', '127.0.0.1:5000'), ('Connection', 'keep-alive'), ('Content-Length', '0'), ('Sec-Ch-Ua', '"Chromium";v="100", " Not A;Brand";v="99"'), ('Accept', 'text/vnd.turbo-stream.html, text/html, application/xhtml+xml'), ('Dnt', '1'), ('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8'), ('Sec-Ch-Ua-Mobile', '?0'), ('User-Agent', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.81 Safari/537.36'), ('Sec-Ch-Ua-Platform', '"Linux"'), ('Origin', 'http://127.0.0.1:5000'), ('Sec-Fetch-Site', 'same-origin'), ('Sec-Fetch-Mode', 'cors'), ('Sec-Fetch-Dest', 'empty'), ('Referer', 'http://127.0.0.1:5000/information/show/278/'), ('Accept-Encoding', 'gzip, deflate, br'), ('Accept-Language', 'en-GB,en-US;q=0.9,en;q=0.8,cs;q=0.7'), ('Cookie', '_pk_id.4.dc78=143222b16f74bd76.1647428499.; session=.eJwlj82KwzAMhF8l-FyKbflH7lPsfSlBtqU2bEmKnZxK333d3ZMYZjT69FKzPKjfuavL90tN-xhqWWVTJ_X1YOo8PbbbtKzTvk1UCvc-7felT0-68Vld39fTaGjc7-qyt4OHWqq6qMJFe9GpekhFE2UsDsTkECtabYBrxBAqG9TWae1zooAAyICaSQwEIhQBFl0S5ArsyQlVl0lnkzIwRQ6YNIK1ELR2OfmQ7XAQDdiBPx-d2z9NFWOCLVU8WudBMGfIMXuyuTiMn3TpTeZ9--F15KGUCGy56jjqbPxgChcIiYQiejdwxDF_9o7WeN3nW9uO5981c1LS51JGT-dd_akn0TCC18Y6cHgePwcH_v0LQldwUg.YlrmPA.e8TcSJ4AUZxhZuKxcna0gZzuPHE; _pk_ses.4.dc78=1')]), 'remote_addr': '127.0.0.1', 'environ': {'wsgi.version': (1, 0), 'wsgi.url_scheme': 'http', 'wsgi.input': <_io.BufferedReader name=4>, 'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>, 'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'werkzeug.socket': <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 5000), raddr=('127.0.0.1', 37120)>, 'SERVER_SOFTWARE': 'Werkzeug/2.1.0', 'REQUEST_METHOD': 'POST', 'SCRIPT_NAME': '', 'PATH_INFO': '/comments/show_new/Reaction/258', 'QUERY_STRING': '', 'REQUEST_URI': '/comments/show_new/Reaction/258', 'RAW_URI': '/comments/show_new/Reaction/258', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': 37120, 'SERVER_NAME': '127.0.0.1', 'SERVER_PORT': '5000', 'SERVER_PROTOCOL': 'HTTP/1.1', 'HTTP_HOST': '127.0.0.1:5000', 'HTTP_CONNECTION': 'keep-alive', 'CONTENT_LENGTH': '0', 'HTTP_SEC_CH_UA': '"Chromium";v="100", " Not A;Brand";v="99"', 'HTTP_ACCEPT': 'text/vnd.turbo-stream.html, text/html, application/xhtml+xml', 'HTTP_DNT': '1', 'CONTENT_TYPE': 'application/x-www-form-urlencoded;charset=UTF-8', 'HTTP_SEC_CH_UA_MOBILE': '?0', 'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.81 Safari/537.36', 'HTTP_SEC_CH_UA_PLATFORM': '"Linux"', 'HTTP_ORIGIN': 'http://127.0.0.1:5000', 'HTTP_SEC_FETCH_SITE': 'same-origin', 'HTTP_SEC_FETCH_MODE': 'cors', 'HTTP_SEC_FETCH_DEST': 'empty', 'HTTP_REFERER': 'http://127.0.0.1:5000/information/show/278/', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_ACCEPT_LANGUAGE': 'en-GB,en-US;q=0.9,en;q=0.8,cs;q=0.7', 'HTTP_COOKIE': '_pk_id.4.dc78=143222b16f74bd76.1647428499.; session=.eJwlj82KwzAMhF8l-FyKbflH7lPsfSlBtqU2bEmKnZxK333d3ZMYZjT69FKzPKjfuavL90tN-xhqWWVTJ_X1YOo8PbbbtKzTvk1UCvc-7felT0-68Vld39fTaGjc7-qyt4OHWqq6qMJFe9GpekhFE2UsDsTkECtabYBrxBAqG9TWae1zooAAyICaSQwEIhQBFl0S5ArsyQlVl0lnkzIwRQ6YNIK1ELR2OfmQ7XAQDdiBPx-d2z9NFWOCLVU8WudBMGfIMXuyuTiMn3TpTeZ9--F15KGUCGy56jjqbPxgChcIiYQiejdwxDF_9o7WeN3nW9uO5981c1LS51JGT-dd_akn0TCC18Y6cHgePwcH_v0LQldwUg.YlrmPA.e8TcSJ4AUZxhZuKxcna0gZzuPHE; _pk_ses.4.dc78=1', 'werkzeug.request': <Request 'http://127.0.0.1:5000/comments/show_new/Reaction/258' [POST]>}, 'shallow': False, 'cookies': ImmutableMultiDict([('_pk_id.4.dc78', '143222b16f74bd76.1647428499.'), ('session', '.eJwlj82KwzAMhF8l-FyKbflH7lPsfSlBtqU2bEmKnZxK333d3ZMYZjT69FKzPKjfuavL90tN-xhqWWVTJ_X1YOo8PbbbtKzTvk1UCvc-7felT0-68Vld39fTaGjc7-qyt4OHWqq6qMJFe9GpekhFE2UsDsTkECtabYBrxBAqG9TWae1zooAAyICaSQwEIhQBFl0S5ArsyQlVl0lnkzIwRQ6YNIK1ELR2OfmQ7XAQDdiBPx-d2z9NFWOCLVU8WudBMGfIMXuyuTiMn3TpTeZ9--F15KGUCGy56jjqbPxgChcIiYQiejdwxDF_9o7WeN3nW9uO5981c1LS51JGT-dd_akn0TCC18Y6cHgePwcH_v0LQldwUg.YlrmPA.e8TcSJ4AUZxhZuKxcna0gZzuPHE'), ('_pk_ses.4.dc78', '1')]), 'url_rule': <Rule '/comments/show_new/<object_class>/<object_id>' (OPTIONS, POST) -> CommentsView:show_new>, 'view_args': {'object_class': 'Reaction', 'object_id': '258'}, 'host': '127.0.0.1:5000', 'url': 'http://127.0.0.1:5000/comments/show_new/Reaction/258'}

here's request.data: b''

miguelgrinberg commented 2 years ago

First of all, you are submitting an empty form. What's the purpose of doing that? Once you add a field the problem will go away, I think. Second, the possible error or bug is in Flask-WTF, not Turbo-Flask, so you would need to ask Flask-WTF about that.

janpeterka commented 2 years ago

I thougth "submitting empty form" is pretty standart way of using Turbo - I have helper button_to based on Rails implementation. I'm using this in most of my code for showing/hiding some part of page with

Here's my usecase: I have information page, and there are comments. I want to show new comment form on page using turbo.append, without changing url. Is there any other way to do this instead of empty form?

I now see you use turbo-frames with GET form in your example app. So that's recommanded way to handle this?


I realize the problem is in using Turbo with Flask-WTF, but as I wasn't abble to find any help anywhere else, I thought to ask here, on intersection between Turbo and Flask things. But it's completely okay to ignore my question :)

janpeterka commented 2 years ago

Ok, so I get it working as shown in example - with GET form and turbo-frame. I'm sorry for bothering, this seems like a correct way to handle this.

miguelgrinberg commented 2 years ago

I thougth "submitting empty form" is pretty standart way of using Turbo

Submitting an empty form as a general practice is fine. Using Flask-WTF to handle an empty form is the problem. There is nothing needs Flask-WTF if all you need is implementing a button. The error occurs because you are giving Flask-WTF an empty form, so it gets confused trying to find where the form data is.

with GET form and turbo-frame

POST requests are better than GET for this though. I would stick with the POST request and drop Flask-WTF, which does not help in any way for this use case.

janpeterka commented 2 years ago

I'm not using Flask-WTF for generating my empty POST "turbo" form. When I'm creating form in controller, I want Flask-WTF, as it's (basically) model form. Problem is with Flask-WTF trying to use incoming data. I will report it to Flask-WTF.

Edit: So, it's already reported, and fixed 16 days ago. So sorry for bothering you, and thank you so much for your help!