使用flask,wtforms和jinja2

时间:2019-06-30 13:57:07

标签: python flask jinja2 flask-wtforms wtforms

我对Python世界还很陌生(打算进行切换并离开CurlyBracesCamelCaseWorld),并且我正在开发简单的应用go在整个初始堆栈(数据库,服务器,处理html页面和资产)中获取exp等)。

到目前为止,发展的步伐从未令我惊叹,资源的数量正在泛滥成灾。

但是我遇到了我无法克服的问题。

我现在尝试做的事情:

  • 具有包含一组静态字段的主wtform
  • 添加动态生成的子表单列表-预先填充值和自定义标签

在我的应用程序场景中,用户将能够指定要回答的问题的列表-这样将有一个表单,其中包含公用详细信息+动态列表,可用于在提交时将数据保存到数据库中。

我这里有完整的问题清单

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个对我也有帮助的链接

1 个答案:

答案 0 :(得分:0)

您的想法正确,但是您所做的工作存在一些问题:

  1. 您不应显式创建SubForm实例。将字典传递到append_entry(),以填充子表单中的字段。 (编辑:删除了有关将表单实例传递到append_entry()的错误信息。它必须是字典对象。)
  2. 您应该在append_entry()块之后而不是之前,在{strong>之后呼叫validate_on_submit()。当POST请求与表单一起返回时,它将已经创建了足够的子表单。您在构建页面时就这样做了。您只需要阅读所有表单内容并在重定向之前提取/保存数据。
  3. 您提到缺少数据和未进行验证。我有一种预感,目前您正在调用验证方法之前覆盖表单数据。因此,此问题可能会自行解决。
  4. 您提到CSRF。您需要在子表单{{ entry_line.hidden_tag() }}循环中包含for。这就是使CSRF与子窗体一起工作所需要的。

尝试一下,看看您的表单是否开始工作。