python flask 使用json开发API接口 flask发送json_flask json传输失败


介绍Flask-WTF

为了在应用中使用表单,我会用到Flask-WTF,这是Flask的一个拓展,整合了WTForms包。Flask还有很多其他拓展。Flask-WTF的安装很简单,执行下面的命令:

(venv) $ pip install flask-wtf


配置文件

一个应用可能有很多需要个性化配置的内容,比如数据库信息等,把这些内容写在单独的文件而不是源代码内会更安全,因为源代码可能在github上分享出来,而我们并不希望分享密码等信息。

所以我们在项目目录新建一个config.py文件。


import os

class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'


这里我们有了一个SECRET_KEY配置项,这是让应用作为加密密钥的,以对网页表单的数据传输进行加密,避免SCRF攻击。下一步,我们需要把密钥应用在应用中。修改app/__init__.py文件。


from flask import Flask
from config import Config

app = Flask(__name__)
app.config.from_object(Config)

from app import routes


编写网页登录表单

编写表单类

Flask-WTF拓展用Python类来定义表单。这里我们新建一个app/forms.py,在其中定义登录表单,包括用户名输入框、密码输入框、记住我的选择框,以及一个提交按钮。


from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired

class LoginForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired()])
    remember_me = BooleanField('Remember Me')
    submit = SubmitField('Sign In')


编写表单模板

现在我们编写表单模板,好在LoginForm类知道如何渲染自己,所以这非常简单。我们新建app/template/login.html文件如下:


{% extends "base.html" %}

{% block content %}
    <h1>Sign In</h1>
    <form action="" method="post" novalidate>
        {{ form.hidden_tag() }}
        <p>
            {{ form.username.label }}<br>
            {{ form.username(size=32) }}
        </p>
        <p>
            {{ form.password.label }}<br>
            {{ form.password(size=32) }}
        </p>
        <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}


和教程2类似,我们继承了base.html,并通过{% block content %}拓展了内容。以后编写html页面都是类似的。这个模板接受一个LoginForm对象,模板中叫做form

HTML代码中的<form>元素定义了表单的容器。action属性定义了接受表单提交的数据的页面。action=""表示用当前的url来处理接收的数据。method属性定义了HTTP请求方法,默认是GET方法,但几乎所有表单数据都会使用POST方法。novalidate属性告诉浏览器不要检查表单数据,而让应用来处理。

{{ form.hidden_tag() }}产生了一个隐藏的数据来防止CSRF攻击。

表单视图函数

最后我们需要编写表单视图函数,来渲染前面编写的模板。我们在app/routes.py中增加以下代码:


from flask import render_template
from app import app
from app.forms import LoginForm

# ...

@app.route('/login')
def login():
    form = LoginForm()
    return render_template('login.html', title='Sign In', form=form)


为了方便找到loging页面,我们修改app/templates/base.html,在导航增加login链接:


<html>
    <head>
      {% if title %}
      <title>{{ title }} - Microblog</title>
      {% else %}
      <title>Welcome to Microblog</title>
      {% endif %}
    </head>
    <body>
        <div>
            Microblog:
            <a href="/index">Home</a>
            <a href="/login">Login</a>
        </div>
        <hr>
        {% block content %}{% endblock %}
    </body>
</html>


接收数据

现在,如果你运行应用,并提交登录表单,网页会显示Method Not Allowed错误。这是因为我们的视图函数还不能处理post请求。所以我们需要修改app/routes.py文件中的login函数如下:


from flask import render_template, flash, redirect

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        flash('Login requested for user {}, remember_me={}'.format(
            form.username.data, form.remember_me.data))
        return redirect('/index')
    return render_template('login.html', title='Sign In', form=form)


我们可以看到函数装饰器中增加了methods变量,这使得视图函数可以处理GET,POST请求。另外form.validate_on_submit()函数可以判断请求是否包括表单数据。如果是GET请求,这个函数返回False,直接渲染空的页面。如果是POST请求,这个方法会收集所有表单数据,进行数据检查,如果一切正常,就返回True,表明表单数据是合法的,可以继续处理。如果填写了不合法的数据,也是返回False。后面我会在数据不合法时增加报错信息。

form.validate_on_submit()函数返回True,redirect()函数可以让页面跳转到其他页面。我用了flash()函数,这是网页上显示一些信息的有用机制。当flash()被调用的时候,Flask会存储这条信息,但网页并没有直接显示,我们需要修改base.html模板文件,来处理flask()函数推送出来的信息。


<html>
    <head>
        {% if title %}
        <title>{{ title }} - microblog</title>
        {% else %}
        <title>microblog</title>
        {% endif %}
    </head>
    <body>
        <div>
            Microblog:
            <a href="/index">Home</a>
            <a href="/login">Login</a>
        </div>
        <hr>
        {% with messages = get_flashed_messages() %} {# messages 变量存储flash推送的信息 #}
        {% if messages %}
        <ul>
            {% for message in messages %}
            <li>{{ message }}</li>
            {% endfor %}
        </ul>
        {% endif %}
        {% endwith %}
        {% block content %}{% endblock %}
    </body>
</html>


现在你可以运行应用,尝试填入登录信息。你也可以看看如果表单内容为空会如何。

改善数据校验

如果你表单内容为空,页面没有报错,而是空白的表单,这对用户并不友好,我们需要给每个表单域给一个失败提醒。事实上,表单对象的validators可以自动处理这些报错逻辑,所以我们仅需要修改模板文件即可。我们修改login.html文件如下:


{% extends "base.html" %}

{% block content %}
    <h1>Sign In</h1>
    <form action="" method="post" novalidate>
        {{ form.hidden_tag() }}
        <p>
            {{ form.username.label }}<br>
            {{ form.username(size=32) }}<br>
            {% for error in form.username.errors %} {# 报错的处理逻辑 #}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>
            {{ form.password.label }}<br>
            {{ form.password(size=32) }}<br>
            {% for error in form.password.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}


产生链接

目前位置,我们在模板中是这样定义链接的:


<div>
        Microblog:
        <a href="/index">Home</a>
        <a href="/login">Login</a>
    </div>


在视图函数的redirect()函数中,我们是这样定义链接的:


@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        # ...
        return redirect('/index')
    # ...


为了避免这种绝对化的链接地址,以及支持动态的url,我们可以用url_for()函数,比如url_for('login')就返回/login。函数接收的是视图函数的函数名称,flask会匹配到视图函数的超链接。

所以后续定义链接,我都会使用url_for()函数。所以base.html修改如下:


<div>
        Microblog:
        <a href="{{ url_for('index') }}">Home</a>
        <a href="{{ url_for('login') }}">Login</a>
    </div>


而login()视图函数修改为:


from flask import render_template, flash, redirect, url_for

# ...

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        # ...
        return redirect(url_for('index'))
    # ...


说明

本文是我的Flask学习笔记,