Flask框架
打开pycharm编译器,新建一个Flask项目,选择提前建好的虚拟环境 。
项目结构:
static:静态资源文件,可以直接被浏览器访问
templates:模版文件,必须在项目的python代码中进行渲染给前端,浏览器才可访问
app.py:python的程序文件
返回信息:可以是字符串、html标签、模版
请求流程:
Flask对象注册的路由
路由映射给相应函数
函数进行和数据交互
数据进行展示给前端
from flask import Flask app = Flask(__name__) @app.route('/')
def hello_world():
return 'Hello World!' if __name__ == '__main__':
app.run() # 默认只能本机访问。app.run(host='0.0.0.0',debug=True)
""" 运行:在Terminal终端中进入到项目的文件夹下,键入python app.py运行 """
视图函数
状态码http
2xx:请求成功
3xx:重定向
4xx:客户端错误
5xx:服务器错误
路由route
from flask import Flask, render_template
app = Flask(__name__) @app.route("/orders/",methods=["GET", "POST"]) # 可以请求的方式为:GET、POST。默认只支持get请求方法,想要支持其他方法需要设置 methods=['请求方法'] 参数
def orders():
return "Orders" @app.route("/users/")
def users():
return "<h1>Users</h1>" # 返回的可以是html标签 @app.route('/index/')
def hello_world(): result = render_template("index.html") # 对模版进行渲染,为返回给前端作准备
print(result) # 获取打印的是html标签字符串
return result # 返回给前端页面展示 @app.route("/goods/<any(a,b):id>/") # 路由可以是/goods/a;也可以是/goods/b
def goods(id): # 可以将路径中的信息以参数的形式传给试图函数 print(id)
print(type(id))
return "Goods Info" if __name__ == '__main__':
app.run(host="0.0.0.0", debug=True, port=5000)
在run( )中,启动前还可以添加参数
- debug: 是否开启调试模式,debug=True表示开启后修改python代码会自动重启
- host: 主机,默认是127.0.0.1,表示本机。指定为 '0.0.0.0'代表所有ip都可访问
- port: 启动指定服务器的端口号,默认5000
- threaded: 是否开启多线程
在@app.route('/path/<param>/')中,参数默认param 是字符串类型,也可以设置如下类型:
- str: 遇到‘/’就停止匹配
- path: 可以匹配任意字符
- int: 整数型
- float: 浮点数
- uuid: 唯一标示
- any: 列举出一些中的某一个
在@app.route('/path/<param>/',methods=["GET", "POST"])中,methods参数默认是get。想要支持其他方法需要设置 methods=['请求方法'] 参数
- GET: 拿,获取。从服务器获取数据获取资源
- POST: 提交,创建、认证。用来向服务器提交数据
- DELETE:删除数据。告诉服务器要删除某些数据
- PUT: 提交,全量更新。用来向服务器提交数据,通常PUT用来更新数据。需更新所有的字段信息
- PATCH: 提交,差量更新。用来向服务器提交数据,通常PATCH用来更新数据,只更新变更的字段信息
在pycharm中的Tools的RESTful Web Service可以动态模拟各种请求
视图views
- Request
request全局对象: 客户端发送给服务器的数据请求后。Flask根据客户端的请求报文自动生成request对象,request对象不可修改
客户端发送的请求- 里面包含了客户端的各种信息- Request不是客户端创建的- 是框架根据客户端的数据(请求报文)创建的- 属于Flask中的内置对象
request属性:
request.url: 完整请求地址
request.base_url: 去掉GET参数的url
request.host_url: 只有主机和端口号的url
request.path: 路由中的路径
request.method: 请求方式
request.remote_addr: 请求的客户端地址
request.args: GET请求参数
request.form: POST请求参数
request.files: 文件上传
request.headers: 请求头
request.cookie: 请求中的cookie
ImmutableMultiDict是类字典的数据结构,与字典的区别是:可以存在相同的键。args和form都是ImmutableMultiDict的对象
ImmutableMultiDict中数据获取的方式:dict['uname'] 或 dict.get('uname')。获取指定key对应的所有值:dict.getlist('uname')
- Response
服务器返回给客户端的数据。response是伴随request出现的,不能主动出现。由开发者创建
返回类型:返回字符串、返回模板、返回标签、返回Response、返回重定向、终止 abort(404)
Response返回方式:
1. 直接返回Response对象;return Response("login success")
2. 通过 make_response(date,code) 返回。date:返回的数据内容;code:状态吗
3. 返回文本内容,状态码。return "登陆成功" 或者:return 404
4. 返回模版。return render_template('req.html')
Response响应类型:
1. 直接的响应请求
2. 重定向请求:return redirect("/") 或者动态获取:return redirect(url_for('蓝图名.视图函数名'))
3. 终止请求: abort(400)
4. 捕获异常: @路由名.errorhandler(状态码)
@bp.route('/resp/')
def resp(): # 通过 render_template 返回字符串。响应类型是 str,页面最终会以字符串的形式返回到前端页面
# response = render_template('req.html') # 通过 make_response 创建一个相应。响应类型是 Flask.wrappers.Response 对象
response = make_response("make_response") print(type(response))
return response @bp.route('/redi/')
def redi(): # redirect 的第一个参数 是需要跳转的地址
# 相应类型:<class 'werkzeug.wrappers.Response'>
# 设置格式:蓝图名.视图函数 response = redirect(url_for('blue.req'))
print(type(response))
return response
返回类型
from flask import Flask, render_template, request, Response, make_response, redirect, url_for, abort
app = Flask(__name__) @app.route('/', methods=["GET", "POST", "PUT", "DELETE"]) # 允许请求的方式 设置methods参数。默认是get
def hello_world(): # 首页视图函数
print(request) # 获取ruquest请求对象。request属于flask中的内置对象,全局可调用
print(request.method) # 获取request请求对象的请求方式
if request.method == "GET":
print(request.remote_addr) # 获取其请求对象远端的IP
print(request.args) # args是获取get的请求参数。打印ImmutableMultiDict([])对象
print(request.args.get("username")) # 获取get请求中url中的用户名参数
print(request.args.get("hobby")) # 获取get请求中url中携带的爱好参数
print(request.args.getlist("hobby")) # 获取get请求中url中携带的爱好参数列表
return render_template('index.html') # 渲染给html页面
elif request.method == "POST":
return "听说你是POST"
elif request.method == "PUT":
return "PUT没听过" @app.route("/register/") # 实现注册功能的视图函数
def register():
return render_template('register.html') # 页面渲染,返回给前端html页面
@app.route("/doregister/", methods=["GET", "POST"])
def do_register():
print(request.form) # form是获取post请求的参数
username = request.form.get("username") # 获取post请求的username参数
password = request.form.get("password") # 获取post请求的password参数
print(username, password) # 打印获取的参数
return "注册成功" # 假装 数据存储成功 @app.route("/login/") # 实现登陆功能的视图函数
def login():
return render_template('login.html') # 页面渲染,返回给前端html页面
@app.route("/dologin/", methods=["POST"])
def do_login():
print(request.form) # form是获取post请求的参数
username = request.form.get("username") # 获取post请求的username参数
password = request.form.get("password") # 获取post请求的password参数
print(username, password) # 打印获取的参数
# return '登陆成功' # 假装 数据读取、数据校验
# response = Response("login success") # 返回response信息
response = make_response("login fail", 400) # 返回response信息。返回登陆失败
return response @app.route("/users/")
def users():
return redirect("/") # 重定向到根目录
# return redirect(url_for('do_register')) # 重定向到do_register视图函数上。也可以在html文件中使用实现页面跳转
# abort(400) # 返回响应数字。终止请求 if __name__ == '__main__':
app.run(debug=True, host="0.0.0.0")
# 终止请求,自动设置模拟异常
@bp.route('/error/404')
def error_404():
# 模拟服务器资源不存在的情况,如果资源不存在,则告诉浏览器 404错误
abort(404) @bp.route('/error/500')
def error_500():
# 模拟服务器代码报错的情况,abort之后的代码不会再被执行
abort(500) # 捕获异常,未抛出的异常不会被捕获到
@bp.errorhandler(404)
def error_hander(exception): # exception获取错误参数
print(exception)
return '服务器走丢了' @bp.errorhandler(500)
def server_error(exception):
print(exception)
return "服务器开小差了"
<!-- Font Awesome Icons -->
<link rel="stylesheet" href="{{ url_for("static",filename="css/fontawesome-free/css/all.min.css") }}">
<!-- Theme style -->
<link rel="stylesheet" href="{{ url_for("static",filename="css/adminlte.min.css") }}"> {# static是静态文件名字,动态获取静态文件的路径,然后和后面的相对路径拼接上获取真实路径 #} <ul>
{% for student in student_list %}
<li><a href="{{ url_for("student",id=student.id) }}">{{ student.name }}</a></li>
{% endfor %}
</ul> {# 实现页面跳转,id是student视图函数的参数 #}
- url_for
url_for 函数⽤于构建指定函数的 URL。它把 路由.函数名. 作为第一个参数。
可以接受任意个关键字参数,每个关键字参数对应 URL 中的变量。未知变量将添加到 URL 中作为查询参数。
@bp.route('/args/<string:name>/<int:age>/')
def args(name, age):
print(request.args)
return 'name: {}, age: {}'.format(name, age) @bp.route('/redi/')
def redi():
# /args/Tom/19/?a=1&b=2
return redirect(url_for('blue.args', name='Tom', age=19, a=1, b=2))
url_for
import random
from flask import Blueprint, render_template
from sqlalchemy import distinct, not_, and_, or_
from app.models import User, db, Grade, Student, Profile bp = Blueprint("blue",__name__) # 向模版中传递参数去渲染。列表类型。
@bp.route("/articles/")
def article_list():
# 从数据库中获取数据,类型是:list。list中的每一个数据是一个对象;一个对象对应数据库中的一行数据
# articles = Article.query.with_entities('id', 'title', 'content').all() list_str = ["Tom", "Suan", "Linli", "Bob", "Sufei"]
return render_template("article.html", list_str=list_str) # 从路由中获取参数。字符串类型。
@bp.route("/articles/<string:article_id>/")
def article_detail(article_id):
return article_id
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
{% for foo in list_str %} {# 页面跳转URL:http://127.0.0.1:5000/articles/4/?dee=4&abc=566。article_id=foo 实现对参数的传递 #}
<li><a href="{{ url_for('blue.article_detail', article_id=foo, dee=4, abc=566) }}">{{ foo }}</a>
</li>
{% endfor %}
</ul>
</body>
</html>
模版语言
模版在程序中主要是呈现给用户的界面展示的,在MTV中充当T的角色,实现了VT解耦合。
开发中VT的对应关系是:N:M的关系。一个V可以调用任意T,一个T可以被任意V调用。
Flask中使用Jinja2模版引擎。Jinja2由Flask的作者开发,是一个现代化设计和友好的python模版语言。模仿的是django的模版语言
优点:
1. 速度快,被广泛使用
2. html设计和后端python开发分离
3. 减少python复杂度
4. 非常灵活,快速和安全
5. 提供了控制、继承等高级功能
模版处理分为两个过程:
1. 加载,在模版中使用视图函数中传递过来的数据
2. 渲染,在视图中通过关键字参数的形式传递参数
模版代码包含两部分:
1. 静态html
2. 动态插入的代码段
模版语法分类:
1. 变量:模版中的变量,{{ var }}
视图传递给模版的数据、前面定义出来的数据、变量不存在的话默认忽略
2. 标签:模版中的标签,{% tag %}
控制逻辑、使用外部表达式、创建变量、宏定义
- 变量 {{ var }}
from flask import Flask, render_template app = Flask(__name__) @app.route('/') # 传入参数给html,模版渲染
def hello_world():
return render_template('FlaskTemplate.html',msg='然后你没有带伞') if __name__ == '__main__':
app.run(debug=True)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body> <h1>听说今天有雷阵雨</h1>
<h3>{{ msg }}</h3> </body>
</html>
- 结构标签 {% tag %}
1. 父类中结构标签 block:
{% block xxx %} block :块操作、坑。父模板挖坑子模板填坑。
{% endblock %} endblock:挖坑继承体现的是化整为零的操作
2. 子类中结构标签 extends:
{% extends 'xxx' %} 继承、扩展。子模板可以填充父类中的块坑。没有填充的块会自动优化掉
{% super() %} 继承后保存父块中的内容
{# base文件中挖的坑信息 #}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
{# 头部信息 挖的坑#}
{% block header %}
{% endblock %} {# 轮播信息 挖的坑#}
{% block banner %}
{% endblock %} {# 内容信息 挖的坑#}
{% block content %}
{% endblock %} {# 尾部信息 挖的坑#}
{% block footer %}
{% endblock %}
</body>
</html>
{# home.html模板 继承自 base.html模板 #}
{% extends 'base.html' %} {# 对继承的header进行数据填充 #}
{% block header %}
<h1>子类模版继承父类模版的坑,填坑</h1>
{% endblock %}
{# home1.html模版 继承自 home.html模版。home模版中的信息,home1都会继承过来 #}
{% extends 'home.html' %} {# 如果孙类中从新赋值新的信息给子类中的坑,子类中的信息会被覆盖掉 #}
{% block header %}
{# 禁止覆盖其父类中的信息 #}
{{ super }}
<h2>子类中的信息,会被孙类覆盖掉</h2>
{% endblock %}
{# 孙类中继续挖坑,在其子类中还可以继续填坑 #}
{% block content %}
{% block left %}
{% endblock %} {% block right %}
{% endblock %}
{% endblock %}
block:块,坑。块没有填充,会被自动优化掉,不会报错
首次出现代表定义一个块代表界面的一种规划;
二次出现代表对既有块填充;块中可以继续规划新的块
三次出现代表对既有块的填充,会覆盖上一次的内容,不想被覆盖,使用{{ super() }}。
extends:继承,模板的继承。block + extends:化整为零
3. 包含 include:
{% include %} include 包含,将其他html文件的内容包含进来,体现的是 由零到一 的概念。效率没有上面的高
{# header.html #}
<h2>这是一个头</h2>
{# content.html #}
<h2>这是一个内容</h2>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
{% include 'home.html' %}
{% include 'content.html' %}
</body>
</html>
4. 宏定义 marco:
{% marco hello(name) %}
{{ name }}
{% endmarco %} marco 宏定义,可以在模版中定义函数,在其他地方调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
{# 编写宏 #}
{% macro hello(name) %}
<h1>你好{{ name }}这是一个宏标签</h1>
{% endmacro %}
{# 调用宏 #}
{{ hello('小明') }}
{{ hello('小红') }}
</body>
</html>
{% from 'xxx' import xxx %} 宏定义可导入
{# haha.html文件 #}
{% macro haha() %}
<h1>哈哈大笑</h1>
{% endmacro %} <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
{# 导入宏 #}
{% from 'haha.html' import haha %}
{# 使用宏 #}
{{ haha() }}
</body>
</html>
- 功能标签
1. 循环控制 {% for item in items %}......{% endfor %}
循环器loop
loop.first 选择第一个数据
loop.last 选择最后一个数据
loop.index 带序号显示数据
loop.revindex 反转序号显示
loop.index0 从0序号开始显示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
{% for habby in habbies %}
{# 带序号的显示爱好信息 #}
<li>{{ loop.index }}{{ habby }}</li>
{% endfor %}
</body>
</html>
2. 逻辑控制 {% if exp %}...{% elif exp %}...{% else exp %}...{% endif %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
{% for habby in habbies %}
{% if loop.first %}
<li style="color:red">{{ habby }}</li>
{% elif %}
<li style="color: green">{{ habby }}</li>
{% else %}
<li>{{ habby }}</li>
{% endif %}
{% endfor %}
</body>
</html>
- 过滤器 {{ var|过滤器|过滤器 }}
过滤器名称:capitalize;lower;upper;title;trim;reverse;format;striptags 渲染之前,将值中标签去掉
过滤器名称:sort;sum;length;first;last;default;safe 将返回数据中的标签渲染出来,确保数据不是js破坏代码再用
{% for habby in habbies %}
{% if loop.first %}
<li style="color:red">{{ habby }}</li>
{% elif %}
<li style="color: green">{{ habby|reverse }}</li>
{% else %}
<li>{{ habby|upper }}</li>
{% endif %}
{% endfor %}
数据模型
Flask 默认没有提供任何数据库操作的API,可选择任意合适自己项目的数据库:可用原生数据库语、也可用ORM实现功能(SQLAlchemy 或 MongoEngine)
ORM(Object Relational Mapping) 对象关系映射。将数据库转为面向对象的操作,通过操作对象就可实现对数据的CRUD。如业务模型和数据库DB之间的翻译机
- ORM 优点:
开发效率高,设计灵活可以轻松完成复杂查询
移植性高,可以对接多种数据库,实现了防SQL注入
易于理解,便于维护(将数据转换为面向对象编程)
- ORM 缺点:
执行效率低,因为需要将对象的操作转换为数据库的SQL。对于复杂操作可能没有支持
- 原生SQL缺点:
代码利用率低、条件复杂代码语句越长、有很多相似的语句。
一些SQL是在业务逻辑中拼出来的,若修改SQL需要了解业务逻辑,
直接写SQL容易忽视SQL问题(SQL注入问题)
- 安装
安装 flask-sqlalchemy:pip install flask-sqlalchemy
安装 pymysql驱动 :pip install pymysql
- 数据类型和操作
常见字段
Integer 整型数字 SmallInteger 小型整数 BigInteger 大型整数 Float 浮点类型 Numeric 数字型
String 字符串 Text 文本 Unicode 编码 Unicode Text 编码文本 Boolean 布尔
Date 时间 Time 时间 DateTime 时间 Interval 二进制 LargeBinary 二进制
常见约束
primary_key 主键 autoincrement 自增 unique 唯一 index 索引,常差字段加索引
nullable 是否为空 default 默认值 ForeignKey() 外键,用来约束级联数据(db.Column( db.Integer, db.ForeignKey(xxx.id) ))
提交数据
在事务处理中,对象(数据)插入、修改、删除是基于查询的
db.session.add(object) 把对象(数据)添加到数据库中
db.session.add_all(list_obj)把一组对象添加到数据库中
db.session.commit() 将要新添加的数据进行提交
db.session.delete() 对查到的对象(数据)进行删除
import random
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy app = Flask(__name__)
# 配置连接mysql数据库的指定信息。dialect+driver://username:password@host:port/database
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:guoyapeng@localhost:3306/FlaskModel'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 设False为更好的兼容性,禁止对象追踪修改
db = SQLAlchemy(app) # 创建班级表
class Grade(db.Model): # 主表
id = db.Column(db.Integer,primary_key=True,autoincrement=True) # id字段:整型、主键、自增
name = db.Column(db.String(32),unique=True,nullable=False) # name字段:字符串型、唯一、不可为空
# 创建学生表
class Student(db.Model): # 从表
id = db.Column(db.Integer,primary_key=True,autoincrement=True) # 学生id: 整型,主键,自增
name = db.Column(db.String(32),nullable=False) # 学生姓名:整型,字符串,不为空
grade = db.Column(db.Integer,db.ForeignKey(Grade.id)) # 学生班级:整型,外键:Grade表中
# 新建数据库表的url
@app.route('/create/')
def create():
db.create_all() # 创建数据库
return '数据库表创建成功'
# 添加C 班级数据信息
@app.route('/addgrade/')
def add_grade():
grade = Grade()
grade.name = 'python%d' % random.randrange(1000) db.session.add(grade)
db.session.commit()
return '添加成功'
# 添加C 学生数据信息
@app.route("/addstudent/")
def add_student():
student = Student()
student.name = "Tom%d" % random.randrange(10000) grade_list = Grade.query.all()
grade = grade_list[random.randrange(len(grade_list))]
student.grade = grade.id db.session.add(student)
db.session.commit()
return "添加成功"
# 查询R 班级列表信息
@app.route('/grades/')
def grades():
grade_list = Grade.query.all()
return render_template('GradeLIst.html',grade_list=grade_list)
# 查询R 学生列表信息
@app.route('/students/')
def students():
student_list = Student.query.all()
return render_template('StudentList.html',student_list=student_list)
# 查询单个班级信息,并显示本班所有的学生。路由是:http://127.0.0.1:5000/grade/2/
@app.route('/grade/<int:id>/')
def grade(id):
grades = Grade.query.filter(Grade.id.__eq__(id)).all() # 查询id等于指定参数id的信息,加.all()以列表形式获取所有数据
# grades = Grade.query.filter(Grade.id == id) 同上方法。结果是:sql语句。类型是:basequery对象
grade = grades[0] # 获取列表中的第一个班级元素
student_list = Student.query.filter(Student.grade == id).all()
return render_template('Grade.html',grade=grade,student_list=student_list)
# 查询单个学生的信息。路由是:http://127.0.0.1:5000/student/2/
@app.route('/student/<int:id>/')
def student(id):
student = Student.query.get(id) # 获取指定id的学生
return render_template('student.html',student=student) if __name__ == '__main__':
app.run()
# 数据库D 对数据删除
@bp.route("/delete/")
def delete():
#删除时,必须先查询到数据,再对对象进行删除
user = User.query.get(1)
# 通过 delete 删除一个数据
db.session.delete(user)
# 对象删除完后,执行commit向数据库提交更新
db.session.commit()
return "update ok"
- 数据库连接
Python语言中的ORM(SQLAlchemy)。下载安装包针对于Flask的支持:pip install flask-sqlalchemy
sqlite数据库是一个轻量级的数据库,适合写案例、微小项目.如:博客、个人网站、手机应用开发(手机内置数据库)
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # 配置连接sqlite数据库的指定信息。
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///hello.sqlite'
# 设False为更好的兼容性,禁止对象追踪修改
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# SQLite数据库连接不需要额外驱动,也不需要用户名和密码
db = SQLAlchemy(app) # 配置连接mysql数据库的指定信息。dialect+driver://username:password@host:port/database
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:guoyapeng@localhost:3306/FlaskModel'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app) if __name__ == '__main__':
app.run()
- 数据库操作
db.create_all():创建数据库表
db.drop_all():删除数据库表
@bp.route("/create_db/")
def create_db():
"""
创建表
"""
from app.models import db
db.create_all()
return "db create"
@bp.route("/drop_db/")
def drop_db():
"""
删除表
"""
from app.models import db
db.drop_all()
return "db drop"
增删改查
- manag.py
from flask_script import Manager
from app import create_app app = create_app()
manager = Manager(app) if __name__ == '__main__':
manager.run()
- app/__init__.py
import os
from flask import Flask
from app import views
from app.models import db def create_app(): app = Flask(__name__) # sqlite3 数据库文件地址
db_file = os.path.join(app.root_path,"sqlite.db")
# 指定数据库的 URI
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + db_file # 关闭 SQLAchemy 底层对象监听功能
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
# 打印sql语句设置
app.config["SQLALCHEMY_ECHO"] = True # 使用 flask app 初始化 SQLAchemy
db.init_app(app=app) app.register_blueprint(views.bp) return app
- app/models.py
"""
flask 使用 ORM 的步骤
1。 指定数据库连接。并建立SQLAlchemy对象
app.config["SQLALCHEMY_DATABASE_URI"]
2。 定义模型类
3。 通过 ORM 将模型类 转换成 数据库中的表
4。 通过模型类进行curd
"""
import datetime
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() # 实例化SQLAlchemy class User(db.Model):
__tablename__ = "users" id = db.Column(db.Integer,primary_key=True,autoincrement=True)
name = db.Column(db.String(16),nullable=False)
age = db.Column(db.Integer,default=18) def __repr__(self): # 自定义打印效果
return "<User,id:{},name:{},age:{}>".format(self.id, self.name, self.age) """
继承 db.Model 说明此类是一个 SQLAlchemy 的模型类
db.Column 定义模型类属性对应的数据库关系 默认的表名规则:
模型名的小写,User -> user
如果是驼峰型,UserModel -> user_model
可以通过 __tablename__ 自定义表名 db。Colume 定义模型类属性对应的数据库表字段关系
默认的字段名为属性名
需要为每个属性指定数据库中使用的类型
db.Integer 整型
db.String(size) 字符串,size为最大长度
db.Text 长文本
db.DataTime 时间日期,python datetime对象
db.Boolean 布尔类型 primary_key : 是否为主键
nullable : 是否为空
unique : 唯一约束
index : 索引
autoincrement : 自增(默认为auto)
"""
- app/views.py
"""
User.query
query 是一个查询接口,BaseQuery的对象。不真正执行SQL
.all() 获得所有记录,真正执行 SQL 查询数据
.first() 获取第一条数据,真正执行 SQL 查询数据
.get(pk) 根据主键查询 .filter() where 条件。支持更丰富的查询条件
filter(User.age > 18)
filter(User.age == 18)
.filter_by() where 条件。只能通过关键字赋值
filter_by(name=XXX,age=XXX)
:return:
"""
调用的方法介绍
import random
from flask import Blueprint
from sqlalchemy import distinct, not_, and_, or_
from app.models import User, db, Grade, Student, Profile bp = Blueprint("blue",__name__)
"""-----------------创建库 删除库-----------------------"""
@bp.route("/create_db/")
def create_db():
"""
创建表
"""
from app.models import db
db.create_all()
return "db create"
@bp.route("/drop_db/")
def drop_db():
"""
删除表
"""
from app.models import db
db.drop_all()
return "db drop"
"""-------------------- 增 -------------------- """
# 数据库C操作。创建数据
@bp.route("/create/")
def create():
user = User()
user.name = "老王"
user.age = 88 db.session.add(user)
db.session.commit()
return "create" @bp.route("/creates/")
def creates():
users = []
for i in range(10):
name = "jack-{}".format(i)
age = random.randint(18,55)
u = User(name=name,age=age)
users.append(u)
# add_all 接收一组数据,实现批量新增数据
db.session.add_all(users)
db.session.commit()
return "creates"
"""--------------------- 改 --------------------"""
# 数据库U。对数据更新
@bp.route("/update/")
def update():
#更新时,必须先查询到数据,再对对象进行更新
user = User.query.get(1)
user.age = 17 # 对象修改完后,执行commit向数据库提交更新
db.session.commit()
return "update ok"
"""--------------------- 删 ------------------"""
# 数据库D。对数据删除
@bp.route("/delete/")
def delete():
#删除时,必须先查询到数据,再对对象进行删除
user = User.query.get(1)
# 通过 delete 删除一个数据
db.session.delete(user) # 对象删除完后,执行commit向数据库提交更新
db.session.commit()
return "update ok"
"""--------------------- 查 ---------------------"""
# 数据库R。查询数据
@bp.route("/read/")
def read():
# 根据主键获取 user 对象。如果主键不存在,则返回 None
user = User.query.get(2) # 查询id为3的人员信息
user = User.query.first() # 查询第一个人员信息 # filter_by 中的关键字参数的参数名为 要查询模型的属性名,即数据库字段
# 过滤查询,打印的是mysql语句。类型:<class 'flask_sqlalchemy.BaseQuery'>实例
user = User.query.filter_by(name="jack-0").first() # .first() 获取对象中的第一个数据信心
user = User.query.filter_by(age=19,name="jack").all() # .all()获取的是一组数据,类型是list。值是User模型对象列表
user = User.query.filter(User.age == 41).all() # 另外一种写法 # 按需查询 年龄为41岁人员的名字。with_entities 按需查询字段
user = User.query.with_entities(User.name).filter_by(age=41).all() # distinct 按年龄字段去重
user = User.query.with_entities(distinct(User.age)).all() # filter 中的关键字参数的参数名为 要查询模型的属性名,即数据库字段
# 条件查询。查询年龄字段小于39岁的人员信息
user = User.query.filter(User.age <= 39).all() # like 'jack%'
users = User.query.filter(User.name.startswith('jack')).all()
# like '%jack'
users = User.query.filter(User.name.endswith('jack')).all()
# like '%jack%'
users = User.query.filter(User.name.contains('jack')).all() # 指定范围内。age in [19, 38, 42]
users = User.query.filter(User.age.in_([19, 38, 42])).all() # 排序
users = User.query.order_by('age')
users = User.query.order_by(User.age.desc()) # not 非
users = User.query.filter(User.age != 19).all()
users = User.query.filter(not_(User.age == 19)).all()
# and 与
users = User.query.filter(and_(User.age == 19, User.name == 'jack-0')).all()
users = User.query.filter(User.age == 19, User.name == 'jack-0').all()
users = User.query.filter_by(age=19, name='jack-0')
# or 或
users = User.query.filter(or_(User.age == 19, User.age == 42)).all() print(users)
return 'read'
模型关系
- app/models.py
从表中,定义外键字段;主表中,定义关系。(谁声明外键谁是从表)
# 1:n Grade:Student
class Grade(db.Model):
__tablename__ = "grades" g_id = db.Column(db.Integer,primary_key=True) # 主键 created_at = db.Column(db.DateTime,default=datetime.datetime.now()) # 通过设置onupdate 在对象更新时,自动更新updated_at。可以帮助统计每天注册人数 updated_at = db.Column(db.DateTime,default=datetime.datetime.now(),onupdate=datetime.datetime.now()) g_name = db.Column(db.String(16),unique=True,nullable=False) # 注意⚠️:db.relationship() 定义一个模型关系(Colume是关系到数据库中)。提供了可以让Grade对象 g 通过g.students的方式访问N(多)的一方数据的便利性。
# 第一参数:关系N方模型的类名;第二参数backref: 反响关系引用,反向在Student方新增grade字段(属性),方便Student对象s.grade访问1方字段;第三参数lazy:加载关联关系数据的时机
# lazy = Ture/"select"时 会发送两条sql语句,一条查 one 的一方;一条查 many的一方
# lazy = False/"joined" 时 会发送一条sql语句,通过LEFT JOIN 的方式查询 one 一方 和 many 一方数据
# lazy = "dynamic" 时 会发送一条 sql语句,只查询 one 一方的数据,many 一方的关联属性是一条 Query 对象,
# 不会真实执行查询,在需要时,调用students.all() 真正去数据库中查询数据
students = db.relationship("Student",backref="grade",lazy=True) class Student(db.Model):
__tablename__ = "students" s_id = db.Column(db.Integer, primary_key=True) # 主键 created_at = db.Column(db.DateTime, default=datetime.datetime.now()) # 通过设置onupdate 在对象更新时,自动更新updated_at。可以帮助统计每天注册人数
updated_at = db.Column(db.DateTime, default=datetime.datetime.now(), onupdate=datetime.datetime.now()) s_name = db.Column(db.String(16),unique=False) s_age = db.Column(db.Integer,default=18,nullable=False) # db.ForeignKey() 用于定义外键,参数:主标的表名.主键名
grade_id = db.Column(db.Integer,db.ForeignKey("grades.g_id")) # one to one 的声明方式和 one to many 几乎一致,区别在于:
# 不使用 lazy。使用 uselist = Flase 来模拟只返回一条数据
profile = db.relationship("Profile",backref= "student",uselist=False) # 1:1 Student:Profile
class Profile(db.Model):
__tablename__ = "profiles" id = db.Column(db.Integer,primary_key=True) # 主键 created_at = db.Column(db.DateTime, default=datetime.datetime.now()) # 通过设置onupdate 在对象更新时,自动更新updated_at。可以帮助统计每天注册人数
updated_at = db.Column(db.DateTime, default=datetime.datetime.now(), onupdate=datetime.datetime.now()) phone = db.Column(db.String(16),unique=True,nullable=False) student_id = db.Column(db.Integer,db.ForeignKey("students.id"))
- 1:1
- app/views/py
@bp.route('/o2o-create/')
def o2o_create():
# p = Profile(phone='13888888881')
# db.session.add(p)
# db.session.commit()
p = Profile.query.filter_by(phone='').first() s = Student.query.first()
s.profile = p # s.profile_id = p.id db.session.commit()
return 'o2o_create' @bp.route('/o2o-read/')
def o2o_read():
s = Student.query.filter_by(name='tom-0').first()
print(s.profile.phone) p = Profile.query.filter_by(phone='').first()
print(p.student.name) # 通过Profile模型的对象p 去获取Student模型中的name属性值 return 'o2o_read'
- 1:N
- app/views.py
@bp.route("/o2m_create/")
def o2m_create():
"""
1:n 新增操作
:return:
"""
python1901 = Grade(g_name='python-1901')
python1902 = Grade(g_name='python-1902')
python1903 = Grade(g_name='python-1903') grades = [python1901,python1902,python1903]
db.session.add_all(grades)
db.session.commit() students = []
for i in range(30):
name = 'tom-{}'.format(i)
age = 18 + random.randint(0,5) s = Student(s_name=name,s_age=age) # ⚠️通过反向引用的属性赋值,也可以只通过 grade_id 进行赋值、
s.grade = random.choice(grades) # 随机选取一个班级对象。将班级对象 g 赋值给 s.grade。本质上相当于:s.grade_id = g.id
students.append(s) db.session.add_all(students)
db.session.commit()
return "o2m create ok" @bp.route("/o2m_read/")
def o2m_read():
# 通过班级查询获取班级下的所有学生
g = Grade.query.first()
# 当lazy= "dynamic" 时,g.students 是一个 Query 对象,只有通过 g.students.all() 才会发送 SQL 语句,执行查询
# print(type(g.students))
# print(g.students.all()) # 通过Student对象s获取Grade模型中的班级名字字段
s = Student.query.first()
print(s.grade.g_name)
return "o2m_read ok" @bp.route('/o2m_update/')
def o2m_update():
tom_4 = Student.query.filter_by(s_name='tom-4').first()
python_1902 = Grade.query.filter_by(g_name="python-1902").first()
# 更改学生班级信息。相当于:tom_4.grade_id = python_1902.id
tom_4.grade = python_1902
db.session.commit()
return "02m_update ok" @bp.route('/o2m_delete/')
def o2m_delete():
# 笨办法
# python_1902 = Grade.query.filter_by(name='python-1902').first()
# tom_4 = Student.query.filter_by(name="tom-4").first()
#
# python_1902.students.remove(tom_4) 添加时: python1902.student.append(s)
# db.session.commit() python_1901 = Grade.query.filter_by(name="python-1901").first()
db.session.delete(python_1901)
db.session.commit()
return "o2m_delete ok"
- N:M
# 笨办法。伪代码
Student(db.Model)
id, name
Group (db.Model)
id, name,
StudentGroup(db.Model)
id, student_id(fk,student.id), group_id(fk,group.id) s = Student(name="Tom")
db.session.add(s)
db.session.commit() g = Group(name="Html")
db.session.add(g)
db.session.commit() sg = StudentGroup()
sg.student_id = s.id
sg.group_id = g.id
db.session.add(sg)
db.session.commit()
普通方法
# 多对多(普通方式)
class User(db.Model):
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
name = db.Column(db.String(32))
age = db.Column(db.Integer,default=18) class Movie(db.Model):
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
name = db.Column(db.String(32)) class Collection(db.Model):
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
u_id = db.Column(db.Integer,db.ForeignKey(User.id))
m_id = db.Column(db.Integer,db.ForeignKey(Movie.id)) # 购物车添加
@blue.route('/getcollection/')
def getcollection():
u_id = int(request.args.get('u_id'))
m_id = int(request.args.get('m_id'))
c = Collection.query.filter(Collection.u_id == u_id).filter_by(m_id = m_id) if c.count() > 0:
print(c.first().u_id,c.first().m_id)
# print(c)
# print(type(c))
# print('i am if')
return '已经添加到了购物车中'
else:
c1 = Collection()
c1.u_id = u_id
c1.m_id = m_id
db.session.add(c1)
db.session.commit()
return 'ok'
普通方式
- app/models.py
class Student(db.Model):
__tablename__ = "students" s_id = db.Column(db.Integer, primary_key=True) # 主键 created_at = db.Column(db.DateTime, default=datetime.datetime.now())
# 通过设置onupdate 在对象更新时,自动更新updated_at。可以帮助统计每天注册人数
updated_at = db.Column(db.DateTime, default=datetime.datetime.now(), onupdate=datetime.datetime.now()) s_name = db.Column(db.String(16),unique=False) s_age = db.Column(db.Integer,default=18,nullable=False) # db.ForeignKey() 用于定义外键,参数:主标的表名.主键名
grade_id = db.Column(db.Integer,db.ForeignKey("grades.g_id")) # one to one 的声明方式和 one to many 几乎一致,区别在于:
# 不使用 lazy。使用 uselist = Flase 来模拟只返回一条数据
profile = db.relationship("Profile",backref= "student",uselist=False) # many to many 的关系定义,本质是双向的一对多关系
# secondary 指定多对多关系中的关系表
groups = db.relationship('Group', secondary='student_groups',
backref=db.backref('students', lazy='dynamic'), lazy='dynamic') """ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++""" # n:m Group:Student
class Group(db.Model):
__tablename__ = 'groups' id = db.Column(db.Integer, primary_key=True) # 主键 created_at = db.Column(db.DateTime, default=datetime.datetime.now())
updated_at = db.Column(db.DateTime, default=datetime.datetime.now(), onupdate=datetime.datetime.now()) name = db.Column(db.String(32), unique=True, nullable=False) # db.Table() 第一个参数:表名;其他参数:字段定义
student_groups = db.Table('student_groups',
db.Column('id', db.Integer, primary_key=True),
db.Column('student_id', db.Integer, db.ForeignKey('students.id')),
db.Column('group_id', db.Integer, db.ForeignKey('groups.id'))
)
- app/views.py
@bp.route('/m2m-init/')
def m2m_init():
g1 = Group(name='python')
g2 = Group(name='html')
g3 = Group(name='golang') db.session.add_all([g1, g2, g3])
db.session.commit() return 'm2m_init ok' @bp.route('/m2m-create/')
def m2m_create():
# 获取 组模型的对象
python = Group.query.filter_by(name='python').first()
html = Group.query.filter_by(name='html').first() # 根据学生主键 获取学生对象
tom_0 = Student.query.get(1)
tom_1 = Student.query.get(2) # 根据 组对象中的students字段添加学生tom_0
python.students.append(tom_0) # 根据 学生对象的groups字段添加组html
tom_1.groups.append(html) db.session.commit()
return 'm2m_create ok' @bp.route('/m2m-delete/')
def m2m_delete():
python = Group.query.filter_by(name='python').first()
html = Group.query.filter_by(name='html').first() tom_0 = Student.query.get(1)
tom_1 = Student.query.get(2) # 删除一个不属于 student 对象的 group 会报错
# tom_0.groups.remove(python) # 清空 html 组内的所有学生
html.students = [] db.session.commit()
return 'm2m_delete ok'
数据模型 增删封装
- app/models.py
import datetime
from flask_sqlalchemy import SQLAlchemy
# 实例化SQLAlchemy
db = SQLAlchemy() class ModelMixin():
"""
Mixin 是一种多继承的模式。主要为子类增加扩展功能
""" @classmethod
def save_all(cls,objs):
"""
定义一个类方法(不需要实例化类就可以被类本身调用)cls 代表当前类
对一组数据进行提交,成功返回True;失败返回Flask,并数据回滚
伪代码:
users = [user, user, user]
try:
db.session.add_all(users)
db.session.commit()
except Exception as e:
db.session.rollback()
User.save_all(users)
:param objs:
:return:
"""
try:
db.session.add_all(objs) # 一次性对一组数据进行存储提交
db.session.commit()
except Exception as e:
db.session.rollback() # 没有存储成功,进行回滚操作
return False def save(self):
"""
定义一个类(必须实例化类之后才能被调用)。self 代表当前类的实例
对一组数据进行提交,正确返回True;错误返回Flask,并数据回滚
伪代码:
user = User(name='jack', age=55)
try:
db.session.add(user)
db.session.commit()
except Exception as e:
db.session.rollback()
user.save()
:return:
"""
try:
db.session.add(self)
db.session.commit()
return True
except Exception as e:
db.session.rollback()
return False def delete(self): try:
db.session.delete(self) # 删除操作
db.session.commit()
return True
except Exception as e:
db.session.rollback()
return False class User(ModelMixin,db.Model):
__tablename__ = "users" id = db.Column(db.Integer,primary_key=True,autoincrement=True)
name = db.Column(db.String(16),nullable=False)
age = db.Column(db.Integer,default=18) def __repr__(self): # 自定义打印效果
return "<User,id:{},name:{},age:{}>".format(self.id, self.name, self.age)
- app/views.py
import random
from flask import Blueprint, render_template
from sqlalchemy import distinct, not_, and_, or_
from app.models import db, Grade, Student, Profile
from app.models import User bp = Blueprint('blue', __name__) # 增添
@bp.route('/create/')
def create():
users = []
for i in range(10):
name = 'tom-{}'.format(i)
age = random.randint(18, 88)
u = User(name=name, age=age)
users.append(u) User.save_all(users)
return 'create ok!' # 删除
@bp.route('/delete/')
def delete():
# 删除时,必须先查询到数据,在对对象进行删除
user = User.query.get(13)
user.delete() return 'delete'