我对Python世界还很陌生(打算进行切换并离开CurlyBracesCamelCaseWorld),并且我正在开发简单的应用go在整个初始堆栈(数据库,服务器,处理html页面和资产)中获取exp等)。
到目前为止,发展的步伐从未令我惊叹,资源的数量正在泛滥成灾。
但是我遇到了我无法克服的问题。
我现在尝试做的事情:
在我的应用程序场景中,用户将能够指定要回答的问题的列表-这样将有一个表单,其中包含公用详细信息+动态列表,可用于在提交时将数据保存到数据库中。
我这里有完整的问题清单
1)主要问题是提交表单时,我无法读取.validate()中的子表单数据。
2)另一件事是我不能强制标签显示要动态设置的自定义值
3)我需要更多地了解如何处理子窗体中的csfr以及如何解决该问题
4)最后一个-如何验证子表单-用于必填字段,长度等
1&2是我现在主要关心的问题,我觉得这些问题具有相同的根本原因 我的直觉告诉我,破碎的元素ID是有意义的(每个子表单字符串字段的“内容”,而不是索引的“条目0-内容”,这是我在提交时看到的)
我找不到如何执行此操作的完整示例,并且我正在努力连接收集的作品。我准备了简单的代码,包括python和jinja2模板,可以运行,以演示问题。在找出原因后,我很乐意发布完整的工作代码,因为我会杀了才找到它。
因此,服务器->
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)
和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>
在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]
在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]
ids显然存在一些问题(2x内容vs条目0-内容),我两次都得到第一个结果。(值=“ my ans 1”)
我希望能够根据问题列表生成完整的表单列表(我在这里只使用静态2),为每个子表单设置自定义标签,然后在服务器中获取数据,因此我可以在那儿做剩下的工作。
此后,我可以自己进行验证和CSFR,但是拥有有效的脚手架似乎是有效的第一步。我已经花了很多时间在闲逛,但是我觉得自己现在正在转圈。
还有办公室-如果您认为我对如何实现自己想要实现的目标的假设是错误的,那么我应该用来做到这一点-让我知道。我想写正确的东西,而不仅仅是行之有效的东西。
Pastebin链接,如果您愿意
感谢@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个对我也有帮助的链接
答案 0 :(得分:0)
您的想法正确,但是您所做的工作存在一些问题:
append_entry()
,以填充子表单中的字段。 (编辑:删除了有关将表单实例传递到append_entry()
的错误信息。它必须是字典对象。)append_entry()
块之后而不是之前,在{strong>之后呼叫validate_on_submit()
。当POST请求与表单一起返回时,它将已经创建了足够的子表单。您在构建页面时就这样做了。您只需要阅读所有表单内容并在重定向之前提取/保存数据。{{ entry_line.hidden_tag() }}
循环中包含for
。这就是使CSRF与子窗体一起工作所需要的。尝试一下,看看您的表单是否开始工作。