flask基础学习
1. flask基础视图(只需一个py文件), flask可以安装0.12.5版本, 较稳定:
from flask import Flask app=Flask(__name__) class Config(): # 加载项目配置 debug = True app.config.from_object(Config) @app.route("/index") # 路由 def index(): return "hello flask" if __name__ == '__main__': app.run(host="0.0.0.0", port=5000) # 可以另设访问IP和端口号
2. 路由中传递参数: 两种方式
方式一: 系统自带的转换器
from flask import Flask app=Flask(__name__) class Config(): # 加载项目配置 debug = True app.config.from_object(Config) @app.route("/index/<int:user_id>") # 带参数的路由 def index(user_id): return "hello flask"+str(user_id) if __name__ == '__main__': app.run()
系统自带的转换器包括以下几种:
默认类型,接受不带斜杠的任何文本 | |
int | 接受正整数 |
float | 接受正浮点值 |
path | 接收string 但也接受斜线 |
uuid |
方式二: 自定义转换器
from flask import Flask app=Flask(__name__) # 1.导入转换器基类 from werkzeug.routing import BaseConverter # 2.自定义转换器 class MobileConverter(BaseConverter): def __init__(self, map, *args, **kwargs): super().__init__(map) self.regex = "1[3-9]\d{9}" # 3.把自定义转换器添加到flask默认的转换器字典中,也就是和原来的int,float等放在一块 app.url_map.converters["mob"] = MobileConverter # 4.使用 @app.route("/index/<mob:mobile>") def index(mobile): return "hello flask"+mobile if __name__ == '__main__': app.run(debug=True)
3. Http请求:from flask import Flask, requestapp=Flask(__name__)
# methods限制客户端的http请求方法,注意这里与django不一样,flask默认没有内置csrf攻击防范 @app.route("/index", methods=["get", "post", "patch", "put", "delete"]) def index(): print(request.method) # 请求方式 GET
print(request.full_path) # /index?username=bob&username=jack print(request.query_string) # 查询参数,字节类型 b'username=bob' print(request.args) # 处理过后的查询参数 ImmutableMultiDict([('username', 'bob'), ('username', 'jack')]) """ ImmutableMultiDict是一个由flask封装的字典类,在字典的基础上,提供了一些其他的方法而已。 格式: ImmutableMultiDict([('键', '值'), ('键', '值')]) 字典本身不支持同名键的,ImmutableMultiDict类解决了键同名问题 操作ImmutableMultiDict,完全可以操作字典操作,同时还提供了get,getlist方法, 获取指定键的1个值或多个值 """ print(request.args.to_dict()) # {'username': 'bob'} 将MutilDict转换为普通字典, # 注意键同名的问题,一般键同名的话只会显示第一个参数,如果加上flat=False 则只会有一个键, 键对应的值转变成一个列表存储,还有一个 to_list()方法,是转换为列表的,某些情况下不能用 print(request.path) # 路由 /index print(request.url) # 完整路由 http://127.0.0.1:5000/index?username=bob print(request.form) # 记录请求中的html表单数据 print(request.files) print(request.json) # 记录ajax请求的json数据 print(request.headers) """ Test: Y Content-Type: text/plain User-Agent: PostmanRuntime/7.13.0 Accept: */* Cache-Control: no-cache Postman-Token: 8f220852-df99-48a0-9adb-efd5a437cd7b Host: 127.0.0.1:5000 """
print(request.headers.to_list()) # 获取请求头
print(request.cookies) # Cookie: pass=abc print(request.data) """ 记录请求体的数据,并转换为字符串 只要是通过其他属性无法识别转换的请求体数据 最终都是保留到data属性中 Accept-Encoding: gzip, deflate Content-Length: 32 Connection: keep-alive """ return "hello flask" if __name__ == '__main__': app.run(debug=True)
4. Http响应: 默认响应html文本,也可以返回 JSON格式,或其他格式
重定向:
from flask import Flask, request, make_response, url_for, redirect app=Flask(__name__) @app.route("/index", methods=["get", "post"]) def index111(): return "我是index111" @app.route("/home", methods=["get", "post"]) def home(): # return redirect(url_for("index")) # 默认反向解析的路由地址是视图名 return redirect(url_for(endpoint="index")) # 当视图名与路由名不一样时,可以使用endpoint来做反向解析
# 跳转的原理,实际就是利用HTML文档中的元信息
response.headers["Location"] = "http://www.baidu.com"
if __name__ == '__main__': app.run(debug=True)
响应json格式:
from flask import Flask, request, make_response, url_for, redirect, jsonify app=Flask(__name__) @app.route("/index", methods=["get", "post"]) def index111(): data = [{ "username": "bob", "passsword": 123 }] # return make_response("<h1>测试</h1>") # 响应HTML文本 return jsonify(data) if __name__ == '__main__': app.run(debug=True)
支持响应图片或者文件下载
from flask import Flask, request, make_response, url_for, redirect, jsonify, Response app=Flask(__name__) @app.route("/index", methods=["get", "post"]) def index111(): if request.args.get("user") == "123": print(request.args) with open("sun.bmp","rb") as f: content = f.read() response = make_response(content) response.headers["Content-Type"] = "image" return response else: return "没有权限" # 支持下载 # with open("123.zip", "rb") as f: # response.headers["Content-Type"] = "application/zip" # return response if __name__ == '__main__': app.run(debug=True)
自定义状态码和响应头:
from flask import Flask, request, make_response, url_for, redirect, jsonify, Response app=Flask(__name__) @app.route("/index", methods=["get", "post"]) def index111(): # 方式一:在返回文本的时候,跟上状态码 # return "666", 400 # 方式二: response = make_response("ok") response.headers["Test"] = "Y" # 自定义响应头 response.status_code = 400 # 状态码 return response if __name__ == '__main__': app.run(debug=True)
5. cookie:
设置cookie:
from flask imoprt Flask,make_response @app.route('/set_cookie') def set_cookie(): resp = make_response('this is to set cookie') resp.set_cookie('username', 'xiaoming', max_age=3600) return resp
获取cookie:
from flask import Flask,request @app.route('/get_cookie') def resp_cookie(): resp = request.cookies.get('username') return resp
6. session:
设置session:
from flask import session @app.route('/set_session') def set_session(): # 设置session的时候一定要配置SECRET_KEY, session需要加密 session['username'] = 'xiaoming' return 'ok!'
获取session:
from flask import session @app.route('/get_session') def get_session(): return session.get('username')
7. 四种请求钩子:
在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如: - 在请求开始时,建立数据库连接; - 在请求开始时,根据需求进行权限校验; - 在请求结束时,指定数据的交互格式; 为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设置的功能,即请求钩子。 请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子: - before_first_request - 在处理第一个请求前执行[项目初始化时的钩子] - before_request - 在每次请求前执行 - 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用 - after_request - 如果没有抛出错误,在每次请求后执行 - 接受一个参数:视图函数作出的响应 - 在此函数中可以对响应值在返回之前做最后一步修改处理 - 需要将参数中的响应在此参数中进行返回 - teardown_request: - 在每次请求后执行 - 接受一个参数:错误信息,如果有相关错误抛出 - 需要设置flask的配置DEBUG=False,teardown_request才会接受到异常对象。
代码:
from flask import Flask, request, make_response class DevConfig(): SECRET_KEY = "692hrwdsafjl#%" app=Flask(__name__) app.config.from_object(DevConfig) @app.before_first_request def before_first_request_demo(): """ 这个钩子会在项目启动后第一次被用户访问时执行 可以编写一些初始化项目的代码,例如,数据库初始化,加载一些可以延后引入的全局配置 """ print("项目启动时,第一次请求之前执行的操作,下次请求时就不执行这个操作了") @app.before_request def before_request_demo(): """ 这个钩子会在每次客户端访问视图的时候执行 可以在请求之前进行用户的身份识别,以及对于本次访问的用户权限等进行判断。.. """ print("每次请求来之前执行的操作") # 请求的时候不用响应 @app.after_request def after_request_demo(response): # 一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作, 需要传一个response response.headers["Content-Type"] = "application/json" print("每次响应结束之后执行的操作") return response # 响应的时候要返回response @app.teardown_request def teardown_request_demo(exc): # 在项目关闭了DEBUG模式以后,则异常信息就会被传递到exc中,我们可以记录异常信息到日志文件中,需要传递一个exc形参 if isinstance(exc, Exception): print(exc) @app.route("/index") def index(): print("请求来啦!") return "ok" if __name__ == '__main__': app.run(debug=False)
8. 自定义终端脚本以及自定义命令
from flask import Flask, request, make_response from flask_script import Manager, Command, Option import os app=Flask(__name__) manage = Manager(app) class SonAppCommand(Command): option_list = [ Option("--name", "-n", dest="name") ] def run(self, name): os.mkdir(name) with open("%s/app.py"%name, "w") as f: f.write("from flask import Flask\n\napp = Flask(__name__)") manage.add_command("startapp", SonAppCommand()) if __name__ == '__main__': manage.run()
flask使用的jinja2模板渲染与Django内置的模板渲染非常相似
9. 过滤器: 过滤器的本质就是函数。有时候我们不仅仅只是需要输出变量的值,我们还需要修改变量的显示,甚至格式化、运算等等,而在模板中是不能直接调用 Python 中的某些方法,这时候就用到了过滤器。过滤器支持链式操作.
字符串操作
-
safe:禁用转义
<p>{{ '<em>hello</em>' | safe }}</p>
-
capitalize:把变量值的首字母转成大写,其余字母转小写
<p>{{ 'hello' | capitalize }}</p>
-
lower:把值转成小写
<p>{{ 'HELLO' | lower }}</p>
-
upper:把值转成大写
<p>{{ 'hello' | upper }}</p>
-
title:把值中的每个单词的首字母都转成大写
<p>{{ 'hello' | title }}</p>
-
reverse:字符串反转
<p>{{ 'olleh' | reverse }}</p>
-
format:格式化输出
<p>{{ '%s is %d' | format('name',17) }}</p>
-
striptags:渲染之前把值中所有的HTML标签都删掉, 如果内容中,存在大小于号的情况,则不要使用这个过滤器,容易误删内容。
<p>{{ '<em>hello</em>' | striptags }}</p> <p>{{ "如果x<y,z>x,那么x和z之间是否相等?" | striptags }}</p>
-
truncate: 字符串截断
<p>{{ 'hello every one' | truncate(9)}}</p>
列表操作
-
first:取第一个元素
<p>{{ [1,2,3,4,5,6] | first }}</p>
-
last:取最后一个元素
<p>{{ [1,2,3,4,5,6] | last }}</p>
-
length:获取列表长度
<p>{{ [1,2,3,4,5,6] | length }}</p>
-
sum:列表求和
<p>{{ [1,2,3,4,5,6] | sum }}</p>
-
sort:列表排序
<p>{{ [6,2,3,1,5,4] | sort }}</p>
-
去重, unique得出生成器, 需转换为列表取值
<p>{{ [1,3,3,5,5,1] | unique | list }}</p>
语句块过滤, 可对长语句进行过滤
{% filter upper %}
#一大堆文字#
{% endfilter %}
自定义过滤器:
方式一:
自定义一个过滤器的方法, 然后通过app.add_template_filter(自定义过滤器的名称, 前端中使用此过滤器的名称)注册
# 自定义过滤器 def do_list_reverse(old_list): # 因为字典/列表是属于复合类型的数据,所以改动数据的结构,也会应该能影响到原来的变量 # 通过list新建一个列表进行操作,就不会影响到原来的数据 new_list = list(old_list) new_list.reverse() return new_list # 注册过滤器 app.add_template_filter(do_list_reverse, "lrev")
方式二: 用@app.template_filter(前端中使用的过滤器名称)来装饰自定义的过滤器
@app.template_filter('lrev') def do_list_reverse(old_list): new_list = list(old_list) new_list.reverse() return new_list
隐藏手机号案例:
from flask import Flask, render_template app=Flask(__name__) @app.template_filter("mobile") def mobile(data, string): return data[:3] + string + data[-4:] @app.route("/index") def index(): data = {} data["user_list"] = [ {"id": 1, "name": "张三", "mobile": "13112345678"}, {"id": 2, "name": "张三", "mobile": "13112345678"}, {"id": 3, "name": "张三", "mobile": "13112345678"}, {"id": 4, "name": "张三", "mobile": "13112345678"}, ] return render_template("index.html", **data) if __name__ == '__main__': app.run(debug=True)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {% for user in user_list %} {{ user.mobile | mobile("****") }} {% endfor %} </body> </html>
9. 模板继承: 与Django的模板继承非常相似, 不支持多继承, 不过支持多级继承.
10. flask中解决CSRF攻击:
安装:
pip install flask_wtf
导入:
from flask.ext.wtf import CSRFProtect CSRFProtect(app)
表单中使用:
<form method="post" action="/"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" /> </form>
11. flask-sqlalchemy组件:
安装:
pip install flask-sqlalchemy -i https://pypi.tuna.tsinghua.edu.cn/simple
如果数据库是链接MySQL的话, 需要安装mysqldb的驱动
pip install flask-mysqldb -i https://pypi.tuna.tsinghua.edu.cn/simple
如果安装驱动报错的话, 需要进行一下步骤:
sudo apt-get install libmysqlclient-dev python3-dev 运行上面的安装命令如果再次报错如下: dpkg 被中断,您必须手工运行 ‘sudo dpkg --configure -a’ 解决此问题。 则根据提示执行命令以下命令,再次安装mysqlclient sudo dpkg --configure -a apt-get install libmysqlclient-dev python3-dev 解决了mysqlclient问题以后,重新安装 flask-mysqldb即可。 pip install flask-mysqldb -i https://pypi.tuna.tsinghua.edu.cn/simple
数据库连接配置:
class Config(): # 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型 SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1:3306/students?charset=utf8mb4" # 动态追踪修改设置,如未设置只会提示警告 SQLALCHEMY_TRACK_MODIFICATIONS = True # 查询时会显示原始SQL语句 SQLALCHEMY_ECHO = True
常用的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 | 二进制文件内容 |
定义模型类:
# 初始化SQLAlchemy db = SQLAlchemy() # 初始化数据库操作对象 db.init_app(app) # 初始化数据库链接 # 学生表 class Student(db.Model): __tablename__ = "tb_student" id = db.Column(db.Integer, primary_key=True, comment="ID") name = db.Column(db.String(64), index=True, comment="名称") sex = db.Column(db.Boolean, default=True, comment="性别") age = db.Column(db.SmallInteger, nullable=True, comment="年龄") email = db.Column(db.String(128), unique=True, comment="邮箱") money = db.Column(db.Numeric(8, 2), default=0, comment="钱包") # 自定义方法 def __repr__(self): return "student: %s"% self.name # 教师表 class Teacher(db.Model): __tablename__ = "tb_teacher" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) option = db.Column(db.Enum("讲师", "助教", "班主任")) def __repr__(self): return "Teacher: %s"%self.name # 课程表 class Course(db.Model): __tablename__ = "tb_course" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) price = db.Column(db.Numeric(6, 2)) def __repr__(self): return "Course: %s"%self.name
创建和删除所有的表结构:
if __name__ == '__main__': with app.app_context(): db.create_all() # 创建所有的数据表 # db.drop_all() # 删除所有的数据表 app.run(debug=True)
创建一条数据:
# 添加一条数据 @app.route("/add") def add(): student1 = Student(name="jack", sex=True, age=17, email="123456@qq.com", money=100) db.session.add(student1) db.session.commit() return "ok"
添加多条数据:
# 添加多条数据 @app.route("/add_all") def add_all(): st1 = Student(name='wang', email='wang@163.com', age=22) st2 = Student(name='zhang', email='zhang@189.com', age=22) st3 = Student(name='chen', email='chen@126.com', age=22) st4 = Student(name='zhou', email='zhou@163.com', age=22) db.session.add_all([st1, st2, st3, st4]) db.session.commit() return "ok"
删除数据:
# 删除数据 # 方式一: 删除第一个 @app.route("/delete") def delete(): student = Student.query.first() db.session.delete(student) db.session.commit() return "ok" # 方式二: 删除指定的一个(事物中使用,就是乐观锁) @app.route("/del") def deleteone(): ret = Student.query.filter(Student.name=="wang").delete() db.session.commit() return "ok"
更新数据:
# 更新数据 # 方式一:更新第一个 @app.route("/update") def update(): student = Student.query.first() student.name = "ming" db.session.commit() return "ok" # 方式二:更新指定一个 @app.route("/updateone") def updateone(): ret = Student.query.filter(Student.name=="chen").update({"money": 1000}) db.session.commit() return "ok" # 方式三:批量更新 @app.route("/updatemany") def updatemany(): ret = Student.query.filter(Student.age==22).update({Student.money: Student.money+"1000"}) db.session.commit() return "ok"
查询过滤器:
把过滤器添加到原查询上,返回一个新查询 | |
filter_by() | 把等值过滤器添加到原查询上,返回一个新查询 |
limit() | 使用指定的值限定原查询返回的结果 |
offset() | 偏移原查询返回的结果,返回一个新查询 |
order_by() | 根据指定条件对原查询结果进行排序,返回一个新查询 |
group_by() |
查询结果的方法:
以列表形式返回查询的所有结果 | |
first() | 返回查询的第一个结果,如果未查到,返回None |
first_or_404() | 返回查询的第一个结果,如果未查到,返回404 |
get() | 返回指定主键对应的行,如不存在,返回None |
get_or_404() | 返回指定主键对应的行,如不存在,返回404 |
count() | 返回查询结果的数量 |
paginate() | 返回一个Paginate分页器对象,它包含指定范围内的结果 |
having |
注意: get():参数为数字,表示根据主键查询数据,如果主键不存在返回None
Student.query.get()
all()返回查询到的所有对象
Student.query.all()
first()返回查询到的第一个对象【first获取一条数据,all获取多条数据】
Student.query.first()
12. 逻辑条件:
逻辑非,返回名字不等于wang的所有数据
@app.route("/search") def search(): student = Student.query.filter(Student.name!="ming").all() print(student) return "ok"
not_, and_, or_, in_
from sqlalchemy import and_, or_, not_ @app.route("/search") def search(): student = Student.query.filter(or_(and_(Student.sex==True, Student.age>20),and_(Student.sex==False, Student.age>22))).all() print(student) student2 = Student.query.filter(not_(Student.age==22)).all() print(student2) student3 = Student.query.filter(Student.id.in_([6,7])).all() print(student3) return "ok"
order_by:
@app.route("/search") def search(): student = Student.query.order_by(Student.name.desc(), Student.id.asc()).all() print(student) return "ok"
count(): 计数
@app.route("/search") def search(): student = Student.query.filter(or_(Student.name.endswith("g"), Student.age<20)).count() print(student) return "ok"
offset()偏移量和limit()限制: 取第二个和第三个学生信息
@app.route("/search") def search(): student = Student.query.offset(1).limit(2).all() print(student) return "ok"
13. 分页器的用法:
@app.route("/search") def search(): """ 分页查询 用法: 模型类.query.查询过滤方法().paginate(page=当前页码,per_page=每一页数据量(默认20),error_out=是否显示404页面,max_per_page=客户端设置的最大单页数据量) 注意: page参数可以不设置,默认是1,也可以在地址栏通过page参数由客户端进行设置 分页器内部会自动通过request.args.page来获取 per_page参数可以不设置,默认是20,也可以在地址栏通过per_page参数由客户端进行设置 分页器内部会自动通过request.args.perpage来获取 """ pagination = Student.query.paginate(per_page=3, error_out=False) print(pagination) # <flask_sqlalchemy.Pagination object at 0x7f649d1804a8> print(pagination.items) # 每一页的数据列表 print(pagination.total) # 总数 print(pagination.page) # 当前页码 print(pagination.pages) # 总页码 print(pagination.prev()) # 上一页分页器 print(pagination.prev().items) # 上一页分页器的数据列表 print(pagination.next()) # 下一页分页器 print(pagination.next().items) # 下一页分页器的数据列表 print(pagination.has_prev) # 是否有上一页 print(pagination.has_next) # 是否有下一页 return "ok" """分页器使用""" @app.route(rule="/list") def list(): pagination = Student.query.paginate(per_page=3) # 获取当前页面所有数据 print( pagination.items ) data = { "items": [], "pages": pagination.pages, "page": pagination.page, "has_prev": pagination.has_prev, "has_next": pagination.has_next, } for item in pagination.items: data["items"].append({ "id": item.id, "sex": "男" if item.sex else "女", "age": item.age, "name": item.name, }) if pagination.has_prev: print( pagination.prev() ) # 上一页数据的分页器对象 print( pagination.prev().items ) # 上一页数据 if pagination.has_next: print( pagination.next() ) # 下一页数据的分页器对象 print( pagination.next().items ) # 下一页数据 return render_template("list.html",pagination=pagination)
14. 分组查询: func.count, func.avg, func.min, func.max
@app.route("/group") def group(): """ 分组查询 分组查询一般都是配合聚合函数一起使用 用法: 模型类.query.group_by(模型类.字段名) """ from sqlalchemy import func """查询出男生和女生各自的总数""" # ret = db.session.query(Student.sex,func.count(Student.id)).group_by(Student.sex).all() # print(ret) # [(False, 4), (True, 5)] """查询当前不同年龄的学生数量""" # ret = db.session.query(Student.age, func.count(Student.age)).group_by(Student.age).all() # print(ret) # [(17, 2), (19, 2), (20, 2), (22, 3)] """查询男生和女生的平均年龄""" # ret = db.session.query(Student.sex, func.avg(Student.age)).group_by(Student.sex).all() # print(ret) # [(False, Decimal('18.7500')), (True, Decimal('20.6000'))] """查询男生和女生的最小年龄""" # ret = db.session.query(Student.sex, func.min(Student.age)).group_by(Student.sex).all() # print(ret) # [(False, 17), (True, 17)] """查询各个年龄段的人数总数低于3""" ret = db.session.query(Student.age, func.count(Student.age)).group_by(Student.age).having(func.count(Student.age)<3).all() print(ret) # [(17, 2), (19, 2), (20, 2)]
15. having:
"""查询各个年龄段的人数总数低于3""" ret = db.session.query(Student.age, func.count(Student.age)).group_by(Student.age).having(func.count(Student.age)<3).all() print(ret) # [(17, 2), (19, 2), (20, 2)]
16. 执行原生的sql语句: execute
"""执行原生的SQL语句""" # 查询操作 ret1 = db.session.execute("select * from tb_student").fetchall() # 获取多条数据 ret2 = db.session.execute("select * from tb_student where id=4").fetchone() # 获取1条数据 print(ret1) # [(2, 'wang', 1, 22, 'wang@163.com', Decimal('3600.00')), (3, 'zhang', 1, 20, 'zhang@189.com', Decimal('3200.00')),...] print(ret2) # (4, 'chen', 0, 17, 'chen@126.com', Decimal('10000.00')) # 修改操作[删除,修改,添加] db.session.execute("UPDATE tb_student SET money=(tb_student.money + %s) WHERE tb_student.age = %s" % (200, 22)) db.session.commit()
17. 关联查询:
在关系的另一模型中添加反向引用,用于设置外键名称,在1查多的 类似django里面的related选项 | |
primary join | 明确指定两个模型之间使用的连表条件 |
lazy | 指定如何加载关联模型数据的方式。参数值: select(立即加载,查询所有相关数据显示,相当于lazy=True) subquery(立即加载,但使用子查询) dynamic(不加载记录,但提供加载记录的查询对象) |
uselist | 如果为False,不使用列表,而使用标量值。 一对一关系中,需要设置relationship中的uselist=Flase,其他数据库操作一样。 |
secondary | 指定多对多关系中关系表的名字。 多对多关系中,需建立关系表,设置 secondary=关系表 |
secondary join |
一对一的关系:
# 学生表 class Student(db.Model): __tablename__ = "tb_student" id = db.Column(db.Integer, primary_key=True, comment="ID") name = db.Column(db.String(64), index=True, comment="名称") sex = db.Column(db.Boolean, default=True, comment="性别") age = db.Column(db.SmallInteger, nullable=True, comment="年龄") email = db.Column(db.String(128), unique=True, comment="邮箱") money = db.Column(db.Numeric(8, 2), default=0, comment="钱包") # 自定义方法 def __repr__(self): return "student: %s"% self.name # 学生详细信息表 class StudentInfo(db.Model): __tablename__ = "tb_student_info" # 设置表名 id = db.Column(db.Integer, primary_key=True, comment="ID") sid = db.Column(db.Integer, db.ForeignKey(Student.id),comment="学生ID") # 外键, # 如果是一对一,则外键放在附加表对应的模型中 # 如果是一对多,则外键放在多的表对象的模型中 student = db.relationship("Student", uselist=False, backref="info") address = db.Column(db.String(250), nullable=True, comment="家庭地址") mobile = db.Column(db.String(15), nullable=True, comment="紧急联系人") def __repr__(self): return "%s" % self.student.name
一对一关系操作:
@app.route("/search") def search(): # 获取数据[从主表读取数据,获取附加表数据] student = Student.query.get(3) print( student.info.address ) # 获取数据[从附加表读取数据,获取主表数据] student_info = StudentInfo.query.filter(StudentInfo.address=="上海").first() print(student_info.Student.name) # 添加数据[添加数据,把关联模型的数据也一并添加] student = Student(name="liu", sex=True, age=22, email="33523@qq.com", money=100) student.info = StudentInfo(address="北京") db.session.add(student) db.session.commit() # 修改数据[通过主表可以修改附加表的数据,也可以通过附加表模型直接修改主表的数据] student = Student.query.get(4) student.info.address = "湖北" db.session.commit()
一对多关系:
# 教师表 class Teacher(db.Model): __tablename__ = "tb_teacher" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) option = db.Column(db.Enum("讲师", "助教", "班主任")) course_list = db.relationship("Course", backref = "teacher", lazy=True) def __repr__(self): return "%s: %s"%(self.option, self.name) # 课程表 class Course(db.Model): __tablename__ = "tb_course" id = db.Column(db.Integer, primary_key=True) teacher_id = db.Column(db.Integer, db.ForeignKey(Teacher.id), comment="老师ID") name = db.Column(db.String(64), unique=True) price = db.Column(db.Numeric(6, 2)) def __repr__(self): return "Course: %s"%self.name
一对多关系操作:
@app.route("/search") def search(): """ 1对多,多对1关联操作 """ """1. 添加主模型,同时添加外键模型""" teacher = Teacher(name="bob",option="讲师") teacher.course_list = [ Course(name="3天Go入门",price=19.99), Course(name="7天Go入门",price=99.99), Course(name="30天Go入门",price=799.99), ] db.session.add(teacher) db.session.commit() """2. 查询主模型, 也查询外键模型""" teacher = Teacher.query.filter(Teacher.name=="bob").first() print(teacher) print(teacher.course_list) """3. 通过主模型,更新外键模型数据""" teacher = Teacher.query.filter(Teacher.name == "bob").first() teacher.course_list[0].price=999.50 db.session.commit() """4. 通过外键模型,更新主模型的数据""" course = Course.query.filter(Course.name=="3天Go入门").first() print(course.teacher) course.teacher.name="jack" db.session.commit() """5. 删除主键模型,同时也删除外键模型""" teacher = Teacher.query.filter(Teacher.name=="张三").first() for course in teacher.course_list: db.session.delete(course) db.session.delete(teacher) db.session.commit() return "ok"
多对多关系表:
"""学生和课程之间存在成绩关系""" # db.Table 用于设置多对多模型之间关联的关系表,这种表,不能在代码中被操作,只是进行关系的记录!!!! achievement = db.Table( 'tb_achievement', # 第一个参数就是表明,后续所有参数都是字段名 db.Column("student_id", db.Integer, db.ForeignKey("tb_student.id"),comment="学生"), db.Column("course_id", db.Integer, db.ForeignKey("tb_course.id"),comment="课程"), ) class Student(db.Model): """学生信息""" # 1. 表相关属性 __tablename__ = "tb_student" # 设置表明 # 2. 字段类型 # db.Column 表示当前属性对应数据库字段 id = db.Column(db.Integer, primary_key=True, comment="ID") name = db.Column(db.String(64), index=True, comment="姓名") sex = db.Column(db.Boolean, default=True, comment="性别") age = db.Column(db.SmallInteger, nullable=True, comment="年龄") email = db.Column(db.String(128), unique=True, comment="邮箱地址") money = db.Column(db.Numeric(8,2), default=0, comment="钱包") # 1. 在主键中设置关联模型属性,这个属性不是数据库的表字段,所以不会在数据库的表中显示出来. # 主要是提供给模型操作的. # 在一对一或者,多对一的时候, lazy的值不能是 dynamic.否则报错 info = db.relationship("StudentInfo", uselist=False, backref="student",lazy="subquery") # 3. 模型方法 def __repr__(self): return "%s" % self.name class Course(db.Model): # 定义表名 __tablename__ = 'tb_course' # 定义字段对象 id = db.Column(db.Integer, primary_key=True) teahcer_id = db.Column(db.Integer, db.ForeignKey(Teacher.id), comment="老师ID") name = db.Column(db.String(64), unique=True) price = db.Column(db.Numeric(6,2)) student_list = db.relationship("Achievement", backref="course",lazy=True) def __repr__(self): return '%s' % self.name # 也可以创建第三张表 class Achievement(db.Model): """学生和课程之间的成绩关系模型""" __tablename__ = 'tb_achievement' id = db.Column(db.Integer, primary_key=True) student_id = db.Column(db.Integer,db.ForeignKey(Student.id), comment="学生") course_id = db.Column(db.Integer,db.ForeignKey(Course.id), comment="课程") def __repr__(self): return '%s-%s' % (self.student,self.course)
多对多操作:
@app.route("/search") def search(): """ 多对多 """ """1. 添加主键模型, 同时把外键模型也添加""" student = Student(name="xiaoming", age=17,sex=True,money=10000,email="1231@qq.com") student.course_list = [ Course(name="3天python入门",price=39.99), Course(name="6天python入门",price=89.99), ] db.session.add(student) db.session.commit() """2. 查询其中一方模型数据,把多的另一方也查询出来""" student = Student.query.filter(Student.name == "xiaoming").first() print(student.course_list) # [3天python入门, 6天python入门] course = Course.query.filter(Course.name == "6天python入门").first() print(course.student_list) # [xiaoming] """3. 更新操作""" student = Student.query.filter(Student.name == "xiaoming").first() student.course_list[0].price=666.99 db.session.commit() """4. 删除操作""" student = Student.query.filter(Student.name == "xiaoming").first() db.session.delete(student) # 删除主模型信息,会自动同步删除关联数据表中的信息,但是外键模型不会被删除 db.session.commit() return "ok"
自主创建的第三张表:
@app.route("/") def index(): """ 多对多 """ """1. 添加主键模型,给外键模型添加数据""" course1 = Course(name="3天Python入门",price=99.99) course2 = Course(name="7天Python入门",price=399.99) db.session.add_all([course1,course2]) student = Student(name="xiaoming",age=17,sex=True, email="64123@qq.com",money=10000) student.course_list = [ Achievement(course=course1), Achievement(course=course2), ] db.session.add(student) db.session.commit() """2. 查询操作""" student = Student.query.filter(Student.name=="xiaoming").first() print(student.course_list) for achievement in student.course_list: print(achievement.course.name) return "ok"