Python Web 之 Flask

FLASK

一、概述

flask是一个基于python并依赖于Jinja2模板引擎和WerkZeug WSGI(Web Server Gatewey InterFace.web) 服务的框架

WSGI:服务网关接口,提供处理网络请求相关的功能

hello world

from flask import Flask

# 创建flask的程序实例
app = Flask(__name__)

@app.route('/') # 路由配置

# 视图函数
def index():
    return "

欢迎访问

" # 启动服务 if __name__ == "__main__": app.run(debug=True)

二、 定义路由

路由是为了匹配用户的请求地址,会自动执行视图函数。视图函数中必须有返回值,返回字符串显示到响应的页面中。

2. 无参数

定义路由

@app.route('/地址')

定义视图函数

def funcName():
    return "" # 响应到页面的内容

例如:

@app.route("/") # '/'表示根路径
def index(): #匹配到路径后执行的视图函数
    return "首页"

3. 带参数

变量:

@app.route("/login/")
def login(name):
    return "欢迎%s登陆"%name

如需要设置默认路由,则在视图函数中传参直接设置 def login(name="Chancey")

还有一种方式,就是在路由装饰器中传入一个字典 @app.route("/login/",defaults={"name":"游客"})

4. 类型转换器

缺省:字符串,不能包含 '/'

int :转换整数 float :转换小数 path :字符串,运行包含 '/'

使用: @app.route('/show/') 在路径中直接转换

例如

配置路由:/calaute//,视图函数中接收参数,返回类似于“3 + 5 = 8”

@app.route('/calaute//')
def calaute(num1,num2):
    return "%s + %s = %d"%(num1,num2,int(num1)+int(num2))

注意:路径中的参数变量永远是字符串

5. 多个URL

多个URL执行同一个视图函数

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

例如

定义路由:127.0.0.1:5000/show 127.0.0.1:5000/show/list 127.0.0.1:5000/show/,执行同一个函数,返回相应的内容

@app.route("/show")
@app.route("/show/list")
@app.route("/show/")
def show(name="Chancey"):
    return "show %s"% name

如果在 app.run() 设置 host=0.0.0.0

不影响当前虚拟IP(127.0.0.1)

可以让当前局域网中的其他计算机通过内网IP访问服务器

二、模板

模板是一种特殊的HTML文件,Python+HTML网页结构,允许在模板文件中使用变量,定义流程控制。使用模板可以使用视图函数专注于处理业务逻辑,将页面渲染交由模板控制

  • 导入 render_template

  • 项目中创建“ templates ”文件夹,所有模板文件必须存放” template “文件夹下
  • 在视图函数中使用 render_template("模板文件") ,生成模板字符串,交由浏览器解析

from flask import Flask,render_template

app = Flask(__name__)

@app.route("/info")
def info():
    return render_template("01-show.html", name="flask", age=20)

1. 变量代码块

  1. 模板中使用变量

    语法: {{ 变量名(key) }}

  2. 从视图函数中获取相关的变量,传递到模板文件中

    语法: return render_template("模板文件", key1=value1, key2=value2)

    函数中可以传递若干键值对,其中的key名就是在模板文件中使用的变量名

    local() 函数,是将当前作用域中的所有局部变量打包成一个字典返回,可以用一个变量接收,然后传递到模板中.

    • 如果变量里面有字典类型的数值,有两种方法取值 variable["keyName"]variable.keyName
    • 如果变量里面有列表类型的数值,则直接用 list[number] 取值

例如:

视图函数中定义变量(name=”” age= dic= tub= list= ),将数据传递到模板文件中显示

from flask import Flask, render_template

# 创建实例
app = Flask(__name__)

# 定义动物类
class Pet(object):
    name = None
    def play(self):
        return "来和" + self.name + "玩耍吧"

@app.route('/show')

def show():
    name = "Chancey"
    age = 18
    dic = {
        "name":"Waller",
        "age":20
    }
    list = ["开车","保健"]
    tup = ("波多","仓井","海翼")

    # 实例化对象
    cat = Pet()
    cat.name = "妲己"

    # locals() 将当前作用域中的局部变量包装成一个字典返回

    # key=value
    return render_template("01-show.html", d=locals())


    
    show


    

模板文件

列表、元组、字典都可以使用[key/index]和点语法访问 name:{{ d["name"] }} age:{{ d.age }} name:{{ d.dic["name"] }} 宠物名:{{ d.cat.name }} 动作:{{ d.cat.play() }}

2. 过滤器

允许模板中的变量在输出之前修改成其他的值,修改显示

upper
lower
title
first
last
length
default
trim

语法: {{ 变量|过滤器1|过滤器2 }}


原版:{{ d.s1 }} 大写:{{ d.s1|upper }} 小写:{{ d.s1|lower }} 首字母大写:{{ d.s1|title }} 获取第一个:{{ d.s1|first }} 获取最后一个:{{ d.s1|last }} 获取长度:{{ d.s1|length }} 去掉两边空格:{{ d.s1|trim }} 未赋值的变量:{{ d.s2|default("默认变量") }}

3. 控制代码块

在模板中书写条件语句和循环语句,使用 {% python语句 %}

3.1 if

{% if 条件 %}
    条件成立时。允许书写静态标签,也可以书写变量
{% endif %}

{% if 条件 %}
    条件成立时执行
{% else %}
    条件不成立时执行
{% endif %}


{% if 条件1 %}
    pass
{% elif 条件2 %}
    pass
{% elif 条件3 %}
    pass
{% endif %}

例如:

{% if d.age < 18 %}
        

{{ d.age }}岁未成年

{% elif d.age < 30 %}

{{ d.age }}岁青成年

{% elif d.age < 50 %}

{{ d.age }}岁中成年

{% else %}

{{ d.age }}岁老年人

{% endif %}

3.2 for

{% for 变量 in 可跌对象 %}
{% endfor %}


    
    show
    
    .c1{
        background: red;
    }
    .c2{
        background: orange;
    }
    .c3{
        background: cyan;
    }
    



{% for item in info %}
    
{% endfor %}
姓名 年龄
{{ item.name }} {{ item.age }}
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def show():
    info = [
        {"name":"Chancey","age":18},
        {"name":"Waller","age":35},
        {"name":"Mary","age":16},
        {"name":"Jacob","age":40},
        {"name":"William","age":17},
        {"name":"Samuel","age":37},
        {"name":"Anthony","age":35},
    ]
    return render_template("02-show.html", info=info)

if __name__ == '__main__':
    app.run(debug=True, host="0.0.0.0")

3.3 loop

循环的内部变量 loop :直接在内部使用,表示本次循环相关的信息

常用属性:

loop.index
loop.index0
loop.first
loop.last

实例在前边for循环中已使用

4. 静态文件

  • 不与服务器交互的文件都是静态文件(css、js、图片、音频等)

  • 所有静态文件都必须存储在一个名为 static 的文件夹下,Flask程序会自动查找

  • 静态文件的访问:必须使用 /static 访问

  • url_for("视图函数名") 实现反向解析路由,后边可以跟参数

    url_for("login") 根据视图函数解析对应的URL

    url_for("login", uname="chancey", passwd="123456") 反向解析带参数的路由

    /login/chancey/123

    /static/css/base.css

    "

5.模板继承

与类的继承相似

如果两个页面中大部分内容与结构都一致,可以采用模板继承

实现:

  • 父模板:指定可以被子模板重写的内容

    {% block 块名 %}
      

    父模板

    {% endblock %}
  • 子模板中继承父模板

    {% extends 父模板名称 %}
  • 子模板中可以重写父模板中指定的块的内容

    {% block 块名 %}
      

    子模板

    {% endblock %}





    
    
        {% block web_title %}
        父模板
        {% endblock %}
    


    

{% block title %} 这里是标题 {% endblock %}

{% block content %} 这里是内容 {% endblock %}



{% extends "02-base.html" %}

{% block web_title %}
 佛曰
{% endblock %}

{% block title %}
 佛曰
{% endblock %}

{% block content %}
  写字楼里写字间,写字间里程序员;
  程序人员写程序,又拿程序换酒钱。
  酒醒只在网上坐,酒醉还来网下眠;
  酒醉酒醒日复日,网上网下年复年。
  但愿老死电脑间,不愿鞠躬老板前;
  奔驰宝马贵者趣,公交自行程序员。
  别人笑我忒疯癫,我笑自己命太贱;
  不见满街漂亮妹,哪个归得程序员?
{% endblock %}

三、网络请求

利用网络通信协议实现前后端数据交互,常用的网络通信协议:HTTP(S),规定数据的传输格式

1. 请求

1.1 请求消息

客户端向服务端发送的消息

1.2 组成

  1. 请求起始行

    协议、请求方式、资源路径

  2. 请求消息头

    使用kry-value字典的方式存储相关信息

  3. 请求主体

    get请求如果携带数据,以参数的形式直接拼接在URL后边( ?key1=value1&key2=value2

    只有POST方式才会有请求主体

2. 响应

2.1 响应信息

服务端接收到请求并且处理之后,返回给客户端的信息(数据)

2.2 组成

  1. 响应起始行

    协议、响应状态码、原因短句

  2. 响应消息头

    描述响应回来的数据,以 key:value 存储

  3. 响应主体

    保存响应数据

四、Flask中的HTTP

1. requests

在requests对象中封装了所有跟当前请求相关的信息

使用:

  1. 引入: from flask import request

  2. 使用:在视图函数中获取request对象内部的信息

  3. 属性:

    scheme 获取此次请求的协议

    method 获取请求方式

    args 获取GET提交的数据

    form 获取POST提交的数据

    cookies 获取cookies中保存的数据

    files 获取上传的文件

    path 获取请求的资源路径(无参数)

    full_path 获取请求的资源路径(携带参数)

    url 获取完整的请求地址

    headers

子模板

协议:{{ params.scheme }} 请求方式:{{ params.method }} 请求的参数:{{ params.args }} 请求的参数的内容:{{ params.args.uname }} 请求的参数的内容:{{ params.args.age }} cookies:{{ params.cookies }} 资源路径:{{ params.path }}

资源路径:{{ params.full_path }}

  1. 获取请求中的数据

    获取GET请求中的数据

    request.args["key"]

    request.args.get("key","默认值")

    request.args.getlist("key") 适用于一个key对应多个值的情况(复选框)

    print(request.args.get("uname"))
    print(request.args.get("passwd"))
    print(request.args.getlist("hobby"))
    #get当时如果未携带数据,在视图函数中直接读取request.args['']数据,报400

    获取POST请求中的数据

    request.form 获取数据字典

    request.form["key"]

    request.form.get("key")

    request.form.getlist('key')

    post即使未携带参数,直接获取字典的值,返回为空
  2. 页面重定向

    由服务器端通知浏览器重新向新的地址发送请求

    引入 redirect

    使用函数 redirect("重定向地址")

    视图函数中返回 return redirect("重定向地址")

  3. 页面源

    当前的请求是从哪一个源地址发起的,保存在请求消息头中( "Referer":""

  4. 文件上传

    使用表单控件 type="file" 向服务器发送文件,因为文件,图片,音频等都是为禁止数据,必须设置表单的提交方式和编码类型

    服务器端使用 request.files 获取上传的文件,返回字典

    例如:

    f = request.files["key"]
    f.save(保存路径)
    @app.route("/add", methods=["GET", "POST"])
    def add():
        if request.method == "GET":
            return render_template("02-add.html")
        else:
            title = request.form["title"]
            type = request.form["type"]
            content = request.form["content"]
            print("标题:%s 类型:%s 内容:%s"%(title, type, content))
    
            # 判断文件上传
            if "userimg" in request.files:
                file = request.files["userimg"]
                file_name = generate_filename(file.filename)
                up_path = generate_upload_path(__file__, "static/upload", file_name)
                print("保存路径:" + up_path)
                return "获取数据成功"
            else:
                return "已存在"

2. response

模型(Models)

模型:根据数据表结构而创建出来的class(一张表一个类,一个字段就是一个属性)

框架:ORM(Object Relational Mapping 对象关系映射)

特征:

  • 数据表到编程类的映射
  • 数据类型的映射
  • 关系映射(将数据库中表与表之间的关系 对应到 编程语言中类与类的关系)

优点:

  • 封装操作提升效率
  • 省略庞大的数据访问层

五、ORM

1. SQLAlchemy

安装: pip install flask-sqlalchemy

导包: from flask_sqlalchemy import SQLAlchemy

配置数据库: app.config['SQLALCHEMY_DATABASE_URI']="MYSQL://用户名:密码@数据地址:端口/数据库名"

创建数据库: create database dbname default charset utf8 collate utf8_general_ci;

数据库自动提交: app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"] = True

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# import pymysql
# pymysql.install_as_MySQLdb()

app = Flask(__name__)

#连接数据库
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql+pymysql://root:Asd.1234@127.0.0.1:3306/flaskDB"
#创建SQLAlachemy实例
db = SQLAlchemy(app)
print(db)


if __name__ == '__main__':
    app.run(debug=True, host="0.0.0.0")

上边的代码中, import pymysql \ pymysql.install_as_MySQLdb() 可以省略,简写在config中

2. 定义类

2.1 作用

通过编写模型类的方式,让程序自动生成数据表模型类也称为实体类

2.2 语法

# 代码中大写单词均为自定义
class MODELNAME(db.Model):
    __tablename__ = "TABLENAME"
    COLUMN = db.Column(db.TYPE, OPPTIONS)
    
"""
1. MODELNAME 定义模型类名称,参考表名
2. TABLENAME 指定要映射到的表名,如果不存在的话,则创建表
3. COLUMN 属性名,映射到数据表中就是列名
4. TYPE 映射到列的数据类型
5. OPPTIONS 列选项
"""

db.TYPE 列类型

OPPTIONS 列选项

class Usres(db.Model):
    __tablename__ = "users"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(20), nullable=False, unique=True)
    age = db.Column(db.Integer)
    email = db.Column(db.String(120), unique=True)

    def __init__(self, username, age, email):
        self.username = username
        self.age = age
        self.email - email
    
    def __repr__(self):
        return ""%(self.username, self.age, self.email)
    
#将创建好的实体类映射回数据库
db.create_all()
## 创建Student实体类,表名student
"""
1. id 主键 自增
2. sname 姓名 长度30 不为空
3. sage 年龄 整数
4. isActive 启用状态 bool类型 默认为True
"""

## 创建Teacher类,表名teacher
"""
1. id 主键 自增
2. tname 姓名 长度30 不为空
3. tage 年龄 整数
"""

## 创建Course类,表名course
"""
1. id 主键 自增
2. cname 课程名称 长度30
"""

class Student(db.Model):
    __tablename__ = "student"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    sname = db.Column(db.String(30), unique=True)
    sage = db.Column(db.Integer)
    isActive = db.Column(db.Boolean, default="True")
class Teacher(db.Model):
    __tablename__ = "teacher"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    tname = db.Column(db.String(30), unique=True)
    tage = db.Column(db.Integer)
class Course(db.Model):
    __tablename__ = "course"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    cname = db.Column(db.String(30), unique=True)

3. 数据迁移

3.1 定义

将实体类的改动再次映射回数据库

3.2 依赖于第三方库

  • Flask-script

    包:flask_script

    类:Manager

    作用:对项目进行管理(启动项目、增加管理指令)

  • flask-migrate

    包: flask_migrate

    类:Migrate(协调app和db之间的关系)、MigrateCommand(在终端中提供实体类迁移的指令)

3.3 使用

  • 修改app.config

    #指定数据库不追踪
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
    # 指定启动的模式为调试模式
    app.config["DEBUG"] = True
  • 创建管理对象: manage = Manager(app)

  • 启动: manager.run() (但是项目的启动需在终端中执行 python demo.py runserver )

在使用了manger启动项目的时候,如需开启相关服务,如debug。。。

在终端启动的时候加参数 python demo,py runserver --host 0.0.0.0

配置app.config: app.config["DEBUG"] = True

from flask_migrate import Migrate, MigrateCommand
migrate = Migrate(app, db)
manage.add_command("db", MigrateCommand)

3.4 迁移

  • python run.py db init

    作用:执行项目和数据库的初始化操作

    特点:一个项目中只执行一次即可

  • python run.py db migrate

    作用:将编辑好的实体类生成一个中间文件并保存

    特点:只要检测到实体类有更改,就会生成中间文件

  • python run.py db upgrade

    作用:将中间文件映射到数据库

from flask import Flask, request, render_template, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql+pymysql://root:Asd.1234@127.0.0.1:3306/run02"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["DEBUG"] = True
app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"] = True

db = SQLAlchemy(app)
manage = Manager(app)
migrate = Migrate(app, db)
manage.add_command("db", MigrateCommand)

六、模型

1. 增

  • 创建实体类对象,并为对象的属性赋值

    user = Users()

    user.username = "Chancey"

    user.age = 30

    user.isActive = True

    user.birthday = "2019-01-01"

  • 将实体对象保存回数据库对象

    db.session.add(user)

    db.session.commit(user)

user = Users(username, age, email)
db.session.add(user)
db.session.commit()

2. 查

2.1 .基于db.session

参数:要查询的列,如果查询多个列的话使用,隔开,如果要查询所有列,参数为实体类名

2.1.1 query()

实例:

  • 查询Users实体类中的id,username

    db.session.query(Users.id, Users.username)

  • 查询Users实体类中所有的列

    db.session.query(Users)

  • 查询Users以及wife实体类所有的列

    db.session.query(Users, Wife)

返回值:返回一个Query对象(SQL语句)

# 返回一个query对象
query = db.session.query(Users.id,Users.uaername)
print(query)
print("type:",type(query))

2.1.2 查询执行函数

作用:在query的基础上得到最终的查询结果

语法: db.session.query(xxx) 查询执行函数()

函数:

  • all()

    以列表的方式返回所有的数据

  • first()

    以实体对象的方式返回第一条数据,没有查询到数据则返回None

  • first_or_404()

    效果同上,没查询到结果则响应404

  • count()

    返回查询结果的数量

s = db.session.query(Users.username, Users.age, Users.email).all()
for i in s:
    print(i.username, i.age, i.email)

user = db.session.query(Users).first()
print(user.username, user.age, user.email)

print(db.session.query(Users).count())

2.1.3 查询过滤器函数

作用:在 db.session.query() 追加条件

语法: db.session.query(xx).执行函数()

函数:

filter()
filter_by()
limit()
offset()
order_by()
group_by()

返回值:均是query对象

实例:

  • 查询年龄大于25的信息

    db.session.query(Users).filter(Users.age>25).all()

  • 查询id为2的User信息

    db.session.query(Users).filter(Users.id==2).first()

  • 查询idActive为true并且年龄大于30的

    db.session.query(Users).filter(Users.isActive==True).filter(Users.age>30).all()

  • 获取前5条数据

    db.session.query(Users).limit(5).all()

  • 对表中的所有数据按照id倒序排序

    db.session.query(Users).order_by("id desc").all()

(1) or_

导入: from sqlalchemy improt or_

使用:

查询isActive为True或者年龄不小于30的

db.session.query(User).filter(or_(User.isActive==True,User.age>=30)).all()

(2) like

模糊查询like主要使用实体类属性所提供的的 like() 函数

查询email中包含an的信息

db.session.query(Users).filter(Users.email.like("%an%")).all()

(3) in_

模糊查询in,需要使用实体类提供的属性 in_ 函数完成

查询年龄是30、17、45的*

db.session.query(Users).filter(Users.age.in_([30,17,45])).all()

(4) between

模糊查询between…and…需要使用实体类属性提供的between(值1,值2)

查询年龄在30到45之间的

db.session.query(Users).filter(Users.age.between(30,45)).all()

2.2 基于Models

Models.query.查询过滤(条件参数).查询执行函数()

例如: Users.query.filter(Users.id>3).all()

3. 删除

user = db.session.query(Users).filter_by(id=5).first()
db.session.delete(user)
db.session.commit()
@app.route('/delete')
def delete_view():
    id = request.args.get("id")
    user = Users.query.filter_by(id=id).first()
    db.session.delete(user)

    url = request.headers.get("referer", '/query_all')
    return redirect(url)

4.修改

  • 保存
user = Users.query.filter_by(id=5).first()
user.uaername = "老杨"
user.passwd = "abcdefg"
return "修改成功"
@app.route('/update', methods=["GET", "POST"])
def update_view():
    if request.method == "GET":
        id = request.args.get('id') # 获取前端传过来的id
        user = Users.query.filter_by(id = id).first() # 根据id查询出对应的实体对象
        return render_template("03-update.html", u = user)# 将实体对象放到前端页面显示
    else:
        id = request.form['id']
        username = request.form["username"]
        passwd = request.form["passwd"]

        user = Users.query.filter_by(id = id).first() #查

        user.uaername = username # 改
        user.passwd = passwd # 改

        db.session.add(user) # 保存

        return redirect("/info?tiaojian=")