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 "<h1>欢迎访问</h1>"

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

二、 定义路由

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

2. 无参数

定义路由

@app.route('/地址')

定义视图函数

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

例如:

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

3. 带参数

变量:<变量名>

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

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

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

4. 类型转换器

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

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

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

例如

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

@app.route('/calaute/<num1>/<num2>')
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/<name>,执行同一个函数,返回相应的内容

@app.route("/show")
@app.route("/show/list")
@app.route("/show/<name>")
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())
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>show</title>
</head>
<body>
    <h1>模板文件</h1>
    <p>列表、元组、字典都可以使用[key/index]和点语法访问</p>
    <p>name:{{ d["name"] }}</p>
    <p>age:{{ d.age }}</p>
    <p>name:{{ d.dic["name"] }}</p>
    <p>宠物名:{{ d.cat.name }}</p>
    <p>动作:{{ d.cat.play() }}</p>
</body>
</html>

2. 过滤器

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

  • upper转大写字母
  • lower转小写字母
  • title首字母大写
  • first获取第一个
  • last获取最后一个
  • length获取列表长度
  • default如果变量未赋值,可采用默认值
  • trim去掉字符串两边的空格
  • ....

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

<!-- 在python文件视图函数中自行添加s1变量,赋值"   hello world" -->
<p>原版:{{ d.s1 }}</p>
<p>大写:{{ d.s1|upper }}</p>
<p>小写:{{ d.s1|lower }}</p>
<p>首字母大写:{{ d.s1|title }}</p>
<p>获取第一个:{{ d.s1|first }}</p>
<p>获取最后一个:{{ d.s1|last }}</p>
<p>获取长度:{{ d.s1|length }}</p>
<p>去掉两边空格:{{ d.s1|trim }}</p>
<p>未赋值的变量:{{ d.s2|default("默认变量") }}</p>

3. 控制代码块

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

3.1 if

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

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

<!-- 多重分支 -->
{% if 条件1 %}
	pass
{% elif 条件2 %}
	pass
{% elif 条件3 %}
	pass
{% endif %}

例如:

    {% if d.age < 18 %}
        <h3>{{ d.age }}岁未成年</h3>
    {% elif d.age < 30 %}
        <h3>{{ d.age }}岁青成年</h3>
    {% elif d.age < 50 %}
        <h3>{{ d.age }}岁中成年</h3>
    {% else %}
        <h3>{{ d.age }}岁老年人</h3>
    {% endif %}

3.2 for

{% for 变量 in 可跌对象 %}
{% endfor %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>show</title>
    <style>
    .c1{
        background: red;
    }
    .c2{
        background: orange;
    }
    .c3{
        background: cyan;
    }
    </style>
</head>
<body>
<table border="2px">
    <tr>
        <td>姓名</td>
        <td>年龄</td>
    </tr>
{% for item in info %}
    <tr
     {% if loop.first %}
        class = c1
     {% elif loop.last %}
        class = c2
     {% else %}
        class = c3
     {% endif %}
    >
        <td>{{ item.name }}</td>
        <td>{{ item.age }}</td>
    </tr>
{% endfor %}
</table>
</body>
</html>
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当前循环的次数,默认从1开始计算
  • loop.index0当前循环的次数,从0开始计算
  • loop.first是否为第一次循环,值为True表示第一次循环
  • 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

    <link rel="stylesheet" href="{{ url_for('static',filename='path') }}>"

5.模板继承

与类的继承相似

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

实现:

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

    {% block 块名 %}
    	<h1>父模板</h1>
    {% endblock %}
    
  • 子模板中继承父模板

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

    {% block 块名 %}
    	<h1>子模板</h1>
    {% endblock %}
    
<!--路由自行配置,不再展示-->
<!-- 01-base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        {% block web_title %}
        父模板
        {% endblock %}
    </title>
</head>
<body>
    <h4>
        {% block title %}
        这里是标题
        {% endblock %}
    </h4>
    <p>
        {% block content %}
        这里是内容
        {% endblock %}
    </p>
</body>
</html>
<!--路由自行配置,不再展示-->
<!-- 02-index.html -->
{% 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

<h2>子模板</h2>
<p>协议:{{ params.scheme }}</p>
<p>请求方式:{{ params.method }}</p>
<p>请求的参数:{{ params.args }}</p>
<p>请求的参数的内容:{{ params.args.uname }}</p>
<p>请求的参数的内容:{{ params.args.age }}</p>
<p>cookies:{{ params.cookies }}</p>
<p>资源路径:{{ params.path }}</p>
<p>资源路径:{{ params.full_path }}</p>
  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"向服务器发送文件,因为文件,图片,音频等都是为禁止数据,必须设置表单的提交方式和编码类型

    <form action="" method="post" ectype="multipart/form-data">

    服务器端使用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列选项

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 "<用户名:%r 年龄:%r 邮箱:%r>"%(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)
  • 提交(使用manger可自动提交)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=")
posted @ 2019-09-03 08:58  ChanceySolo  阅读(567)  评论(0编辑  收藏  举报