Flask 入门(第一篇)

1. 认识 Flask

Flask 是一个微型 Web 框架,依赖于 jinjia2 模板系统和 Werkzeug WSGI(本质为 Socket 服务端) 服务,默认情况不支持数据库抽象层、表单验证,如果要使用可以进行拓展。

Flask 常用扩展包:

  • Flask-SQLalchemy:操作数据库;
  • Flask-migrate:管理迁移数据库;
  • Flask-Mail:邮件;
  • Flask-WTF:表单;
  • Flask-script:插入脚本;
  • Flask-Login:认证用户状态;
  • Flask-RESTful:开发REST API的工具;
  • Flask-Bootstrap:集成前端Twitter Bootstrap框架;
  • Flask-Moment:本地化日期和时间;

安装:

pip3 install flask

Werkzeug:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from werkzeug.wrappers import Request, Response

@Request.application
def hello(request):
    return Response('Hello World!')

if __name__ == '__main__':
    from werkzeug.serving import run_simple
    run_simple('localhost', 4000, hello)

尝鲜:

from flask import Flask

app = Flask(__name__)


@app.route("/")
def index():
    return "Index Page"


if __name__ == "__main__":
    app.run()

官网地址:

2. 路由系统

2.1 五种常用路由

Flask 常用五种路由:

# 前四种可以接收变量
@app.route('/user/<username>')      # /user/rose
@app.route('/post/<int:post_id>')   # /post/1
@app.route('/post/<float:post_id>') # /post/1.2
@app.route('/post/<path:subpath>')     # /post/static/css/xx.css
@app.route('/login', methods=['GET', 'POST'])

示例:

@app.route('/user/<username>')
def show_user_profile(username):
    
    return 'User %s' % username

以上五种路由基于以下对应关系:

DEFAULT_CONVERTERS = {
    'default':          UnicodeConverter,
    'string':           UnicodeConverter,
    'any':              AnyConverter,
    'path':             PathConverter,
    'int':              IntegerConverter,
    'float':            FloatConverter,
    'uuid':             UUIDConverter,
}

让 Flask 支持正则:https://segmentfault.com/q/1010000000125259

2.2 反解 URL

url_for() 方法可以根据视图函数名,反解出 url,类似于 Django 的 reverse() 方法:

# 接收视图函数名字,返回所对应的绝对 URL
url_for('index', _external=True)
from flask import Flask, redirect, url_for

app = Flask(__name__)


@app.route("/")
def index():
    return "Index Page"

@app.route("/hello")
def hello():
    return "Hello World!"


@app.route('/user/<username>')
def profile(username):
    return username

with app.test_request_context():
    print(url_for('hello'))
    print(url_for('profile', username='rose'))
    print(url_for('index'))

if __name__ == "__main__":
    app.run()

test_request_context() 告诉 Flask,即使我们使用Python shell,它也会像处理请求一样行事。

运行结果如下:

/hello
/user/rose
/

2.3 正则匹配

from flask import Flask
from werkzeug.routing import BaseConverter

class Regex_url(BaseConverter):
    def __init__(self,url_map,*args):
        super(Regex_url,self).__init__(url_map)
        self.regex = args[0]

app = Flask(__name__)
app.url_map.converters['re'] = Regex_url

@app.route('/user/<re("[a-z]{3}"):id>')
def hello_itcast(id):
    return 'hello %s' % id


if __name__ == '__main__':
    app.run()

3. 模板系统

3.1 静态文件和模板路径设置

from flask import Flask, redirect, url_for, render_template

app = Flask(__name__)       # 静态文件和模板依赖这句

@app.route('/hello/')
def hello():
    return render_template('t1.html')


if __name__ == '__main__':
    app.run()

静态文件语法:

url_for('static', filename='style.css')

静态文件目录 static 在项目根目录,与程序同级(需要自己创建),Flask 默认回会去 static 中查找,你可以在里面新建子文件夹:

<img src="{{ url_for('static', filename='imgs/1.png') }}">

模板路径设置

Flask 会在 templates 文件夹里寻找模板。所以,如果你的应用是个模块,这个文件夹应该与模块同级;如果它是一个包,那么这个文件夹作为包的子目录:

情况 1: 模块:

/application.py
/templates
    /hello.html

情况 2: 包:

/application
    /__init__.py
    /templates
        /hello.html

3.2 模板语言及自定义模板

Flask 使用的是 Jinja2模板,与 Django 一样,因此语法也没什么差别。

自定义模板

在模板中使用 Python 函数:

app.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, render_template

app = Flask(__name__)


def func():

    return '<h1>Flask 自定义模板</h1>'


@app.route('/index', methods=['GET', 'POST'])
def index():
    return render_template('index.html', func=func)

if __name__ == '__main__':
    app.run()

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {{ func() | safe }}			<!-- safe 不转义 -->
</body>
</html>

3.3 过滤器

语法:

变量名 | 过滤器

字符串过滤器:

# 禁用转义 safe
<p>{{ '<em>hello</em>' | safe }}</p>


# capitalize:把变量值的首字母转成大写,其余字母转小写;
<p>{{ 'hello' | capitalize }}</p>

# lower:把值转成小写;
<p>{{ 'HELLO' | lower }}</p>

# upper:把值转成大写;
<p>{{ 'hello' | upper }}</p>

# title:把值中的每个单词的首字母都转成大写;
<p>{{ 'hello' | title }}</p>

#trim:把值的首尾空格去掉;
<p>{{ ' hello world ' | trim }}</p>

# reverse:字符串反转;
<p>{{ 'olleh' | reverse }}</p>

# format:格式化输出;
<p>{{ '%s is %d' | format('name',17) }}</p>

# striptags:渲染之前把值中所有的HTML标签都删掉;
<p>{{ '<em>hello</em>' | striptags }}</p>

列表操作:

# first:取第一个元素
<p>{{ [1,2,3,4,5,6] | first }}</p>

# last:取最后一个元素
<p>{{ [1,2,3,4,5,6] | last }}</p>

# length:获取列表长度
<p>{{ [1,2,3,4,5,6] | length }}</p>

# sum:列表求和
<p>{{ [1,2,3,4,5,6] | sum }}</p>

# sort:列表排序
<p>{{ [6,2,3,1,5,4] | sort }}</p>


自定义过滤器

过滤器的本质是函数,两种实现方式:一种是通过Flask应用对象的add_template_filter方法。还可以通过装饰器来实现自定义过滤器,与内置过滤器重名则覆盖:

1、add_template_filter,第一个参数是函数名,第二个参数是自定义的过滤器名称

def filter_double_sort(ls):
    return ls[::2]
app.add_template_filter(filter_double_sort,'double_2')

2、装饰器来实现自定义过滤器,参数为过滤器名字:

@app.template_filter('db3')
def filter_double_sort(ls):
    return ls[::-3]

app.py

from flask import Flask, render_template

app = Flask(__name__)


def filter_double_sort(ls):
    return ls[::2]
app.add_template_filter(filter_double_sort, 'double_2')

@app.route('/')
def index():
    data_list = [1, 2, 5, 8]
    return render_template('index4.html', data_list=data_list)

if __name__ == "__main__":
    app.run()

index4.html

<p>{{ data_list | double_2}}</p>

3.5 宏

类似于 python 中的函数,宏的作用就是在模板中重复利用代码,避免代码冗余。

定义不带参数的宏:

<!-- 定义宏 -->
{% macro input() %}
  <input type="text" name="username" value="" size="30"/>
{% endmacro %}

<!-- 调用宏 -->
{{ input() }}

定义带参数的宏:

{% macro input(name,value='',type='text',size=20) %}
    <input type="{{ type }}"
           name="{{ name }}"
           value="{{ value }}"
           size="{{ size }}"/>
{% endmacro %}

<!-- 调用宏,并传递参数 -->
{{ input(value='name',type='password',size=40)}}

还可以把宏单独抽取出来,封装成 html 文件,其它模板中导入使用文件名可以自定义 macro.html

{% macro function() %}

    <input type="text" name="username" placeholde="Username">
    <input type="password" name="password" placeholde="Password">
    <input type="submit">
{% endmacro %}

在其它模板文件中先导入,再调用:

{% import 'macro.html' as func %}
{% func.function() %}


宏、继承(extend)和包含(include)区别:

  • 宏(Macro)、继承(Block)、包含(include)均能实现代码的复用
  • 继承(Block):本质是代码替换,一般用来实现多个页面中重复不变的区域
  • 宏(Macro):功能类似函数,可以传入参数,需要定义、调用
  • 包含(include):是直接将目标模板文件整个渲染出来

3.4 特殊变量和方法

  • config 对象: Flask的config对象,也就是 app.config 对象,{{ config.SQLALCHEMY_DATABASE_URI }}
  • get_flashed_messages方法:应用在模板中一般用于获取错误信息,返回之前在Flask中通过 flash() 传入的信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用 get_flashed_messages() 方法取出。
{% for message in get_flashed_messages() %}
    {{ message }}
{% endfor %}

4. 公共组件

4.1 请求和响应

1、请求 request

浏览器发送过来的 HTTP 请求,被 flask 封装在 request中(werkzeug.wrappers.BaseRequest) 中,提供了一下字段或方法以供使用:

request.method			# 请求方法
request.args			# 获取GET请求参数
request.form			# 获取POST请求参数
request.values
request.files			# 获取上传文件
request.cookies			# cookies
request.headers			# 请求头
request.path
request.full_path
request.script_root
request.url				# 请求 URL 
request.base_url		# 获取请求路径
request.url_root
request.host_url
request.host			# 获取ip和端口

2、响应 response

1、模板渲染、重定向

模板渲染:render_template、重定向:redirecturl_for

from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)

@app.route('/index/', method=['GET', 'POST'])
def index():
    return render_template('index.html')
    # redirect(url_for('login'))		# 重定向

2、错误信息

from flask import Flask, abort, render_template
app = Flask(__name__)


@app.route('/index/', methods=['GET', 'POST'])
def index():
    abort(404, 'Page Not Found!')		# 返回一个  Page Not Found! 的页面

if __name__ == '__main__':
    app.run()

3、make_reponse

make_response 类似于 Django 的 HTTPResponse,用于返回字符串,下面是几个比较常见的用法:

返回内容、页面:

from flask import Flask, abort, render_template, make_response

app = Flask(__name__)

def func():
    return '<h1>Flask 自定义模板</h1>'

@app.route('/index/', methods=['GET', 'POST'])
def index():
    # return make_response('make response')			# 返回内容
    response = make_response(render_template('index.html', func=func))		# 返回页面
    # return response, 200,			# 返回内容和状态码

responseflask.wrappers.Response 类型,同时 make_resposne 还可以用来设置响应头、设置 cookie 等:

# response.delete_cookie
# response.set_cookie
# response.headers['X-Something'] = 'A value'

@app.route('/index/', methods=['GET', 'POST'])
def index():
    response = make_response(render_template('index.html', func=func))
    response.headers['hahaha'] = 'hello world'
    return response

4.2 自定义错误页面

如果在浏览器中输入不可用的路由,那么就会显示一个 404 状态的错误页面,这个页面太过简陋、平庸,并不是我们所希望的。好在 Flask 允许我们基于模板自定义错误页面,需要用到模块:flask-bootstrap

安装:

pip3 install flask-bootstrap

下面来示范两个最常见的错误:404 和 500:

1、app.py

初始化 bootstrap,定义两个函数,用于处理 404 和 500 错误页面:

from flask import Flask, abort, render_template
from flask_bootstrap import Bootstrap


app = Flask(__name__)
bootstrap = Bootstrap(app)		# 初始化,千万别忘记

@app.route('/index/', methods=['GET', 'POST'])
def index():
    return "OK"

@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'),404

@app.errorhandler(500)
def internal_server_error(e):
    return render_template('500.html'),500

if __name__ == '__main__':
    app.run()

2、在模板中定义一个母版 templates/errors/base.html,编辑如下:

{%extends "bootstrap/base.html"%}

{%block title %}Flask{% endblock %}

{%block navbar %}
<div class="navbar navbar-inverse" role="navigation">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle"
            data-toggle="collapse" data-target=".navbar-collapse">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">Flasky</a>
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li><a href="/">Home</a></li>
            </ul>
        </div>
    </div>
</div>

{% endblock %}
{% block content %}
<div class="container">
    <div class="page-header">
        {% block page_content %}{% endblock %}
    </div>
</div>
{% endblock %}

3、下面再编写 404.html500.html

404.html

{% extends "base.html" %}
{% block title %}Flasky - Page Not Found{% endblock %}
{% block page_content %}
<div class="page-header">
    <h1>Not Found</h1>
</div>
{% endblock %}

500.html

{% extends "base.html" %}
{% block title %}Flasky - internal server error{% endblock %}
{% block page_content %}
<div class="page-header">
    <h1>internal server error</h1>
</div>
{% endblock %}

现在我们来访问一个不存在的页面看看:http://127.0.0.1:5000/main/

似乎好看了点,当然你还可以定制更为高端一点。

4.3 Session

Session 依赖于 Cookies ,并且对 Cookies 进行密钥签名要使用会话,你需要设置一个密钥。

  • 设置:session['username'] = 'xxx'
  • 删除:session.pop('username', None)
  • 获取:session.get('username')

设置获取 Cookie

from flask import Flask,make_response
@app.route('/cookie')
def set_cookie():
    resp = make_response('this is to set cookie')
    resp.set_cookie('username', 'itcast')
    return resp

from flask import Flask,request
#获取cookie
@app.route('/request')
def resp_cookie():
    resp = request.cookies.get('username')
    return resp

示例:利用 Session 验证用户登录

from flask import Flask, session, redirect, url_for, escape, request

app = Flask(__name__)


@app.route('/')
def index():
    if 'username' in session:
        return '欢迎回来: %s' % escape(session['username'])
    return '你没有登录'		# 也可以重定向到登录页面


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['username'] = request.form['username']
        return redirect(url_for('index'))
    return '''
        <form action="" method="post">
            <p><input type=text name=username>
            <p><input type=submit value=Login>
        </form>
    '''


@app.route('/logout')
def logout():
    # 从 session 移除 username
    session.pop('username', None)
    return redirect(url_for('index'))


# 设置密钥
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'


if __name__ == '__main__':
    app.run()

4.3 message

message 是一个基于 Session 实现的用于保存数据的集合,其特点是:使用一次就删除

app.py

from flask import Flask, flash, redirect, render_template, request

app = Flask(__name__)
app.secret_key = 'some_secret'


@app.route('/')
def index1():
    return render_template('index2.html')


@app.route('/set')
def index2():
    v = request.args.get('p')
    flash(v)        # 将消息给下一个请求,并将其删除,模板必须调用 get_flashed_messages() 函数
    return 'ok'


if __name__ == "__main__":
    app.run()

index2.html

模板中必须使用 get_flashed_messages() 才能获取到 message 传递的信息:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>messages</title>
</head>
<body>
    {% with messages = get_flashed_messages() %}		<!-- with xxx:取别名 -->
        {% if messages %}
        <ul class=flashes>
            {% for message in messages %}
            <li>{{ message }}</li>
            {% endfor %}
        </ul>
        {% endif %}
    {% endwith %}
</body>
</html>

首先请求:http://127.0.0.1:5000/set?p=123index() 函数获取 p 的值,并将其传递给下一个请求(传递后即将其删除)。

第二个请求访问:http://127.0.0.1:5000/,从而获得上一次请求传递的信息:

4.4 请求钩子

所谓请求钩子即是用来处理请求前、后,一些特定事情的功能实现。如:请求前连接数据库、请求后指定数据交互格式等。Flask 中请求钩子以装饰器形式实现:

Flask 中请求钩子:

  • before_first_request:在处理第一个请求前运行,比如:数据库的连接,初始化操作
  • before_request:在每次请求前运行。
  • after_request:如果没有未处理的异常抛出,在每次请求后运行。
  • teardown_request:在每次请求后运行,即使有未处理的异常抛出。

示例:基于 before_request 实现用户登录认证

相比较装饰器而已有如下优势:

  • 装饰器实现用户登录认证,需要给每个需要认证的视图函数添加装饰器
  • before_request,只需一个即可。
from flask import Flask, render_template, request, redirect, session, url_for

app = Flask(__name__)

app.secret_key = "123$456"

# 基于flask里请求扩展来做
@app.before_request
def process_request(*args, **kwargs):
    """不管访问哪个视图函数,都会实现执行这个函数,也就意味着每次都会检查是否登录"""
    # 访问/login的时候还没有登录,就会一直重定向到登录页,所以就要设置个白名单,如果请求地址是/login,就返回None
    if request.path == "/login":
        return None

    # 1.登录验证功能
    user = session.get('user_info')
    # 2.如果登录信息正常,什么都不做,程序继续其他执行
    if user:
        return None
    # 3.如果登录验证不通过,就重定向到登录页面
    return redirect("/login")


@app.route("/index", methods=['GET'])
def index():
    name = session.get('user_info', None)
    return 'Welcome, %s' % name


@app.route("/login", methods=['GET', 'POST'])
def login():
    if request.method == "GET":
        return render_template("login.html")
    else:
        user = request.form.get("username")
        pwd = request.form.get("password")
        if user == "rose" and pwd == "123456":
            session['user_info'] = user
            return redirect("/index")

        return render_template("login.html", **{"error": "用户名和密码错误"})


if __name__ == "__main__":
    app.run()


也可以有多个请求钩子,但是要注意的是当有多个请求中间件时,执行顺序是不同的:

  • before_request:从上到下按照顺序执行
  • after_request:从下到上的顺序执行
  • 当有多个 before_request 时,若第一个有 return 语句,那么后面的 before_request 将被拦截不被执行,但是 after_request 会继续执行

4.5 中间件

Flask 程序在运行过程中会事先加载 wsgi_app,基于此我们可以拓展做些别的事情:

from flask import Flask
app = Flask(__name__)

@app.route("/login", methods=['GET', 'POST'])
def index():
    pass

class Md(object):
    def __init__(self, old_wsgi_app):
        self.old_wsgi_app = old_wsgi_app

    def __call__(self, environ, start_response):
        print("开始之前")
        ret = self.old_wsgi_app(environ, start_response)
        print("结束之后")
        return ret

if __name__ =="__main__":
    app.wsgi_app = Md(app.wsgi_app) # 相当于把wsgi_app给更新了
    app.run()

5. 表单

5.1 表单拓展 WTForms

表单由三个部分组成:表单标签、表单域、表单按钮。Flask 通过Flask-WTF 实现表单功能。它封装了WTForms,可以生成表单、以及验证表单数据。

安装:

pip3 install Flask-WTF
pip3 install flask-moment

WTForms 支持的 HTML 标准字段:

StringField	    # 文本字段
TextAreaField	# 多行文本字段
PasswordField	# 密码文本字段
HiddenField	    # 隐藏文本字段
DateField	    # 文本字段,值为datetime.date格式
DateTimeField	# 文本字段,值为datetime.datetime格式
IntegerField	# 文本字段,值为整数
DecimalField	# 文本字段,值为decimal.Decimal
FloatField	    # 文本字段,值为浮点数
BooleanField	# 复选框,值为True和False
RadioField	    # 一组单选框
SelectField	    # 下拉列表
SelectMultipleField	    # 下拉列表,可选择多个值
FileField	    # 文本上传字段
SubmitField	    # 表单提交按钮
FormField	    # 把表单作为字段嵌入另一个表单
FieldList	    # 一组指定类型的字段

WTForms 常用验证函数:

验证函数 说明
DataRequired 确保字段中有数据
AEqualTo 比较两个字段的值,常用于比较两次密码输入
Length 验证输入的字符串长度
NumberRange 验证输入的值在数字范围内
URL 验证URL
AnyOf 验证输入值在可选列表中
NoneOf 验证输入值不在可选列表中

跨站请求伪造保护 csrf

设置一个密匙,生成加密令牌,再用令牌验证请求表单中数据真伪:

app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'  # 密匙随便取 

# 如果你设置的模板中存在表单,你只需要在表单中添加如下
<form method="post" action="/">
    {{ form.csrf_token }}
</form>

# 如果没有模板中没有表单,你仍然需要一个 CSRF 令牌
<form method="post" action="/">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
</form>


简单示例

1、定义一个表单类:

from flask_wtf import FlaskForm
#导入自定义表单需要的字段
from wtforms import SubmitField, StringField, PasswordField
#导入wtf扩展提供的表单验证器
from wtforms.validators import DataRequired, EqualTo

# validators 不能为空
class NameForm(FlaskForm):
    name = StringField(label='用户名', validators=[Required()])
    submit = SubmitField('提交')

2、在模板中使用表单:

<h1>Hello, {% if name %}{{ name }} {% else %} Stranger {% endif %}!</h1>

<form method='post'>
    {{ form.hidden_tag() }}
    {{ form.name.label }} {{form.name() }}
    {{ form.submit() }}

    # 传入 HTML 属性
    {{ form.name.label }} {{form.name(id='id_name') }}
</form>

总是自己传属性太麻烦,而且表单样式过于丑陋。可以用 flask-bootstrap,它是对 bootstrap 进行了简单封装,安装好之后可以直接调用:

<!--上面表单可用下面方式一次渲染-->
{% import 'bootstrap/wtf.html' as wtf %}
{{ wtf.quick_form(form) }}

from flask_bootstrap import Bootstrap
app = Flask(__name__)

bootstrap = Bootstrap(app)

wtf.quick_form(form) 函数参数为 Flask-WTF 表单对象

<!-- 条件结果为 True,渲染 if else 中文字,否则渲染 else endif 中语句 -->
<h1>Hello, {% if name %}{{ name }} {% else %} Stranger {% endif %}!</h1>

5.2 基于 WTForms 实现用户登录验证

实现方式:继承+WTForms +Flask-Bootstrap

app.py

from flask import Flask, render_template, redirect, url_for, session, request,flash
from flask_bootstrap import Bootstrap
from flask_moment import Moment

#导入wtf扩展的表单类
from flask_wtf import FlaskForm
#导入自定义表单需要的字段
from wtforms import SubmitField, StringField, PasswordField
#导入wtf扩展提供的表单验证器
from wtforms.validators import DataRequired, EqualTo
app = Flask(__name__)
app.config['SECRET_KEY'] = '1'

bootstrap = Bootstrap(app)  # 使用 bootstrap 渲染表单
moment = Moment(app)    # 本地化时间

#自定义表单类,文本字段、密码字段、提交按钮
class Login(FlaskForm):
    user = StringField(label=u'用户:', validators=[DataRequired()])
    pwd1 = PasswordField(label=u'密码', validators=[DataRequired()])
    pwd2 = PasswordField(label=u'确认密码', validators=[DataRequired()])
    submit = SubmitField(u'提交')

@app.route('/', methods=['GET','POST'])
def index():
    return render_template('index.html')

#定义根路由视图函数,生成表单对象,获取表单数据,进行表单数据验证
@app.route('/login', methods=['GET','POST'])
def login():
    form = Login()
    if form.validate_on_submit():
        name = form.user.data
        pwd1 = form.pwd1.data
        pwd2 = form.pwd2.data
        print(name, pwd1, pwd2)

        if name == 'rose' and (pwd1 == pwd2 and pwd2 == '123'):
            return redirect(url_for('index'))
        else:
            flash(u'用户名或密码错误!')

    return render_template('login.html',  form=form)

if __name__ == '__main__':
    app.run()

include/base.html

{% extends "bootstrap/base.html" %}

{% block title %}Flask WTF{% endblock %}

{% block head %}
{{ super() }}
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
{% endblock %}

{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">Flask WTF</a>
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li><a href="/">Home</a></li>
            </ul>
        </div>
    </div>
</div>
{% endblock %}

{% block content %}
<div class="container">
    <!-- get_flashed_messages() 显示错误信息 -->

    {% for message in get_flashed_messages() %}
    <div class="alert alert-warning">
        <button type="button" class="close" data-dismiss="alert">&times;</button>
        {{ message }}
    </div>
    {% endfor %}

    {% block page_content %}{% endblock %}
</div>
{% endblock %}

{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}

login.html

继承 base.html

{% extends "include/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}

{% block title %}Flask WTF{% endblock %}

{% block page_content %}
<div class="page-header">

</div>
{{ wtf.quick_form(form) }}
{% endblock %}

将用户个人信息保存到会话中:

app.py

from flask import Flask, render_template, redirect, url_for, session, request,flash


def login('/login', methods=['GET', 'POST']):
    ...
    # 会话中原有值
    old_name = session.get('name')
    if old_name is not None and old_name != form.name.data:
        flash('用户名错误!')
    session['name'] = form.name.data
    return redirect(url_for('index'))
 return render_template('login.html', form=form, name=session.get('name'))

login.html

<div class="page-header">
    <h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>

原文地址:https://www.cnblogs.com/midworld/p/11075990.html