[英]Problem with setting up dynamic list of subforms with flask, wtforms and jinja2
I'm fairly new to the Python world (planing to make a switch and leave the CurlyBracesCamelCaseWorld) and I'm working on the simple app go get exp in the whole initial stack (database, server, handling html pages & assets, etc). 我对Python世界还很陌生(打算进行切换并离开CurlyBracesCamelCaseWorld),并且我正在开发简单的应用go在整个初始堆栈(数据库,服务器,处理html页面和资产等)中获取exp 。
So far so good, the pace of the development never ceases to amaze me and the amount of resources is hearth worming. 到目前为止,发展的步伐从未令我惊叹,资源的数量却泛滥成灾。
But I've encounter the problem that I just can't get past by. 但是我遇到了我无法克服的问题。
What I try to do now: 我现在尝试做的是:
In my apps scenario user will be able to specify the list of questions to answer - so there will be a form with common details + the dynamic list, that I can use to save the data to the database on submit. 在我的应用程序场景中,用户将能够指定要回答的问题的列表-因此将有一个表单,其中包含公用详细信息+动态列表,我可以使用该表单在提交时将数据保存到数据库中。
I have a whole list of issues here 我这里有一个完整的问题清单
1) The main problem is that I can't read the subforms data in the .validate() when I submit the form 1)主要问题是我提交表单时无法读取.validate()中的子表单数据
2) The other thing is that I can't force the labels to show the custom values That I want to set dynamically 2)另一件事是我不能强制标签显示要动态设置的自定义值
3) I need to do more reading on handling csfr in subforms and how to work around that as well 3)我需要更多阅读有关在子窗体中处理csfr的信息以及如何解决该问题的方法
4) And the last one - how do I validate the subforms - for required fields, length, etc 4)最后一个-如何验证子表单-用于必填字段,长度等
1 & 2 is my main concern now and I have a feeling that the issues have the same root-cause My gut feeling tells me that the broken element id is meaningful ('content' for each subform string field, instead of indexed 'entries-0-content' - that I see on submit) 1&2是我现在主要关心的问题,我感到这些问题具有相同的根本原因。我的直觉告诉我,破碎的元素ID是有意义的(每个子表单字符串字段的“内容”,而不是索引的“条目- 0内容”-我在提交时看到)
I wasn't able to find the full example how to do that and I'm struggling to connect the pieces I collected. 我找不到完整的示例该如何做,而我正在努力连接我收集的作品。 I prepared the simple code, both python & jinja2 template, ready to run, to demonstrate the problems.
我准备了简单的代码,包括python和jinja2模板,可以运行,以演示问题。 I'll gladly post the full working code after I figure it out as I'd kill to find just that..
在找出完整的工作代码后,我很乐意发布完整的工作代码,因为我很想找到它。
So, the server -> 因此,服务器->
from flask import Flask, redirect, url_for, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, FieldList, FormField, SubmitField, HiddenField, Label
from wtforms.validators import DataRequired
app = Flask(__name__, template_folder='flaskblog/templates')
app.config['SECRET_KEY'] = 'SECRET_KEY-SECRET_KEY-SECRET_KEY'
# subforms
class SubForm(FlaskForm):
# how to handle hidden id that I can use to properly commit that on submit?
# entry_type_id = HiddenField()
# validators for subforms don't work, but that's something I'll try to address later
content = StringField(validators=[DataRequired()])
# I use custom __init__ set custom label for the field - or rather I try, as it doesn't work..
def __init__(self, custom_label=None, *args, **kwargs):
# not sure if safe - even just for the subform! #
# Without that, I get 'TypeError: argument of type 'CSRFTokenField' is not iterable' on main_form.validate_on_submit()
kwargs['csrf_enabled'] = False
FlaskForm.__init__(self, *args, **kwargs)
if custom_label is not None:
self.content.label = Label(self.content.id, custom_label)
print(f'INIT // id: [{self.content.id}] // content.data: [{self.content.data}] // label: [{self.content.label.text}]')
# main forms
class MainForm(FlaskForm):
title = StringField('title')
entries = FieldList(FormField(SubForm))
submit = SubmitField('Post')
@app.route("/test", methods=['GET', 'POST'])
def test_route():
# the main form
main_form = MainForm(title='title')
# sub forms, created before validate_on_submit()
sub_form_1 = SubForm(content='Default answer 1', custom_label='Question 1')
sub_form_2 = SubForm(content='Default answer 2', custom_label='Question 2')
main_form.entries.append_entry(sub_form_1)
main_form.entries.append_entry(sub_form_2)
if main_form.validate_on_submit():
for entry in main_form.entries.entries:
print(f'LOOP // id: [{entry.content.id}] // content.data: [{entry.content.data}] // label: [{entry.content.label.text}]')
return redirect(url_for('test_route'))
print(f'INSTANCE_1 // id: [{sub_form_1.content.id}] // content.data: [{sub_form_1.content.data}] // label: [{sub_form_1.content.label.text}]')
print(f'INSTANCE_2 // id: [{sub_form_2.content.id}] // content.data: [{sub_form_2.content.data}] // label: [{sub_form_2.content.label.text}]')
return render_template('test_form.html', title='Test Form', main_form=main_form, legend='Test Form')
if __name__ == '__main__':
app.run(debug=True)
And the html template -> 和html模板->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title> Confused -.-' </title>
</head>
<body>
<div class="content-section">
<form action="" method="post">
{{ main_form.hidden_tag() }}
{{ main_form.title.label(class="form-control-label") }}: {{ main_form.title(class="form-control form-control-lg") }}
{% for entry_line in main_form.entries %}
<div class="form-group">
{{ entry_line.content.label(class="form-control-label") }}
{{ entry_line.content.data(class="form-control form-control-lg") }}
</div>
{% endfor %}
{# For the main form I use main_form.title(), main_form.submit(), etc - without .data(). #}
{# If I try to do main_form.title.data() I get the ex that I can't call on 'str' #}
{# But, for entry_lines, if I don't add .data() and just use entry_line.content() #}
{# I can see the input field, but it's prepopulated with HTML for that input instead of the value (that I see in that html) #}
<div class="form-group">
{{ main_form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
</body>
</html>
Debug on GET: 在GET上进行调试:
INIT // id: [content] // content.data: [Default answer 1] // label: [Question 1]
INIT // id: [content] // content.data: [Default answer 2] // label: [Question 2]
INSTANCE_1 // id: [content] // content.data: [Default answer 1] // label: [Question 1]
INSTANCE_2 // id: [content] // content.data: [Default answer 2] // label: [Question 2]
Debug on POST: 在POST上调试:
INIT // id: [content] // content.data: [my ans 1] // label: [Question 1]
INIT // id: [content] // content.data: [my ans 1] // label: [Question 2]
LOOP // id: [entries-0-content] // content.data: [<input id="content" name="content" type="text" value="my ans 1">] // label: [Content]
LOOP // id: [entries-1-content] // content.data: [<input id="content" name="content" type="text" value="my ans 1">] // label: [Content]
INIT // id: [content] // content.data: [Default answer 1] // label: [Question 1]
INIT // id: [content] // content.data: [Default answer 2] // label: [Question 2]
INSTANCE_1 // id: [content] // content.data: [Default answer 1] // label: [Question 1]
INSTANCE_2 // id: [content] // content.data: [Default answer 2] // label: [Question 2]
There is clearly some issue with ids (2x content vs entries-0-content) & I get the first result twice.. (value="my ans 1") id显然存在一些问题(2x内容vs条目0-内容),我两次都得到第一个结果。(值=“ my ans 1”)
I'd love to be able to generate the full form list based on the list of questions (I just use a static 2 here), set the custom labels per subform and then get the data in the server, so I can do the rest of the job inn there. 我希望能够根据问题列表生成完整的表单列表(我在这里仅使用静态2),为每个子表单设置自定义标签,然后在服务器中获取数据,因此我可以完成其余工作那里的工作旅馆。
I can fight with validation and csfr myself after that, but having the working scaffolding seems like a valid first step. 在那之后,我可以自己进行验证和使用csfr,但是拥有可工作的脚手架似乎是有效的第一步。 I've spend a lot of time poking around but I feel like I'm running in circle now.
我已经花了很多时间在闲逛,但是我觉得自己现在正在转圈。
And ofc - if you think that my assumptions on how to achieve what I want to achieve are wrong and I should use to do that - let me know. 而且,ofc-如果您认为我对如何实现自己想要实现的目标的假设是错误的,并且我应该习惯这样做,请告诉我。 I want to write the right thing, not just something that works.
我想写正确的东西,而不仅仅是行之有效的东西。
Pastebin links, if you prefer Pastebin链接,如果您愿意
Thanks to @Nick K9! 感谢@Nick K9!
from flask import Flask, redirect, url_for, render_template, request
from flask_wtf import FlaskForm
from wtforms import StringField, FieldList, FormField, SubmitField, HiddenField, Label
from wtforms.validators import DataRequired
app = Flask(__name__, template_folder='flaskblog/templates')
app.config['SECRET_KEY'] = 'SECRET_KEY-SECRET_KEY-SECRET_KEY'
subform_datasource = {
0: {'question': 'Question 1', 'answare': 'Answare 1'},
1: {'question': 'Question 2', 'answare': 'Answare 2'}
}
# subforms
class SubForm(FlaskForm):
entry_type_id = HiddenField()
content = StringField(validators=[DataRequired()])
# main forms
class MainForm(FlaskForm):
title = StringField('title')
entries = FieldList(FormField(SubForm))
submit = SubmitField('Post')
@app.route("/test", methods=['GET', 'POST'])
def test_route():
main_form = MainForm()
if main_form.validate_on_submit():
for entry in main_form.entries.entries:
entry_message = (
f'POST // wtform id: [{entry.content.id}] '
f' // entry_type_id id: [{entry.entry_type_id.data}]'
f' // content.data: [{entry.content.data}]'
f' // label: [{entry.content.label.text}]'
)
print(str(entry_message))
return redirect(url_for('test_route'))
elif request.method == 'GET':
# You can indeed set the default values, but you need to pass the dict, not the SubForm instance!
for key, subform in subform_datasource.items():
main_form.entries.append_entry({'content': subform['answare'], 'entry_type_id': key})
# Moved out from the constructor - on subform failed validation labels reset to the default value 'Content'
# I guess that matching what was send to the form does not cast back the labels but creates the fresh instances with just the value
# What, of course, makes sense - it's an edge case, no point in affecting performance for everyone
for entry in main_form.entries.entries:
entry.content.label.text = subform_datasource[entry.entry_type_id.data]['question']
return render_template('test_form.html', title='Test Form', main_form=main_form, legend='Test Form')
if __name__ == '__main__':
app.run(debug=True)
2 links that also helped me 2个也对我有帮助的链接
You have the right idea, but there are a few issues with what you've done: 您有正确的想法,但是您所做的工作存在一些问题:
append_entry()
to populate the fields in the subform. append_entry()
以填充子表单中的字段。 ( Edit: Took out incorrect information about passing form instances to append_entry()
. It needs to be a dictionary object.) append_entry()
错误信息。它必须是一个字典对象。) append_entry()
after the validate_on_submit()
block, not before. append_entry()
后 validate_on_submit()
之前没有块。 When the POST request comes back with your form, it will already have sufficient subforms created. {{ entry_line.hidden_tag() }}
inside the subform for
loop. {{ entry_line.hidden_tag() }}
子窗体里面for
循环。 That should be all you need to get CSRF working with the subform. Try that and see if your form starts working. 尝试一下,看看您的表单是否开始工作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.