一、概述

Flask 是典型的微框架,作为 Web 框架来说,它仅保留了核心功能:请求响应处理模板渲染。这两类功能分别由 Werkzeug(WSGI 工具库)完成和 Jinja(模板渲染库)完成。
Flask本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login),都需要用第三方的扩展来实现。
Flask常用扩展包:

  • Flask-SQLalchemy:操作数据库;
  • Flask-migrate:管理迁移数据库;
  • Flask-Mail:邮件;
  • Flask-WTF:表单;
  • Flask-Bable:提供国际化和本地化支持,翻译;
  • Flask-script:插入脚本;
  • Flask-Login:认证用户状态;
  • Flask-OpenID:认证;
  • Flask-RESTful:开发REST API的工具;
  • Flask-Bootstrap:集成前端Twitter Bootstrap框架;
  • Flask-Moment:本地化日期和时间;
  • Flask-Admin:简单而可扩展的管理接口的框架

扩展列表:http://flask.pocoo.org/extensions/

  1. 中文文档(http://docs.jinkan.org/docs/flask/
  2. 英文文档(http://flask.pocoo.org/docs/0.12/

Flask与Django对比

  • Django功能大而全,Flask只包含基本的配置
    Django的一站式解决的思路,能让开发者不用在开发之前就在选择应用的基础设施上花费大量时间。Django有模板,表单,路由,认证,基本的数据库管理等等内建功能。与之相反,Flask只是一个内核,默认依赖于两个外部库: Jinja2 模板引擎和 Werkzeug WSGI 工具集,其他很多功能都是以扩展的形式进行嵌入使用。
  • Flask 比 Django 更灵活
  • Flask 在 Django 之后发布,现阶段有大量的插件和扩展满足不同需要
    Django发布于2005年,Flask创始于2010年年中。

体验:

  1. #导入flash
  2. from flask import Flask
  3. #初始化
  4. app = Flask(__name__)
  5. #添加路由
  6. @app.route('/')
  7. def hello():
  8. return 'Hello Flask!'
  9. if __name__ == '__main__':
  10. app.run(debug=True)

image.png
image.png
整个请求的处理过程如下所示:

  1. 1.当用户在浏览器地址栏访问这个地址,在这里即 http://localhost:5000/
  2. 2.服务器解析请求,发现请求 URL 匹配的 URL 规则是 /,因此调用对应的处理函数 hello()
  3. 3.获取 hello() 函数的返回值,处理后返回给客户端(浏览器)
  4. 4.浏览器接受响应,将其显示在窗口上

二、路由

请求方式限定

使用 methods 参数指定可接受的请求方式,可以是多种

  1. @app.route('/',methods=['GET', 'POST'])
  2. def hello():
  3. return 'hello world'

给路由传参示例

有时我们需要将同一类URL映射到同一个视图函数处理,比如:使用同一个视图函数 来显示不同用户的订单信息。
路由传递的参数默认当做string处理

  1. @app.route('/orders/<order_id>')
  2. def hello_itheima(order_id):
  3. # 此处的逻辑: 去查询数据库改用户的订单信息, 并返回
  4. print type(order_id) # 类型为unicode
  5. return 'hello %d' % order_id

这里指定int, 会调用系统的路由转换器进行匹配和转换.
- 大致原理是将参数强转为int, 如果成功, 则可以进行路由匹配
- 如果参数无法转换成功, 就无法匹配该路由
除了默认字符串变量部分之外,还可以使用以下转换器构建规则:

序号 转换器和描述
1 int
接受整数
2 float
对于浮点值
3 **path

接受用作目录分隔符的斜杠 |

  1. from flask import Flask
  2. app = Flask(__name__)
  3. @app.route('/blog/<int:postID>')
  4. def show_blog(postID):
  5. return 'Blog Number %d' % postID
  6. @app.route('/rev/<float:revNo>')
  7. def revision(revNo):
  8. return 'Revision Number %f' % revNo
  9. if __name__ == '__main__':
  10. app.run()

三、URL构建

url_for()函数对于动态构建特定函数的URL非常有用。
url_for()函数接受函数的名称作为第一个参数,以及一个或多个关键字参数,每个参数对应于URL的变量部分。
以下脚本演示了如何使用url_for()函数:

  1. from flask import Flask, redirect, url_for
  2. app = Flask(__name__)
  3. @app.route('/admin')
  4. def hello_admin():
  5. return 'Hello Admin'
  6. @app.route('/guest/<guest>')
  7. def hello_guest(guest):
  8. return 'Hello %s as Guest' % guest
  9. @app.route('/user/<name>')
  10. def hello_user(name):
  11. if name =='admin':
  12. return redirect(url_for('hello_admin'))
  13. else:
  14. return redirect(url_for('hello_guest', guest = name))
  15. if __name__ == '__main__':
  16. app.run(debug = True)

四、模板页面

Jinja2 的语法和 Django 大致相同。在模板里,你需要添加特定的定界符将 Jinja2 语句和变量标记出来,下面是三种常用的定界符:

  • {{ … }} 用来标记变量。
  • {% … %} 用来标记语句,比如 if 语句,for 语句等。
  • {# 这是注释 #}

    控制代码块

  • 用 {%%} 定义的控制代码块,可以实现一些语言层次的功能,比如循环或者if语句 ```python {% if user %} {{ user }} {% else %} hello!

{% for index in indexs %} {{ index }} {% endfor %}

  1. <a name="xm7LW"></a>
  2. ## 过滤器
  3. 过滤器的本质就是函数。有时候我们不仅仅只是需要输出变量的值,我们还需要修改变量的显示,甚至格式化、运算等等,而在模板中是不能直接调用 Python 中的某些方法,那么这就用到了过滤器。<br />使用方式:
  4. - 过滤器的使用方式为:变量名 | 过滤器。
  5. ```python
  6. {{variable | filter_name(*args)}}

如果没有任何参数传给过滤器,则可以把括号省略掉

{{variable | filter_name}}

如:``,这个过滤器的作用:把变量variable 的值的首字母转换为大写,其他字母转换为小写

链式调用

在 jinja2 中,过滤器是可以支持链式调用的,示例如下:

{{ "hello world" | reverse | upper }}

常见内建过滤器

字符串操作

  • safe:禁用转义

    {{ ‘hello‘ | safe }}

  • capitalize:把变量值的首字母转成大写,其余字母转小写

    {{ ‘hello’ | capitalize }}

  • lower:把值转成小写

    {{ ‘HELLO’ | lower }}

  • upper:把值转成大写

    {{ ‘hello’ | upper }}

  • title:把值中的每个单词的首字母都转成大写

    {{ ‘hello’ | title }}

  • reverse:字符串反转

    {{ ‘olleh’ | reverse }}

  • format:格式化输出

    {{ ‘%s is %d’ | format(‘name’,17) }}

  • striptags:渲染之前把值中所有的HTML标签都删掉

    {{ ‘hello‘ | striptags }}

  • truncate: 字符串截断

    {{ ‘hello every one’ | truncate(9)}}

列表操作

  • first:取第一个元素

    {{ [1,2,3,4,5,6] | first }}

  • last:取最后一个元素

    {{ [1,2,3,4,5,6] | last }}

  • length:获取列表长度

    {{ [1,2,3,4,5,6] | length }}

  • sum:列表求和

    {{ [1,2,3,4,5,6] | sum }}

  • sort:列表排序

    {{ [6,2,3,1,5,4] | sort }}

语句块过滤

{% filter upper %}
一大堆文字 {% endfilter %}

如:
在创建templates文件下创建index.html。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>{{ name }}'s Watchlist</title>
</head>
<body>
<h2>{{ name }}'s Flash</h2>
{# 使用 length 过滤器获取 movies 变量的长度 #}
<p>{{ movies|length }} Titles</p>
<ul>
    {% for movie in movies %} {# 迭代 movies 变量 #}
    <li>{{ movie.title }} - {{ movie.year }}</li>
    {# 等同于 movie['title'] #}
    {% endfor %} {# 使用 endfor 标签结束 for 语句 #}
</ul>
<footer>
    <small>&copy; 2018 <a href="/">HelloFlask</a></small>
</footer>
</body>
</html>

image.png
创建访问路由。

#导入flash
from flask import Flask, render_template

#初始化
app = Flask(__name__)

#添加路由
@app.route('/')
def hello():
    return 'Hello Flask!'


name = 'Li'
movies = [
    {'title': 'My Neighbor Totoro', 'year': '1988'},
    {'title': 'Dead Poets Society', 'year': '1989'},
    {'title': 'A Perfect World', 'year': '1993'},
    {'title': 'Leon', 'year': '1994'},
    {'title': 'Mahjong', 'year': '1996'},
    {'title': 'Swallowtail Butterfly', 'year': '1996'},
    {'title': 'King of Comedy', 'year': '1999'},
    {'title': 'Devils on the Doorstep', 'year': '1999'},
    {'title': 'WALL-E', 'year': '2008'},
    {'title': 'The Pork of Music', 'year': '2012'},
]

@app.route('/movie')
def movie():
    return render_template("index.html",name=name, movies=movies)


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

五、静态文件

在项目的路径下创建静态文件static 。
image.png
生成静态文件 URL:
在 HTML 文件里,引入这些静态文件需要给出资源所在的 URL。为了更加灵活,这些文件的 URL 可以通过 Flask 提供的 url_for() 函数来生成。
如:

<img src="{{ url_for('static', filename='foo.jpg') }}">

花括号部分的调用会返回 /static/foo.jpg。

<h2>
    <img alt="Avatar" src="{{ url_for('static', filename='images/avatar.png') }}">
</h2>

花括号部分的调用会返回 /static/images/avatar.png。

<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css">

image.png

五、模板优化

自定义错误页面:
访问一个不存在的 URL,比如 /hello,Flask 会自动返回一个 404 错误响应。默认的错误页面非常简陋
image.png
自定义404 错误页面模板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>404页面</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css">
</head>
<body>
<p>{{user.name}}</p>
<ul class="movie-list">
    <li>
        Page Not Found - 404
    </li>
</ul>
<footer>
    <small>&copy; 2018 <a href="/">HelloFlask</a></small>
</footer>
</body>
</html>
@app.context_processor
def inject_user():  # 函数名可以随意修改
    user={"name":"zhw"}
    return dict(user=user)  # 需要返回字典

#app.errorhandler() 装饰器注册一个错误处理函数,它的作用和视图函数类似,
# 当 404 错误发生时,这个函数会被触发,返回值会作为响应主体返回给客户端
@app.errorhandler(404)  # 传入要处理的错误代码
def page_not_found(e):  # 接受异常对象作为参数
    return render_template('404.html'), 404  # 返回模板和状态码

注意:
多个模板内都需要使用的变量,我们可以使用 app.context_processor 装饰器注册一个模板上下文处理函数

@app.context_processor
def inject_user():  # 函数名可以随意修改
    user={"name":"zhw"}
    return dict(user=user)  # 需要返回字典

六、模板继承

多个页面存在相同的内容,可以提取一个公共的页面,其他页面继承该页面。比如:基本模板中包含完整的 HTML 结构和导航栏、页首、页脚等通用部分。在子模板里,我们可以使用 extends 标签来声明继承自某个基本模板。
案例:
基模板 base.html

<!DOCTYPE html>
<html lang="en">
<head>
    {% block head %}
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ user.name }}'s Watchlist</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" type="text/css">
    {% endblock %}
</head>
<body>
    <h2>
        {{ user.name }}'s
    </h2>
    <nav>
        <ul>
            <li><a href="{{ url_for('index') }}">Home</a></li>
        </ul>
    </nav>
    {% block content %}{% endblock %}
    <footer>
        <small>&copy; 2018 <a href="/">HelloFlask</a></small>
    </footer>
</body>
</html>

上述存在两个块:
一个是包含 内容的 head 块,另一个是用来在子模板中插入页面主体内容的 content 块。
子模块:

七、Web表单

web表单是web应用程序的基本功能。
它是HTML页面中负责数据采集的部件。表单有三个部分组成:表单标签、表单域、表单按钮。表单允许用户输入数据,负责HTML页面数据采集,通过表单将用户输入的数据提交给服务器。
在Flask中,为了处理web表单,我们一般使用Flask-WTF扩展,它封装了WTForms,并且它有验证表单数据的功能。

WTForms支持的HTML标准字段

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

WTForms常用验证函数

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

使用Flask-WTF需要配置参数SECRET_KEY。
CSRF_ENABLED是为了CSRF(跨站请求伪造)保护。 SECRET_KEY用来生成加密令牌,当CSRF激活的时候,该设置会根据设置的密匙生成加密令牌。在HTML页面中直接写form表单:

示例

使用普通方式实现表单

在HTML页面中直接写form表单:

<form method="post">
    <label>用户名:</label><input type="text" name="username"><br>
    <label>密码:</label><input type="password" name="password"><br>
    <label>确认密码:</label><input type="password" name="password2"><br>
    <input type="submit" value="提交"><br>
    {% for message in get_flashed_messages() %}
        {{ message }}
    {% endfor %}
</form>

视图函数中获取表单数据:

from flask import Flask,render_template,request

app.secret_key = 'heima'

@app.route('/', methods=['GET', 'POST'])
def hello_world():

    # 1. 判断请求方式是post
    if request.method == 'POST':

        # 2. 获取参数, 并效验参数完整性, 如果有问题就进行flash
        username = request.form.get('username')
        password = request.form.get('password')
        password2 = request.form.get('password2')
        if not all([username, password, password2]):
            flash('params error')

        # 3. 效验密码
        elif password != password2:
            flash('password error')

        # 4. 没有问题就返回'success'
        else:
            print username
            return 'success'

    return render_template('wtf.html')

使用Flask-WTF实现表单

模板页面:

<form method="post">
    {#设置csrf_token#}
    {{ form.csrf_token() }}
    {{ form.username.label }}{{ form.username }}<br>
    {{ form.password.label }}{{ form.password }}<br>
    {{ form.password2.label }}{{ form.password2 }}<br>
    {{ form.input }}<br>
</form>

视图函数:

#coding=utf-8
from flask import Flask, render_template, request, flash

#导入wtf扩展的表单类
from flask_wtf import FlaskForm

#导入自定义表单需要的字段
from wtforms import SubmitField,StringField,PasswordField

#导入wtf扩展提供的表单验证器
from wtforms.validators import DataRequired,EqualTo

# 解决编码问题
import sys
reload(sys)
sys.setdefaultencoding("utf-8")

app = Flask(__name__)
app.config['SECRET_KEY']='zhw'

#自定义表单类,文本字段、密码字段、提交按钮
# 需要自定义一个表单类
class RegisterForm(FlaskForm):
    username = StringField('用户名:', validators=[DataRequired()]})
    password = PasswordField('密码:', validators=[DataRequired()])
    password2 = PasswordField('确认密码:', validators=[DataRequired(), EqualTo('password', '密码输入不一致')])
    input = SubmitField('提交')

#定义根路由视图函数,生成表单对象,获取表单数据,进行表单数据验证
@app.route('/form', methods=['GET', 'POST'])
def form():
    register_form = RegisterForm()

    if request.method == 'POST':
        # 调用validate_on_submit方法, 可以一次性执行完所有的验证函数的逻辑
        if register_form.validate_on_submit():
            # 进入这里就表示所有的逻辑都验证成功
            username = request.form.get('username')
            password = request.form.get('password')
            password2 = request.form.get('password2')
            print username
            return 'success'

        else:
            #message = register_form.errors.get('password2')[0]
            #flash(message)
            flash('参数有误')

    return render_template('wtf.html', form=register_form)

八、Flask Cookies

在Flask中,对cookie的处理步骤为:
1. 设置cookie:
设置cookie,默认有效期是临时cookie,浏览器关闭就失效
可以通过 max_age 设置有效期, 单位是秒

resp = make_response("success")   # 设置响应体
resp.set_cookie("name", "zs", max_age=3600)

2.获取cookie
获取cookie,通过request.cookies的方式, 返回的是一个字典,可以获取字典里的相应的值

cookie_1 = request.cookies.get("name")

3.删除cookie
这里的删除只是让cookie过期,并不是直接删除cookie
删除cookie,通过delete_cookie()的方式, 里面是cookie的名字

resp = make_response("del success")  # 设置响应体
resp.delete_cookie("name")

案例:

from flask import Flask, make_response, request  # 注意需导入 make_response

app = Flask(__name__)


@app.route("/set_cookies")
def set_cookie():
    resp = make_response("success")
    resp.set_cookie("name", "zs", max_age=3600)
    return resp


@app.route("/get_cookies")
def get_cookie():
    cookie_1 = request.cookies.get("name")  # 获取名字为Itcast_1对应cookie的值
    return cookie_1


@app.route("/delete_cookies")
def delete_cookie():
    resp = make_response("del success")
    resp.delete_cookie("name")

    return resp


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

image.png

九、Flask Sessions(会话)

与Cookie不同,Session(会话)数据存储在服务器上。会话是客户端登录到服务器并注销服务器的时间间隔。需要在该会话中保存的数据会存储在服务器上的临时目录中。
为每个客户端的会话分配会话ID。会话数据存储在cookie的顶部,服务器以加密方式对其进行签名。对于此加密,Flask应用程序需要一个定义的SECRET_KEY
Session对象也是一个字典对象,包含会话变量和关联值的键值对。

Session['username'] = 'admin'

要释放会话变量,请使用pop()方法。

session.pop('username', None)

如:

from flask import render_template

from flask import make_response

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

app = Flask(__name__)

app.secret_key = 'fkdjsafjdkfdlkjfadskjfadskljdsfklj'


@app.route('/')
def index():
    if 'username' in session:
        username = session['username']
        return '登录用户名是:' + username + '<br>' + "<b><a href = '/logout'>点击这里注销</a></b>"
    return "您暂未登录, <br><a href = '/login'></b>" + "点击这里登录</b></a>"


@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>
          <p><input type="submit" value ="登录"/></p>
       </form>
   '''


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


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

十、Flask 重定向和错误

重定向

Flask类有一个redirect()函数。调用时,它返回一个响应对象,并将用户重定向到具有指定状态代码的另一个目标位置。
redirect()函数的原型如下:

Flask.redirect(location, statuscode, response)

在上述函数中:

  • location参数是应该重定向响应的URL。
  • statuscode发送到浏览器标头,默认为302。
  • response参数用于实例化响应。

以下状态代码已标准化:

  • HTTP_300_MULTIPLE_CHOICES
  • HTTP_301_MOVED_PERMANENTLY
  • HTTP_302_FOUND
  • HTTP_303_SEE_OTHER
  • HTTP_304_NOT_MODIFIED
  • HTTP_305_USE_PROXY
  • HTTP_306_RESERVED
  • HTTP_307_TEMPORARY_REDIRECT

默认状态代码为302,表示‘found’
在以下示例中,redirect()函数用于在登录尝试失败时再次显示登录页面。

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

# Initialize the Flask application
app = Flask(__name__)


@app.route('/')
def index():
    return render_template('log_in.html')


@app.route('/login', methods=['POST', 'GET'])
def login():
    if request.method == 'POST' and request.form['username'] == 'admin':
        return redirect(url_for('success'))
        return redirect(url_for('index'))


@app.route('/success')
def success():
    return 'logged in successfully'


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

错误

Flask类具有带有错误代码的abort()函数。

Flask.abort(code)

Code参数采用以下值之一:

  • 400 - 用于错误请求
  • 401 - 用于未身份验证的
  • 403 - Forbidden
  • 404 - 未找到
  • 406 - 表示不接受
  • 415 - 用于不支持的媒体类型
  • 429 - 请求过多

让我们对上述代码中的login()函数稍作更改。如果要显示‘Unauthurized’页面,请将其替换为调用abort(401),而不是重新显示登录页面。

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

app = Flask(__name__)


@app.route('/')
def index():
    return render_template('log_in.html')


@app.route('/login', methods=['POST', 'GET'])
def login():
    if request.method == 'POST':
        if request.form['username'] == 'admin':
            return redirect(url_for('success'))
        else:
            abort(401)
    else:
        return redirect(url_for('index'))


@app.route('/success')
def success():
    return 'logged in successfully'


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

十一、Flask 文件上传

在 Flask 中处理文件上传非常简单。它需要一个 HTML 表单,其 enctype 属性设置为“multipart/form-data”,将文件发布到 URL。
URL 处理程序从 request.files[] 对象中提取文件,并将其保存到所需的位置。
每个上传的文件首先会保存在服务器上的临时位置,然后将其实际保存到它的最终位置。
目标文件的名称可以是硬编码的,也可以从 request.files[file] 对象的 filename 属性中获取。但是,建议使用 secure_filename() 函数获取它的安全版本。
可以在 Flask 对象的配置设置中定义默认上传文件夹的路径和上传文件的最大大小。

app.config['UPLOAD_FOLDER'] 定义上传文件夹的路径 
app.config['MAX_CONTENT_LENGTH'] 指定要上传的文件的最大大小(以字节为单位)

以下代码具有 ‘/upload’ URL 规则,该规则在 templates 文件夹中显示 ‘upload.html’,以及 ‘/upload-file’ URL 规则,用于调用 uploader() 函数处理上传过程。
‘upload.html’ 有一个文件选择器按钮和一个提交按钮。

<html>
<head>
  <title>File Upload</title>
</head>
<body>
    <form action="http://localhost:5000/uploader" method="POST" enctype="multipart/form-data">
        <input type="file" name="file"  />
        <input type="submit" value="提交" />
    </form>
</body>
</html>
from flask import Flask, render_template, request
from werkzeug.utils import secure_filename

import os

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'upload/'


@app.route('/upload')
def upload_file():
    return render_template('upload.html')


@app.route('/uploader', methods=['GET', 'POST'])
def uploader():
    if request.method == 'POST':
        f = request.files['file']
        print(request.files)
        isExists = os.path.exists(app.config['UPLOAD_FOLDER'])
        if isExists:
            f.save(os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(f.filename)))
        else:
            os.makedirs(app.config['UPLOAD_FOLDER'])
            f.save(os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(f.filename)))
        return 'file uploaded successfully'

    else:

        return render_template('upload.html')


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

注意:app.config[‘UPLOAD_FOLDER’] = ‘upload/‘
upload 前面不能加“/”。

十二、Flask 邮件

基于web的应用程序通常需要具有向用户/客户端发送邮件的功能。
Flask-Mail扩展使得与任何电子邮件服务器建立简单的接口变得非常容易。
首先,应该在pip实用程序的帮助下安装Flask-Mail扩展。

pip install Flask-Mail

然后需要通过设置以下应用程序参数的值来配置Flask-Mail。

序号 参数与描述
1 MAIL_SERVER
电子邮件服务器的名称/IP地址
2 MAIL_PORT
使用的服务器的端口号
3 MAIL_USE_TLS
启用/禁用传输安全层加密
4 MAIL_USE_SSL
启用/禁用安全套接字层加密

| | 5 | MAIL_DEBUG
调试支持。默认值是Flask应用程序的调试状态 | | 6 | MAIL_USERNAME
发件人的用户名 | | 7 | MAIL_PASSWORD
发件人的密码 | | 8 | MAIL_DEFAULT_SENDER
设置默认发件人 | | 9 | MAIL_MAX_EMAILS
设置要发送的最大邮件数 | | 10 | MAIL_SUPPRESS_SEND
如果app.testing设置为true,则发送被抑制 | | 11 | MAIL_ASCII_ATTACHMENTS
如果设置为true,则附加的文件名将转换为ASCII |

flask-mail模块包含以下重要类的定义。

Mail类

它管理电子邮件消息传递需求。类构造函数采用以下形式:

flask-mail.Mail(app = None)

构造函数将Flask应用程序对象作为参数。

Mail类的方法

序号 方法与描述
1 send()
发送Message类对象的内容
2 connect()
打开与邮件主机的连接
3 send_message()
发送消息对象

Message类

它封装了一封电子邮件。Message类构造函数有几个参数:

flask-mail.Message(subject, recipients, body, html, sender, cc, bcc, 
   reply-to, date, charset, extra_headers, mail_options, rcpt_options)

Message类方法

attach() - 为邮件添加附件。此方法采用以下参数:

  • filename - 要附加的文件的名称
  • content_type - MIME类型的文件
  • data - 原始文件数据
  • 处置 - 内容处置(如果有的话)。

add_recipient() - 向邮件添加另一个收件人
在下面的示例中,Google gmail服务的SMTP服务器用作Flask-Mail配置的MAIL_SERVER。

#步骤1 - 在代码中从flask-mail模块导入Mail和Message类。
from flask import Flask
from flask_mail import Mail, Message

app = Flask(__name__)
mail = Mail(app)

#步骤2 - 然后按照以下设置配置Flask-Mail。
app.config['MAIL_SERVER'] = 'smtp.qq.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USERNAME'] = 'ddd@qq.com'
app.config['MAIL_PASSWORD'] = 'dddd'
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True

#步骤3 - 创建Mail类的实例。
mail = Mail(app)

#步骤4 - 在由URL规则(‘/’)映射的Python函数中设置Message对象。
@app.route("/")
def index():
    msg = Message('Hello', sender='dd@qq.com', recipients=['id1@gmail.com'])
    msg.body = "Hello Flask message sent from Flask-Mail"
    mail.send(msg)
    return "Sent"


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

十三、数据库

一、Flask SQLite

在 Flask 中,通过使用特殊的 g 对象可以使用 before_request() 和 teardown_request() 在请求开始前打开数据库连接,在请求结束后关闭连接。

基本链接:

如:

# 步骤1 - 在代码中从flask-mail模块导入Mail和Message类。
from flask import Flask

app = Flask(__name__)

import sqlite3
from flask import g

DATABASE = '/path/to/database.db'


#链接数据库
def connect_db():
    return sqlite3.connect(DATABASE)

#链接数据库之前
@app.before_request
def before_request():
    g.db = connect_db()

#链接数据库之后
@app.teardown_request
def teardown_request(exception):
    if hasattr(g, 'db'):
        g.db.close()

注意:
销毁函数是一定会被执行的。即使请求前处理器执行失败或根本没有执行, 销毁函数也会被执行。因此,我们必须保证在关闭数据库连接之前数据库连接是存在 的。
缺点:
只有在 Flask 执行了请求前处理器时才有效。

按需连接

在只有需要的时候链接数据库,进行操作。

案例

初始化数据库

import sqlite3
conn = sqlite3.connect('database.db')
print("Opened database successfully")

conn.execute('CREATE TABLE students (name TEXT, addr TEXT, city TEXT, pin TEXT)')
print("Table created successfully")
conn.close()

home.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>Title</title>
</head>
<body>
<h2><a href ="/enternew" >怎加一个记录</a></h2>
<h2><a href ="/list" >展示一个列表</a></h2>
</body>
</html>

student.html

<html>
<body>

<form action="/addrec" method="POST">
    <h3>Student Information</h3>
    Name<br>
    <input type="text" name="nm"/></br>
    Address<br>
    <textarea name="add"></textarea><br>
    City<br>
    <input type="text" name="city"/><br>
    PINCODE<br>
    <input type="text" name="pin"/><br>
    <input type="submit" value="submit"/><br>
</form>

</body>
</html>

result.html

<!doctype html>
<html>
   <body>
      result of addition : {{ msg }}
      <h2><a href ="\" >go back to home page</a></h2>
   </body>
</html>

list.html

<!doctype html>
<html>
   <body>
      <table border = 1>
         <thead>
            <td>Name</td>
            <td>Address>/td<
            <td>city</td>
            <td>Pincode</td>
         </thead>
         {% for row in rows %}
            <tr>
               <td>{{row["name"]}}</td>
               <td>{{row["addr"]}}</td>
               <td> {{ row["city"]}}</td>
               <td>{{row['pin']}}</td>
            </tr>
         {% endfor %}
      </table>
      <a href = "/">Go back to home page</a>
   </body>
</html>
# import sqlite3
# conn = sqlite3.connect('database.db')
# print("Opened database successfully")
#
# conn.execute('CREATE TABLE students (name TEXT, addr TEXT, city TEXT, pin TEXT)')
# print("Table created successfully")
# conn.close()

from flask import Flask, render_template, request
import sqlite3 as sql

app = Flask(__name__)


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


@app.route('/enternew')
def new_student():
    return render_template('student.html')

@app.route('/addrec', methods=['get', 'post'])
def addrec():
    if request.method == 'POST':
        try:
            nm = request.form['nm']
            addr = request.form['add']
            city = request.form['city']
            pin = request.form['pin']

            with sql.connect("database.db") as con:
                cur = con.cursor()

                cur.execute("INSERT INTO students (name,addr,city,pin) VALUES(?, ?, ?, ?)", (nm, addr, city, pin))

                con.commit()
                msg = "Record successfully added"
        except:
            con.rollback()
            msg = "error in insert operation"

        finally:
            return render_template("result.html", msg=msg)
            con.close()


@app.route('/list')
def list():
    con = sql.connect("database.db")
    con.row_factory = sql.Row

    cur = con.cursor()
    cur.execute("select * from students")

    rows = cur.fetchall();
    return render_template("list.html", rows=rows)


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

image.png

二、Flask SQLAlchemy

在Flask Web应用程序中使用原始SQL对数据库执行CRUD操作可能很繁琐。相反, SQLAlchemy ,Python工具包是一个强大的OR Mapper,它为应用程序开发人员提供了SQL的全部功能和灵活性。Flask-SQLAlchemy是Flask扩展,它将对SQLAlchemy的支持添加到Flask应用程序中。

什么是ORM(Object Relation Mapping,对象关系映射)?
大多数编程语言平台是面向对象的。另一方面,RDBMS服务器中的数据存储为表。
对象关系映射是将对象参数映射到底层RDBMS表结构的技术。
ORM API提供了执行CRUD操作的方法,而不必编写原始SQL语句。

安装 flask-sqlalchemy

pip install flask-sqlalchemy

如果连接的是mysql数据库,需要安装mysqldb

pip install flask-mysqldb

使用Flask-SQLAlchemy管理数据库

在Flask-SQLAlchemy中,数据库使用URL指定,而且程序使用的数据库必须保存到Flask配置对象的SQLALCHEMY_DATABASE_URI键中。

Flask的数据库设置:

app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@127.0.0.1:3306/test'

其他设置:

# 动态追踪修改设置,如未设置只会提示警告, 不建议开启
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# 查询时会显示原始SQL语句
app.config['SQLALCHEMY_ECHO'] = True
名字 备注
SQLALCHEMY_DATABASE_URI 用于连接的数据库 URI 。例如:sqlite:////tmp/test.dbmysql://username:password@server/db
SQLALCHEMY_BINDS 一个映射 binds 到连接 URI 的字典。更多 binds 的信息见用 Binds 操作多个数据库
SQLALCHEMY_ECHO 如果设置为Ture, SQLAlchemy 会记录所有 发给 stderr 的语句,这对调试有用。(打印sql语句)
SQLALCHEMY_RECORD_QUERIES 可以用于显式地禁用或启用查询记录。查询记录 在调试或测试模式自动启用。更多信息见get_debug_queries()。
SQLALCHEMY_NATIVE_UNICODE 可以用于显式禁用原生 unicode 支持。当使用 不合适的指定无编码的数据库默认值时,这对于 一些数据库适配器是必须的(比如 Ubuntu 上 某些版本的 PostgreSQL )。
SQLALCHEMY_POOL_SIZE 数据库连接池的大小。默认是引擎默认值(通常 是 5 )
SQLALCHEMY_POOL_TIMEOUT 设定连接池的连接超时时间。默认是 10 。
SQLALCHEMY_POOL_RECYCLE 多少秒后自动回收连接。这对 MySQL 是必要的, 它默认移除闲置多于 8 小时的连接。注意如果 使用了 MySQL , Flask-SQLALchemy 自动设定 这个值为 2 小时。

常用的SQLAlchemy字段类型

类型名 python中类型 说明
Integer int 普通整数,一般是32位
SmallInteger int 取值范围小的整数,一般是16位
BigInteger int或long 不限制精度的整数
Float float 浮点数
Numeric decimal.Decimal 普通整数,一般是32位
String str 变长字符串
Text str 变长字符串,对较长或不限长度的字符串做了优化
Unicode unicode 变长Unicode字符串
UnicodeText unicode 变长Unicode字符串,对较长或不限长度的字符串做了优化
Boolean bool 布尔值
Date datetime.date 时间
Time datetime.datetime 日期和时间
LargeBinary str 二进制文件

数据库基本操作

一. 增删改操作

1. 基本概念

  • 在Flask-SQLAlchemy中,插入、修改、删除操作,均由数据库会话管理。
    • 会话用db.session表示。在准备把数据写入数据库前,要先将数据添加到会话中然后调用 commit() 方法提交会话。
  • 在Flask-SQLAlchemy中,查询操作是通过query对象操作数据。
    • 最基本的查询是返回表中所有数据,可以通过过滤器进行更精确的数据库查询。
      db.session.add(role)    添加到数据库的session中
      db.session.add_all([user1, user2]) 添加多个信息到session中
      db.session.commit()     提交数据库的修改(包括增/删/改)
      db.session.rollback()   数据库的回滚操作
      db.session.delete(user) 删除数据库(需跟上commit)
      

      2. 示例

      ```python

      -- coding:utf-8 --

from flask import Flask from flask_sqlalchemy import SQLAlchemy

app = Flask(name) SQLALCHEMY_DATABASE_URI = “mysql://%s:%s@%s:%d/%s” % (‘root’, ‘ablejava’, ‘192.168.9.223’, 3306,’zhw-test’) app.config[‘SQLALCHEMY_DATABASE_URI’] = SQLALCHEMY_DATABASE_URI app.config[‘SQLALCHEMY_TRACK_MODIFICATIONS’] = False

db = SQLAlchemy(app)

class Author(db.Model): tablename = ‘authors’ id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) books = db.relationship(‘Book’, backref=’author’)

def __repr__(self):
    return 'Author:%s' % self.name

class Book(db.Model): tablename = ‘books’ id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64)) author_id = db.Column(db.Integer, db.ForeignKey(‘authors.id’))

def __repr__(self):
    return 'Book:%s,%s' % (self.name, self.author_id)

@app.route(‘/‘) def hello_world(): return ‘Hello World!’

if name == ‘main‘:

# 删除所有的表
db.drop_all()
# 创建数据表
db.create_all()

# 生成数据
au1 = Author(name='老王')
au2 = Author(name='老惠')
au3 = Author(name='老刘')
# 把数据提交给用户会话
db.session.add_all([au1, au2, au3])
# 提交会话
db.session.commit()
bk1 = Book(name='老王回忆录', author_id=au1.id)
bk2 = Book(name='我读书少,你别骗我', author_id=au1.id)
bk3 = Book(name='如何才能让自己更骚', author_id=au2.id)
bk4 = Book(name='怎样征服美丽少女', author_id=au3.id)
bk5 = Book(name='如何征服英俊少男', author_id=au3.id)
bk5 = Book(name='test', author_id=au3.id)
# 把数据提交给用户会话
db.session.add_all([bk1, bk2, bk3, bk4, bk5])
# 提交会话
db.session.commit()

# 查询操作
# 查询表内所有内容
res = Book.query.all()
print("查询表内所有内容" + str(res))
# 只显示第一个
res = Book.query.first()
print("只显示第一个" + str(res))
# 按照条件查询
res = Book.query.filter_by(name='老王回忆录').all()
print("按照条件查询" + str(res))
# 逻辑非
res = Book.query.filter(Book.name != '老王回忆录').all()
print('逻辑非' + str(res))
# 取反
from sqlalchemy import not_

res = Book.query.filter(not_(Book.name != 'test')).all()
print('取反' + str(res))
# 逻辑与
from sqlalchemy import and_

res = Book.query.filter(and_(Book.id == 3, Book.name == 'test')).all()
print('与' + str(res))
# 逻辑或
from sqlalchemy import or_

res = Book.query.filter(or_(Book.name == 'guest', Book.name == 'test')).all()
print('与' + str(res))

app.run(debug=True)
如果报错:
```python
ModuleNotFoundError: No module named 'MySQLdb'
解决方式:
    pip install mysqlclient

十四、Flask蓝图

Flask 用 蓝图(blueprints) 的概念来在一个应用中或跨应用制作应用组件和支 持通用的模式。蓝图很好地简化了大型应用工作的方式,并提供给 Flask 扩展在应用 上注册操作的核心方法。
蓝图定义了可用于单个应用的视图,模板,静态文件等等的集合。
蓝图的使用:
要想创建一个蓝图对象,你需要import flask.Blueprint()类并用参数name和importname初始化。importname通常用**__name**,一个表示当前模块的特殊的Python变量,作为import_name的取值。

from flask import Blueprint

admin = Blueprint('admin',__name__)

@admin.route('/show')
def show():
    return 'admin_show'

假设项目中有多个模型,每个模型都有大量的操作。如admin,user,order等模块。将其进行拆分,形成不同的组件。
如:
admin

#admin.py
from flask import Blueprint,render_template, request

admin = Blueprint('admin',__name__)

@admin.route('/index')
def index():
    return render_template('admin/index.html')

@admin.route('/add')
def add():
    return 'admin_add'

@admin.route('/show')
def show():
    return 'admin_show'

user

from flask import Blueprint, render_template, redirect

user = Blueprint('user',__name__)

@user.route('/index')
def index():
    return render_template('user/index.html')

@user.route('/add')
def add():
    return 'user_add'

@user.route('/show')
def show():
    return 'user_show'

order

from flask import Blueprint, render_template, redirect

user = Blueprint('order',__name__)

@order.route('/index')
def index():
    return render_template('order/index.html')

@order.route('/add')
def add():
    return 'order_add'

@order.route('/show')
def show():
    return 'order_show'

各个视图组件创建完成,接下来进行注册。

from flask import Flask

app = Flask(__name__, template_folder='templates')

from admin import admin
from user import user
from order import order

# 这里分别给app注册了两个蓝图admin,user
# 参数url_prefix='/xxx'的意思是设置request.url中的url前缀,
# 即当request.url是以/admin或者/user的情况下才会通过注册的蓝图的视图方法处理请求并返回
app.register_blueprint(admin, url_prefix='/admin')
app.register_blueprint(user, url_prefix='/user')
app.register_blueprint(order, url_prefix='/order')



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


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

image.png

代码