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. 变量代码块
-
模板中使用变量
语法:
{{ 变量名(key) }}
-
从视图函数中获取相关的变量,传递到模板文件中
语法:
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")
根据视图函数解析对应的URLurl_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 组成
-
请求起始行
协议、请求方式、资源路径
-
请求消息头
使用kry-value字典的方式存储相关信息
-
请求主体
get请求如果携带数据,以参数的形式直接拼接在URL后边(
?key1=value1&key2=value2
)只有POST方式才会有请求主体
2. 响应
2.1 响应信息
服务端接收到请求并且处理之后,返回给客户端的信息(数据)
2.2 组成
-
响应起始行
协议、响应状态码、原因短句
-
响应消息头
描述响应回来的数据,以
key:value
存储 -
响应主体
保存响应数据
四、Flask中的HTTP
1. requests
在requests对象中封装了所有跟当前请求相关的信息
使用:
-
引入:
from flask import request
-
使用:在视图函数中获取request对象内部的信息
-
属性:
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>
-
获取请求中的数据
获取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即使未携带参数,直接获取字典的值,返回为空
-
页面重定向
由服务器端通知浏览器重新向新的地址发送请求
引入
redirect
使用函数
redirect("重定向地址")
视图函数中返回
return redirect("重定向地址")
-
页面源
当前的请求是从哪一个源地址发起的,保存在请求消息头中(
"Referer":""
) -
文件上传
使用表单控件
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
列选项
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=")