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()

系统自带的转换器包括以下几种: 

string 默认类型,接受不带斜杠的任何文本
int 接受正整数
float 接受正浮点值
path 接收string但也接受斜线
uuid 接受UUID(通用唯一识别码)字符串 xxxx-xxxx-xxxxx-xxxxx

方式二: 自定义转换器

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

app = 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() 把过滤器添加到原查询上,返回一个新查询
filter_by() 把等值过滤器添加到原查询上,返回一个新查询
limit() 使用指定的值限定原查询返回的结果
offset() 偏移原查询返回的结果,返回一个新查询
order_by() 根据指定条件对原查询结果进行排序,返回一个新查询
group_by() 根据指定条件对原查询结果进行分组,返回一个新查询

查询结果的方法:

all() 以列表形式返回查询的所有结果
first() 返回查询的第一个结果,如果未查到,返回None
first_or_404() 返回查询的第一个结果,如果未查到,返回404
get() 返回指定主键对应的行,如不存在,返回None
get_or_404() 返回指定主键对应的行,如不存在,返回404
count() 返回查询结果的数量
paginate() 返回一个Paginate分页器对象,它包含指定范围内的结果
having 返回结果中符合条件的数据,必须跟在group by后面,其他地方无法使用。

注意:  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. 关联查询:

backref 在关系的另一模型中添加反向引用,用于设置外键名称,在1查多的 类似django里面的related选项
primary join 明确指定两个模型之间使用的连表条件
lazy 指定如何加载关联模型数据的方式。参数值: select(立即加载,查询所有相关数据显示,相当于lazy=True) subquery(立即加载,但使用子查询) dynamic(不加载记录,但提供加载记录的查询对象)
uselist 如果为False,不使用列表,而使用标量值。 一对一关系中,需要设置relationship中的uselist=Flase,其他数据库操作一样。
secondary 指定多对多关系中关系表的名字。 多对多关系中,需建立关系表,设置 secondary=关系表
secondary join 在SQLAlchemy中无法自行决定时,指定多对多关系中的二级连表条件

一对一的关系:

# 学生表
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"
原文地址:https://www.cnblogs.com/fdsimin/p/13891959.html