一篇博客搞定flask基础(完结)

1.flask基础知识

1.1 flask框架介绍

Flask相比于django更加的轻量级,最核心的两个模块: Werkzeug(路由模块),模板引擎则使用 Jinja2。Flask不像django自带各种模块,用于一些小中型的项目开发。

Flask诞生于2010年,是Armin ronacher(人名)用 Python 语言基于 Werkzeug 工具箱编写的轻量级Web开发框架。

Flask 本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login,数据库Flask-SQLAlchemy),都需要用第三方的扩展来实现。比如可以用 Flask 扩展加入ORM、窗体验证工具,文件上传、身份验证等。Flask 没有默认使用的数据库,你可以选择 MySQL,也可以用 NoSQL。

其 WSGI 工具箱采用 Werkzeug(路由模块),模板引擎则使用 Jinja2。这两个也是 Flask 框架的核心。

官网: https://flask.palletsprojects.com/en/1.1.x/

官方文档: http://docs.jinkan.org/docs/flask/

Flask常用第三方扩展包:

  • Flask-SQLalchemy:操作数据库,ORM;
  • Flask-script:终端脚本工具,脚手架;
  • Flask-migrate:管理迁移数据库;
  • Flask-Session:Session存储方式指定;
  • Flask-WTF:表单;
  • Flask-Mail:邮件;
  • Flask-Bable:提供国际化和本地化支持,翻译;
  • Flask-Login:认证用户状态;
  • Flask-OpenID:认证, OAuth;
  • Flask-RESTful:开发REST API的工具;
  • Flask JSON-RPC: 开发rpc远程服务[过程]调用
  • Flask-Bootstrap:集成前端Twitter Bootstrap框架
  • Flask-Moment:本地化日期和时间
  • Flask-Admin:简单而可扩展的管理接口的框架

可以通过 https://pypi.org/search/?c=Framework+%3A%3A+Flask 查看更多flask官方推荐的扩展

1.2 flask的安装与创建

创建一个虚拟环境

mkvirtualenv flask -p python3  # -p:指定python3

安装

pip install flask==0.12.5

创建一个目录,并在pycharm中打开

mkdir -r /home/wangfan/flask/flask_demo01

创建一个main.py,并设置依赖环境(pycharm:file->settings)

一篇博客搞定flask基础(完结)_第1张图片

写flask框架的主程序

#导入Flask类
from flask import Flask

# 1.创建应用对象
app = Flask(__name__)


# 2.指定路由
@app.route('/')
def index():  # 3.视图函数
    return "

hello,world

" # 4. 返回字符串 if __name__ == '__main__': # 5. 启动项目 app.run(debug=True, port=8001, host='0.0.0.0') # 可以指定参数,由wsgiref模块提供

代码分析:

  1. 创建应用对象时,传入参数的说明

  2. 额外加载配置的说明(1.自定义类2.通过app.config.from_object(类名)注册),也有其他方式如下:

一篇博客搞定flask基础(完结)_第2张图片

# 导入Flask类
from flask import Flask

"""
import_name      Flask程序所在的包(模块),传 __name__ 就可以
                 其可以决定 Flask 在访问静态文件时查找的路径
static_path      静态文件访问路径(不推荐使用,使用 static_url_path 代替)
static_url_path  静态文件访问路径,可以不传,默认为:/ + static_folder
static_folder    静态文件存储的文件夹,可以不传,默认为 static
template_folder  模板文件存储的文件夹,可以不传,默认为 templates
"""
app = Flask(import_name=__name__)


# 编写路由视图
# flask的路由是通过给视图添加装饰器的方式进行编写的。当然也可以分离到另一个文件中。
# flask的视图函数,flask中默认允许通过return返回html格式数据给客户端。
@app.route('/')
def index():
    return "

hello world

"
# 加载项目配置 class Config(object): # 开启调试模式 DEBUG = True # flask中支持多种配置方式,通过app.config来进行加载,我们会这里常用的是配置类 app.config.from_object( Config ) # 指定服务器IP和端口 if __name__ == '__main__': # 运行flask app.run(host="0.0.0.0", port=5000)

2.flask之路由

2.1 路由的基本知识

1.路由: 一种访问地址[url]和应用程序[视图]进行一对一绑定的映射关系

2.往往在开发中,我们所说的路由,其实通常指代完成路由绑定关系的路由类

3.路由和视图的名称必须全局唯一,不能出现重复,否则报错。

4.可以通过参数method指定请求方法

2.2 有名分组

指定参数进行传递

from flask import Flask

app = Flask(__name__)


# 指定参数进行传递-->django路由:有名分组
@app.route('/user/', methods=['get'])  # 只能发送get请求
def index(user_name):
    return "hello,%s" % (user_name)


if __name__ == '__main__':
    app.run()

2.3 内置路由参数转换器

1.int:接受正整数

2.float:接受正浮点值

3.uuid:接受UUID(通用唯一识别码)字符串 xxxx-xxxx-xxxxx-xxxxx

4.string:默认类型,接受不带斜杠的任何文本

5.path:接收string但也接受斜线

from flask import Flask

app = Flask(__name__)


# 内置路由参数转换器
@app.route('/user2/')
def user(user_id):
    return 'user_id:%s' % user_id


if __name__ == '__main__':
    app.run()

原码:(werkzeug.routing.py)

DEFAULT_CONVERTERS = {
   "default": UnicodeConverter,
   "string": UnicodeConverter,
   "any": AnyConverter,
   "path": PathConverter,
   "int": IntegerConverter,
   "float": FloatConverter,
   "uuid": UUIDConverter,
}

2.4 自定义路由参数转换器

也叫正则匹配路由参数.

  1. 引入BaseConverter路由参数转换器基类
  2. 自定义路由参数转换器(继承父类的构造方法)
  3. 注册路由参数转换器到app对象中(app.url_map.converters['别名']=自定义类名
from flask import Flask

app = Flask(__name__)


# 1.引入BaseConverter路由参数转换器基类
from werkzeug.routing import BaseConverter

# 2.自定义路由参数转换器
class MobileConverter(BaseConverter):
    regex = r'1[3-9]\d{9}'

    def __init__(self, map, *args, **kwargs):
        super().__init__(map)                                            # map相当于django中的urlpatterns(所有路由的列表)

# 3.注册路由参数转换器到app对象中
app.url_map.converters['mob'] = MobileConverter    # 起别名为mob


@app.route(rule='/user3/')
def user3(user_mobile):
    return '%s' % user_mobile


if __name__ == '__main__':
    app.run()

2.5 在路径上直接写正则匹配

相当与django中的re_path()

与2.4中不同的是:正则规则,在路径上写直接当参数传至自定义类中

两种方法的本质为:改父类的regex属性

其中,注意传参时加上引号

from flask import Flask

app = Flask(__name__)

# 1.引入BaseConverter路由参数转换器基类
from werkzeug.routing import BaseConverter

# 自定义类
class MobileConverter2(BaseConverter):
    def __init__(self, map, *args, **kwargs):
        self.regex = args[0]
        super().__init__(map)


# 注册
app.url_map.converters['mob2'] = MobileConverter2


# 传参 (注意传参时加上引号)
@app.route(rule=r'/user4/')
def user4(user_id):
    return '%s' % user_id


if __name__ == '__main__':
    app.run()

2.6 路由注册和视图进行分离

通过app.add_url_rule(rule='路径',view_func=视图函数)实现

from flask import Flask

app = Flask(__name__)

def index():
    return "ok"

# 也可以让路由注册和视图进行分离
app.add_url_rule(rule="/",view_func=index)

if __name__ == '__main__':
    app.run(debug=True)

3.flask之请求与响应

3.1 请求request

属性 说明 类型
data 记录请求体的数据,并转换为字符串
只要是通过其他属性无法识别转换的请求体数据
最终都是保留到data属性中
bytes类型
form 记录请求中的html表单数据 MultiDict
args 记录请求中的查询字符串,也可以是query_string MultiDict
cookies 记录请求中的cookie信息 Dict
headers 记录请求中的请求头 EnvironHeaders
method 记录请求使用的HTTP方法 GET/POST
url 记录请求的URL地址 string
files 记录请求上传的文件列表 *
json 记录ajax请求的json数据 json

ImmutableMultiDict:这个类就是一个字典的子类,我们可以称之为类字典对象,所以可以通过字典的操作来使用(思路来源:from collection import OrderedDict)

文档: http://docs.jinkan.org/docs/flask/api.html#flask.request

原码位置:from flask.app import Request

引入:from flask import Flask,request

from flask import Flask,request

app = Flask(__name__)


@app.route(rule='/', methods=['get', 'post'])
def index():
    print('查询字符串', request.args)
    '''
    ImmutableMultiDict([
    ('name', 'xyw'), 
    ('hobby', '1'), 
    ('hobby', '2'), 
    ('hobby', '3')
    ])
    '''
    print(request.args.get('name'))   # xyw
    print(request.args.getlist('hobby'))  # ['1', '2', '3']
    print(request.args.to_dict(flat=False))  # 转成普通字典,列表 {'name': ['xyw'], 'hobby': ['1', '2', '3']}
    print(request.args.to_dict(flat=True))   # 转成普通字典,保留第一个 {'name': 'xyw', 'hobby': '1'}
    print('请求体数据(bytes类型)', request.data)
    print('请求中表单数据', request.form)  # ImmutableMultiDict([('name', 'xyw'), ('age', '48')])
    print('请求头', request.headers)
    '''
        请求头 Authorization: jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo2LCJ1c2VybmFtZSI6Inh5dyIsImV4cCI6MTYwNTI3NDU4MiwiZW1haWwiOiIifQ.rc0Sjezl_uAa836wUzExyNru1VEE3qtes6-sJPWCv-0
        User-Agent: PostmanRuntime/7.26.5
        Accept: */*
        Postman-Token: b636c222-ba16-4bae-95e3-1995b7344e5f
        Host: 127.0.0.1:5000
        Accept-Encoding: gzip, deflate, br
        Connection: keep-alive
        Content-Type: multipart/form-data; boundary=--------------------------145884211271048853446909
        Content-Length: 266
    '''
    print('请求方法', request.method)
    print('请求URL', request.url)
    print('上传的文件', request.files)
    print('请求的json数据', request.json)
    print('是否是ajax请求', request.is_json)  # True
    return "

hello,world

" if __name__ == '__main__': app.run()

3.2 响应

flask默认支持2种响应方式:

​ 1.数据响应: 默认响应html文本,也可以返回 JSON格式,或其他格式

​ 2.页面响应: 重定向

​ url_for 视图之间的跳转

响应的时候,flask也支持自定义http响应状态码

3.2.1 响应html文本

make_response

from flask import Flask, make_response

app = Flask(__name__)


@app.route('/')
def index():
    # return "

hello,world

" return make_response("

hello,world

") # 与直接返回相同 if __name__ == '__main__': app.run()

3.2.2 响应json数据

可以直接使用 jsonify 生成一个 JSON 的响应

from flask import Flask, make_response, jsonify

app = Flask(__name__)

@app.route('/')
def index():
    data = {
        'name': 'xyw',
        'age': 18
    }
    return jsonify(data)

if __name__ == '__main__':
    app.run()

3.2.3 重定向到外链接

redirect

from flask import Flask, make_response, jsonify, redirect

app = Flask(__name__)

@app.route('/user')
def user():
    return redirect('http://www.baidu.com')

if __name__ == '__main__':
    app.run()

3.2.4 重定向到其他视图

1.可以直接填写自己 url 路径

2.也可以使用 url_for 生成指定视图函数所对应的 url

from flask import url_for

from flask import Flask, make_response, jsonify, redirect, url_for

app = Flask(__name__)

@app.route('/')
def index():
    data = {
        'name': 'xyw',
        'age': 18
    }
    return jsonify(data)

@app.route('/user')
def user():
    return redirect(url_for("index"))

if __name__ == '__main__':
    app.run()

3.2.5 重定向到其他视图时携带参数

url_for()中加上endpoint='视图名', 参数=xx

from flask import Flask, make_response, jsonify, redirect, url_for

app = Flask(__name__)


@app.route('/')
def index(userid):
    return 'userid:%s' % userid


@app.route('/user')
def user():
    return redirect(url_for(endpoint="index", userid=1))


if __name__ == '__main__':
    app.run()

3.2.6 响应时添加响应头

添加响应头,支持文件上传

from flask import Flask

app = Flask(__name__)

from flask import make_response,jsonify,request
@app.route(rule="/user", methods=["get","post"])
def user():
    # 识别身份
    if request.args.get("user") == "abc":
        # 也可以返回图片,压缩包 等其他在浏览器能支持的数据,既可以支持显示图片,也可以支持显示
        with open("2.jpg","rb") as f:
            content = f.read()
            response = make_response(content)
            response.headers["Content-Type"] = "image/jpeg"
            return response
    else:
        return "没有权限"
    # 支持下载
    # with open("123.zip", "rb") as f:
    #     response.headers["Content-Type"] = "application/zip"
    #     return response

@app.route("/")
def index():
    return ""

if __name__ == '__main__':
    app.run(debug=True)

添加自定义响应头,实现页面转跳

from flask import Flask,make_response

app = Flask(__name__)

@app.route("/user1")
def index1():
    # 页面跳转
    # return redirect("http://www.baidu.com") # 跳转到站外
    # return redirect("/user") # 跳转到站内
    # return redirect(url_for("user",user_id=100)) # 通过url_for指定视图名称,直接找到对应路由进行跳转

    # 跳转的原理,实际就是利用HTML文档中的元信息
    response = make_response()
    response.headers["Location"] = "http://www.baidu.com"  # 实现页面转跳
    response.headers["Company"] = "oldboy"  # 自定义响应头
    response.status_code = 302 # 自定义响应状态吗
    return response


if __name__ == '__main__':
    app.run(port=8000)

3.2.7 Response的参数说明

  1. 上面make_response本质上就是Response
  2. Response(response=“内容”, status=“http响应状态码”,headers=自定义响应头,mimetype=“数据格式”)
from flask import Flask,request,make_response,Response,jsonify

app = Flask(__name__)

@app.route("/")
def index():
    """返回html数据"""

    # return "ok"
    # return make_response("ok") # 上面的代码是这段代码的简写
    # return Response("ok") # 上面make_response本质上就是Response

    """返回json格式数据"""
    # data = {"name":"xiaoming","age":13}
    # return jsonify(data)

    """返回其他类型数据"""
    # 关于Response常用的参数
    # Response(response="内容", status="http响应状态码",headers=自定义响应头,mimetype="数据格式")
    # return Response(response="ok",status=201,headers={"company":"hello"})
    # 返回图片信息
    with open('./1.zip',"rb") as f:
        content=f.read()
    # 判断权限,身份...
    return Response(response=content,mimetype="application/zip")

if __name__ == '__main__':
    app.run(debug=True)

4.cooike和session

4.1 cookie

4.1.1 cookie基本知识回顾

产生原因:
http协议(基于tcp)的特点:1 无连接(短连接);2 无状态.
http协议不会记录客户端和服务端的任何信息,导致服务端和客户端不能维持会话
正是由于http无状态的特性,所以出现了cookie(一种浏览器技术).请求头健值对.

工作原理:
浏览器访问服务端,带着一个空的cookie,然后由服务器产生内容,浏览器收到相应后保存在本地;
当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。

4.1.2 操作cookie

from flask import Flask, request, make_response, session

app = Flask(__name__)

@app.route('/set_cookie')
def set_cookie():
    # 设置cookie
    response = make_response('ok')
    response.set_cookie('username', 'xyw', 30)  # key=键,value=值,max_age=有效时间(秒)
    response.set_cookie('age', '108')  # 如果cookie没有设置过期时间,则默认过期为会话结束过期
    # 会话结束:浏览器关闭;意味着下一个站点下同变量名的cookie会被覆盖
    return response

@app.route('/get_cookie')
def get_cookie():
    # 获取cookie
    print(request.cookies)
    print(request.cookies.get('username'))
    return 'ok'

@app.route('/del_cookie')
def del_cookie():
    # 删除cookie
    response = make_response('ok')
    response.set_cookie('age', '', 0)  # 设置有效时间覆盖原有的cookie达到删除的目的
    return response

if __name__ == '__main__':
    app.run(port=8002)

4.2 session

4.2.1 session知识回顾

产生原因:
    1.cookie是明文存储的
    2.大小限制:
        Cookie大小上限为4KB; 
         一个服务器最多在客户端浏览器上保存20个Cookie; 
         一个浏览器最多保存300个Cookie,因为一个浏览器可以访问多个服务器。
特点:
    cookie中放的数据是密文的
    数据存在服务端,没有大小上限
    一个浏览器对应一个服务端,就是一个session

4.2.2 操作session

from flask import Flask, request, make_response, session

app = Flask(__name__)

# 需要配置一个secret_key用于session加密
class Config():
    SECRET_KEY = '123456'
app.config.from_object(Config)

@app.route('/set_session')
def set_session():
    # 设置session
    # session 存在服务器的缓存中,支持python基本数据类型作为值
    session['username'] = 'xyw'
    session['info'] = {
        'age': 11,
        'sex': False
    }
    '''
    浏览器中cookies中:
    session:eyJpbmZvIjp7ImFnZSI6MTEsInNleCI6ZmFsc2V9LCJ1c2VybmFtZSI6Inh5dyJ9.X7YptA.J4GTqRRgsnt2ijZJLIhV5X-3pq8
    '''
    return 'ok'

@app.route('/get_session')
def get_session():
    # 获取session
    ret1 = session.get('username')
    ret2 = session.get('info')
    return '%s+%s' % (ret1, ret2)

@app.route('/del_session')
def del_session():
    # 删除session
    try:
        del session['username']
        # session.clear()  # 删除所有
    except:
        pass
    '''
    删除了之后在获取得到:
    None+{'age': 11, 'sex': False}
    '''
    return 'ok'

if __name__ == '__main__':
    app.run(port=8002)

5.视图请求钩子

5.1 请求钩子作用

在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:

  • 在请求开始时,建立数据库连接;
  • 在请求开始时,根据需求进行权限校验;
  • 在请求结束时,指定数据的交互格式;

为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设置的功能,即请求钩子。

请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子:

  • before_first_request
    • 在处理第一个请求前执行[项目初始化时的钩子]
  • before_request
    • 在每次请求前执行
    • 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用
  • after_request
    • 如果没有抛出错误,在每次请求后执行
    • 接受一个参数:视图函数作出的响应
    • 在此函数中可以对响应值在返回之前做最后一步修改处理
    • 需要将参数中的响应在此参数中进行返回
  • teardown_request:
    • 在每次请求后执行
    • 接受一个参数:错误信息,如果有相关错误抛出
    • 需要设置flask的配置DEBUG=False,teardown_request才会接受到异常对象。

5.2 实例

from flask import Flask
app = Flask(__name__)

@app.before_first_request
def first_request():
    print('1.项目启动以后,首次请求后执行')

@app.before_request
def before_request():
    print('2.每次请求时,执行')

@app.after_request
def after_request(response):
    # 会将响应结果传递进来
    print('4.视图执行之后,执行')
    return response

@app.teardown_request
def have_error(exc):
    # 用来接收异常
    print('5.在after_request执行后,执行')
    '''
    1. 在debug=False时,才能接收错误被执行,在debug=True时,只执行到有错误的那一行
    2. 没有异常时,在after_request执行后,执行
    3. 在有异常时,在有异常的那一行执行后,立即执行,(视图函数,after_request不在执行)
    '''
    print(exc)

@app.route('/')
def index():
    1 / 0
    print('3.视图函数执行了')

    return 'ok'

if __name__=='__main__':
    app.run(debug=False, port=8000)

代码执行效果:

一篇博客搞定flask基础(完结)_第3张图片

有异常时:

一篇博客搞定flask基础(完结)_第4张图片

6.执行上下文

执行上下文:
    1.请求上下文 : request;session(来自于客户端)
    2.应用上下文 : current_app;g(flask 应用程序运行过程中,保存的一些配置信息)
两者区别:
	请求上下文:保存了客户端和服务器交互的数据,一般来自于客户端。
	应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如路由列表,程序名、数据库连接、应用信息等

request:封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get(‘user’),获取的是get请求的参数。

session:用来记录请求会话中的信息,针对的是用户信息。举例:session[‘name’] = user.id,可以记录用户信息。还可以通过session.get(‘name’)获取用户信息。

from flask import Flask, current_app, g
'''
执行上下文:
    1.请求上下文 : request;session(来自于客户端)
    2.应用上下文 : current_app;g(flask 应用程序运行过程中,保存的一些配置信息)
'''
app = Flask(__name__)

@app.route('/')
def index():
    print(current_app.config)  # 获取当前项目的所有配置信息
    print(current_app.url_map)  # 获取当前项目的所有路由信息
    func()
    print(g.name)
    return 'ok'

@app.before_request
def before_request():
    g.name = 'xyw'

7.Flask-Script

文档: https://flask-script.readthedocs.io/en/latest/

这个模块的作用可以让我们通过终端来控制flask项目的运行,类似于django的manage.py

安装:

pip install flask-script

7.1 manager 自带两个命令:runserver/shell

from flask import Flask
from flask_script import Manager, Command, Option

app = Flask(__name__)
# 使用flask_script启动项目
manage = Manager(app)

if __name__ == '__main__':
    '''
    manager 自带两个命令:runserver/shell
    端口和域名不写,默认为127.0.0.1:5000
    python 04Flask-Script.py runserver

    通过-h设置启动域名,-p设置启动端口
    python 04Flask-Script.py runserver -h127.0.0.1 -p8888
    '''
    manage.run()

7.2 自定义命令

1.定义一个类 继承Command
2.添加参数option_list
3.添加run方法
4.将自定义的类加到终端脚本工具中(add_command)
from flask import Flask
from flask_script import Manager, Command, Option

app = Flask(__name__)


@app.route('/')
def index():
    return 'ok'

# 1.定义一个类 继承Command
class HelloCommand(Command):
    """a simple example"""
    option_list = [  # 2.添加参数
        Option('--name', '-n', help='名称'),
        Option('--num', '-m', help='数量')
    ]

    # 3.添加run方法
    def run(self, name, num):   # 输入命令时,执行
        print('name=%s' % name)
        print(num)
        print('hello,world')

# 使用flask_script启动项目
manage = Manager(app)
# 4.将自定义的类加到终端脚本工具中(add_command)
manage.add_command('hello', HelloCommand)

if __name__ == '__main__':
    manage.run()

7.3 用自定义命令生成项目目录

from flask import Flask
from flask_script import Manager, Command, Option

app = Flask(__name__)

@app.route('/')
def index():
    return 'ok'

# 使用命令生成目录
import os
class BluePrintCommand(Command):
    option_list = [
        Option('--name', '-n', help='蓝图名称')
    ]
    def run(self, name=None):
        if name is None:
            print('蓝图名称不能为空')
            return
        if not os.path.isdir(name):
            os.mkdir(name)
        open('%s/views.py' % name, 'w')
        open('%s/models.py' % name, 'w')
        with open('%s/urls.py' % name, 'w') as f :
            f.write('''from . import views
urlpatterns = [

]
            ''')

manage.add_command('bule', BluePrintCommand)

if __name__ == '__main__':
    manage.run()

8.Jinja2模板引擎

使用Flask提供的render_template函数,该函数封装了该模板引擎

它的第一个参数为页面的文件名,后面的参数为模板中变量对应的真实值(键值对)

8.1 模板的基本使用

1.创建应用对象的时候,添加template_folder参数(存放页面的目录位置)

2.使用Flask提供的render_template函数

main.py:

from flask import Flask, render_template
# 1.创建应用对象的时候,添加template_folder参数(存放页面的目录位置)
app = Flask(__name__,
            template_folder='templates',  # html页面
            static_folder='static',       # 静态资源
            static_url_path='/static'     # 提供给外界的访问路径
            )

# 基本使用
@app.route('/')
def index():
    data = {}
    data['title'] = 'hello,world'
    data['num'] = 100
    #2.使用Flask提供的render_template函数
    return render_template('index1.html', **data)

if __name__ == '__main__':
    app.run(port=8000)

在项目目录下创建static目录,index1.html:


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{title}}title>
head>
<body>
<h1>{{num}}h1>
<a href='/show_info'>信息展示a>
<a href='/show_contain'>内置变量展示a>
<a href='/filter'>过滤器a>
<a href='/jicheng'>继承a>
body>
html>

8.2 各种数据的模板渲染及if语句和for语句的使用

Jinja2 模版中的变量代码块可以是任意 Python 类型或者对象,只要它能够被 Python 的 __str__ 方法或者str()转换为一个字符串就可以。

main.py:

from flask import Flask, render_template, session, g

app = Flask(__name__,
            template_folder='templates',  # html页面
            static_folder='static',       # 静态资源
            static_url_path='/static'     # 提供给外界的访问路径
            )

# 各种数据类型的模板渲染
@app.route('/show_info')
def show_info():
    data = {}
    data['info'] = {
        'name': 'xyw',
        'age': 18,
        'sex': True,
    }
    data['student_list'] = ['xyw1', 'xyw2', 'xyw3', 'xyw4', 'xyw5']
    data['goods_list'] = [
        {"id": 10, "name": "Python7天入门到放弃", "price": 99.9, "num": 100},
        {"id": 11, "name": "Python3天入门到放弃", "price": 99.9, "num": 100},
        {"id": 12, "name": "Python5天入门到放弃", "price": 99.9, "num": 100},
        {"id": 13, "name": "Go7天入门到放弃", "price": 99.9, "num": 100},
        {"id": 14, "name": "Go5天入门到放弃", "price": 99.9, "num": 100},
        {"id": 15, "name": "Linux7天入门到放弃", "price": 99.9, "num": 100},
    ]
    return render_template('index2.html', **data)

if __name__ == '__main__':
    app.run(port=8000)

index2.html:

访问列表中的成员,注意:点不支持负下标

在一个 for 循环块中你可以访问这些特殊的变量:

变量 描述
loop.index 当前循环迭代的次数(从 1 开始)
loop.index0 当前循环迭代的次数(从 0 开始)
loop.revindex 到循环结束需要迭代的次数(从 1 开始)
loop.revindex0 到循环结束需要迭代的次数(从 0 开始)
loop.first 如果是第一次迭代,为 True 。
loop.last 如果是最后一次迭代,为 True 。
loop.length 序列中的项目数。
loop.cycle 在一串序列间期取值的辅助函数。见下面示例程序。

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
    <h1>访问字典中的成员h1>
    <p>{{ info.name }}p>
    <p>{{ info['age'] }}p>
    <h1>访问列表中的成员,注意:点不支持负下标h1>
    <p>{{ student_list.0 }}p>
    <p>{{ student_list.2 }}p>
    <p>{{ student_list[-1] }}p>
    <p>{{ student_list[2] }}p>
    <h1>if判断h1>
    {% if info.age < 18 %}
        <p>小明请出去p>
    {% endif %}

    {% if info.age > 10 %}
        <p>小明同学p>
    {% else %}
        <p>小明小朋友p>
    {% endif %}

    {% if info.age < 10 %}
        <p>小明小朋友p>
    {% elif info.age < 18 %}
        <p>小明同学p>
    {% else %}
        <p>大明同学p>
    {% endif %}

    <h1>for循环,通常和if判断配合使用h1>
    <ul>
        {% for student in student_list %}
            {% if loop.last %}
            <li style="background-color: #000;color:#fff">{{ student }}li>
            {% else %}
            <li>{{ student }}li>
            {% endif %}
        {% endfor %}
    ul>

    <table>
        <tr>
            <th>序号th>
            <th>IDth>
            <th>名称th>
            <th>价格th>
            <th>库存th>
        tr>
        {% for goods in goods_list %}
            {% if loop.index % 2 %}
            <tr bgcolor="#faebd7">
            {% else %}
            <tr>
            {% endif %}
                <td>{{ loop.index }}td>
                <td>{{ goods.id }}td>
                <td>{{ goods.name }}td>
                <td>{{ goods.price }}td>
                <td>{{ goods.num }}td>
            tr>
        {% endfor %}
    table>
body>
html>

8.3 模板中可以直接使用的变量

可以在模板中访问一些 Flask 默认内置的函数和对象:

config/request/session/g变量/url_for()

main.py:

from flask import Flask, render_template, session, g

app = Flask(__name__,
            template_folder='templates',  # html页面
            static_folder='static',       # 静态资源
            static_url_path='/static'     # 提供给外界的访问路径
            )

# 内置变量
app.config['SECRET_KEY'] = '123456'
@app.route('/show_contain')
def show_contain():
    session['name'] = 'xyw'
    g.name = 'xyw_g'
    return render_template('index3.html', )

if __name__ == '__main__':
    app.run(port=8000)

index3.html:


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
    <h1>requesth1>
    <p>{{ request.url }}p>     {# http://127.0.0.1:8000/show_contain #}
    <p>{{ request.method }}p>
    <h1>sessionh1>
    <p>{{ session }}p>
    <p>{{ session.name }}p>
    <h1>configh1>
    <p>{{ config.DEBUG }}p>
    <h1>url_forh1>
    <p>{{ url_for('show_contain') }}p> {# /show_contain #}
    <p>{{ url_for('show_info') }}p>    {# /show_info #}
    <h1>g变量h1>
    <p>{{ g.name }}p>
body>
html>

8.4 过滤器

8.4.1 内置过滤器

字符串操作:

safe:禁用转义

capitalize:把变量值的首字母转成大写,其余字母转小写

lower:把值转成小写

upper:把值转成大写

title:把值中的每个单词的首字母都转成大写

reverse:字符串反转

format:格式化输出

striptags:渲染之前把值中所有的HTML标签都删掉

truncate: 字符串截断

列表操作:

first:取第一个元素

last:取最后一个元素

length:获取列表长度

sum:列表求和

sort:列表排序

语句块过滤:

{% filter upper %}
    #一大堆文字#
{% endfilter %}

例子:

main.py:

from flask import Flask, render_template

app = Flask(__name__,
            template_folder='templates',  # html页面
            static_folder='static',       # 静态资源
            static_url_path='/static'     # 提供给外界的访问路径
            )

# 过滤器
@app.route('/filter')
def show_filter():
    data = {}
    data['message'] = 'hello,world'
    data['image'] = ""
    data['mobile'] = '13813241123'
    return render_template('index4.html', **data)

if __name__ == '__main__':
    app.run(port=8000)

index4.html:


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
    <p>{{ message | upper }}p>
    <p>{{ image | safe }}p>
    <p>{{ message | reverse | upper }}p>
    <p>{{ '<em>helloem>' | striptags }}p>
    <p>{{ "如果x<y,z>x,那么x和z之间是否相等?" | striptags }}p>

    <p>{{ '床前明月光,疑是地上霜。' | truncate(5,False,'...', 0)}}p>

    <p>{{ [1,1,2,3,4,5,1,2,2,3,4] | unique | list }}p>
body>
html>

8.4.2 自定义过滤器

1.自定义一个方法

2.使用add_template_filter(方法名,过滤器名)注册

例(网页上隐藏手机号):

main.py:

from flask import Flask, render_template

app = Flask(__name__,
            template_folder='templates',  # html页面
            static_folder='static',       # 静态资源
            static_url_path='/static'     # 提供给外界的访问路径
            )
@app.route('/filter')
def show_filter():
    data = {}
    data['mobile'] = '13813241123'
    return render_template('index4.html', **data)
# 自定义过滤器
# 1.自定义一个方法
def do_mobile(content):
    return content[:3]+'*****'+content[-3:]
# 2.使用add_template_filter注册
app.add_template_filter(do_mobile, 'mobile')

index4.html:


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
    <p>{{ mobile | mobile }}p>
body>
html>

8.5 模板继承

与django使用方法一样

1.在母版中,遇到需要定制的地方使用{% block 名字%}{endblock}标记

2.在子版中,先引入母版{% extends 'base.html' %},再在{% block 名字%}{endblock}中写内容

3.需要用到母版中的内容,直接用{{ super() }}即可

模板继承使用时注意点:

  1. 不支持多继承
  2. 为了便于阅读,在子模板中使用extends时,尽量写在模板的第一行。
  3. 不能在一个模板文件中定义多个相同名字的block标签。
  4. 当在页面中使用多个block标签时,建议给结束标签起个名字,当多个block嵌套时,阅读性更好。
from flask import Flask, render_template

app = Flask(__name__,
            template_folder='templates',  # html页面
            static_folder='static',       # 静态资源
            static_url_path='/static'     # 提供给外界的访问路径
            )

# 模板继承
@app.route('/jicheng')
def jicheng():
    return render_template('index5.html')

if __name__ == '__main__':
    app.run(port=8000)

index5.html:

{% extends 'base.html' %}

{% block title %}子模板的标题{% endblock %}

{% block hander%}
<script>
    alert('xxx')
script>
{% endblock %}

{% block content %}
{{ super() }}
<p>子模板的内容p>
{{ super() }}
{% endblock %}

base.html


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}父级模板的标题{% endblock %}title>
    {% block hander %}{% endblock %}
head>
<body>
    {% block content %}
        <p>父模板的内容p>
    {% endblock %}
body>
html>

9.Flask中解决csrf

9.1 csrf回顾

一个典型的CSRF攻击有着如下的流程:

1.受害者登录a.com,并保留了登录凭证(Cookie)。
2.攻击者引诱受害者访问了b.com。
3.b.com 向 a.com 发送了一个请求:a.com/act=xx。浏览器会默认携带a.com的Cookie。
4.a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。
5.a.com以受害者的名义执行了act=xx。
6.攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作。

前后端分离:

随着前后端分离与单页应用的到来,我们往往在后端使用 RESTful 的方式暴露接口,前端使用 react、angular 或者 VUE 来控制渲染和交互,那么,也就不存在如何在 form 中放入一个 token 来进行 CSRF 的验证了。对于 RESTful 的接口,本质上是无状态的(stateless),而 anti-CSRF token 是依靠 session 中的状态来进行判断,那么也就无法再使用这种方式了。

在前后端进行分离后,最简单的集成方式:
1)用户通过浏览器请求某个网站例如 www.google.com,然后 DNS 转移至前端站点,获取前端资源
2)返回页面,JS,CSS 等后,浏览器进行渲染页面,这时候用户就能看到页面了
3)在页面准备好后,用户的所有操作(不论是 form 提交、还是 ajax 请求),都发送给后端服务,再通过 web service 响应,修改页面,支持业务逻辑

这个流程中,对于真正存储、修改用户数据的后端服务,是无状态的,而用户所操作的 form 是完全由前端应用控制,后端服务无法感知。所以,即使前端使用某种方式在 form 中放入了 token,但是后端也无法验证,这种 anti CSRF token 的方式是无法实现的。

资料1

资料2

9.2 解决办法

在 Flask 中, Flask-wtf 扩展有一套完善的 csrf 防护体系

安装flask_wtf

pip install flask_wtf

1.导入 flask_wtf.csrf 中的 CSRFProtect 类,进行初始化,并在初始化的时候关联 app

2.设置应用程序的 secret_key,用于加密生成的 csrf_token 的值

3.前端提交form表单时,携带

main.py:

from flask import Flask, render_template, request
# 1. 导入 flask_wtf.csrf 中的 CSRFProtect 类,进行初始化,并在初始化的时候关联 app
from flask_wtf import CSRFProtect
app = Flask(__name__, template_folder='templates')
csrf = CSRFProtect(app)
# 2. 设置应用程序的 secret_key,用于加密生成的 csrf_token 的值
app.config['SECRET_KEY'] = '123QAZ'

@app.route('/')
def index():
    return render_template('index6.html')

@app.route('/login',methods=['POST'])
def login():
    print(request.form)
    '''
    ImmutableMultiDict([
    ('csrf_token', 'IjBjMmY1NDcyZTYxYzk0MDg0ZDk5NmNmODAwNmU2YTkxYzJjMTJhMDEi.X7fChw.VQoiibCR2vCnzmvFmNiMaKLIGlI'), 
    ('username', 'xyw'), 
    ('password', '111')
    ])
    '''
    return 'ok'

if __name__ == '__main__':
    app.run(port=8000)

index6.html


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<form action="{{ url_for('login') }}" method="post">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
    帐号:<input type="text" name="username" value="">
    密码:<input type="password" name="password" value="">
    <input type="submit" value="登录">
form>
body>
html>

10.数据库的基本操作(ORM)

ORM 全拼Object-Relation Mapping,中文意为 对象-关系映射。主要实现模型对象到关系数据库数据的映射

优点 :

  • 只需要面向对象编程, 不需要面向数据库编写代码.
    • 对数据库的操作都转化成对类属性和方法的操作.
    • 不用编写各种数据库的sql语句.
  • 实现了数据模型与数据库的解耦, 屏蔽了不同数据库操作上的差异.
    • 不再需要关注当前项目使用的是哪种数据库。
    • 通过简单的配置就可以轻松更换数据库, 而不需要修改代码.

缺点 :

  • 相比较直接使用SQL语句操作数据库,有性能损失.
  • 根据对象的操作转换成SQL语句,根据查询的结果转化成对象, 在映射过程中有性能损失.

10.1 安装相关模块

flask默认提供模型操作,但是并没有提供ORM,所以一般开发的时候我们会采用flask-SQLAlchemy模块来实现ORM操作。

SQLAlchemy是一个关系型数据库框架,它提供了高层的 ORM 和底层的原生数据库的操作。flask-sqlalchemy 是一个简化了 SQLAlchemy 操作的flask扩展。

SQLAlchemy: https://www.sqlalchemy.org/

中文文档: https://www.osgeo.cn/sqlalchemy/index.html

安装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

安装flask-mysqldb时,注意

安装 flask-mysqldb的时候,python底层依赖于一个底层的模块 mysql-client模块
如果没有这个模块,则会报错如下:

Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-21hysnd4/mysqlclient/

解决方案:

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

10.2 连接数据库及创建表

连接数据库:

​ 1.配置数据库参数

​ 2.初始化SQLAlchemy,链接数据库

创建表:

1. 创建模型类
2. 根据模型创建所有的数据表(在`with app.app_context()`中`db.create_all()`)

常用的SQLAIchemy字段类型:

模型字段类型名 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.time 时间
DateTime datetime.datetime 日期和时间
LargeBinary str 二进制文件内容

常用的SQLAIchemy列约束选项:

选项名 说明
primary_key 如果为True,代表表的主键
unique 如果为True,代表这列不允许出现重复的值
index 如果为True,为这列创建索引,提高查询效率
nullable 如果为True,允许有空值,如果为False,不允许有空值
default 为这列定义默认值
from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# 1.配置数据库参数
class Config():
    # SQLALCHEMY_DATABASE_URI = "mysql://账号:密码@IP/数据库名?编码"
    SQLALCHEMY_DATABASE_URI = "mysql://root:[email protected]:3306/students?charset=utf8mb4"
    # 动态追踪修改设置,如未设置只会提示警告
    SQLALCHEMY_TRACK_MODIFICATIONS = True
    # 查询时会显示原始SQL语句
    SQLALCHEMY_ECHO = True
app.config.from_object(Config)
# 2.初始化SQLAlchemy,链接数据库
db = SQLAlchemy()  # 初始化数据库操作对象
db.init_app(app)   # 初始化数据库链接

"""创建模型类"""
# 1. 创建模型类
class Student(db.Model):
    __tablename__ = "tb_student"
    id = db.Column(db.Integer, primary_key=True,comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    age = db.Column(db.Integer, comment="年龄")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    money = db.Column(db.DECIMAL(8,2), nullable=True, comment="钱包")

    def __repr__(self):
        return self.name

class Teacher(db.Model):
    __tablename__ = "tb_teacher"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    option = db.Column(db.Enum("讲师","助教","班主任"), default="讲师", comment="教职")

    def __repr__(self):
        return self.name

class Course(db.Model):
    __tablename__ = "tb_course"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), unique=True, comment="课程名称")
    price = db.Column(db.Numeric(6, 2))

    def __repr__(self):
        return self.name
 
@app.route('/')
def index():
    return 'ok'


if __name__ == '__main__':
    # 2.根据模型创建所有的数据表
    with app.app_context():
        db.create_all()# 注意,create_all()方法执行的时候,需要放在模型的后面
# 上面这段语句,后面我们需要转移代码到flask-script的自定义命令中。
# 执行了一次以后,需要注释掉。
#删除表:db.drop_all()
    app.run()

10.3 单表的增删改查

10.3.1增

1.增加一条数据add 1)用db.session调用add方法 2)需要提交事物

2.批量增加数据 add_all()(也需要提交事物)其他方法

@app.route('/')
def index():
    '''增'''
    # 1.增加一条数据 add 1)用db.session调用add方法 2)需要提交事物
    student1 = Student(name='xyw', age=16, money=100, sex=True)
    db.session.add(student1)
    db.session.commit()
    
    # 2.批量增加数据 add_all()
    # 其他方法:https://blog.csdn.net/weixin_44777680/article/details/105654220
    data_list = [
        Student(name="xiaohui1号",age=16,money=1000, sex=True),
        Student(name="xiaohui2号",age=16,money=1000, sex=True),
        Student(name="xiaohui3号",age=16,money=1000, sex=True),
        Student(name="xiaohui4号",age=16,money=1000, sex=True),
        Student(name="xiaohui5号",age=16,money=1000, sex=True),
        Student(name="xiaohui6号",age=16,money=1000, sex=True),
    ]
    db.session.add_all(data_list)
    db.session.commit()
	 return 'ok'

if __name__ == '__main__':
    app.run()

10.3.2 查

1.get 不会报错

2.filter 写布尔值模型.query.filter(模型.字段==条件值).first()

3.加.first()或all()得到的类型:;不加是是BaseQuery类型。所以更新和修改有两种方式:1.先查后改 2.直接根据条件改(乐观锁)

@app.route('/')
def index():
    # 1.get (不会报错)
    # 根据主键ID查询一条数据,如果ID不存在,则返回None不会报错!
    student = Student.query.get(100)
    if student is None:
        print("当前学生不存在!")
    else:
        print(student)
        print(student.name,student.age) # 获取属性

    # 2.filter (写布尔值)
    # 根据查询条件获取一条数据
    # 模型.query.filter(模型.字段==条件值).first()
    student = Student.query.filter(Student.id==1)
    print('------------',student, type(student))
    '''
    打印结果:
    (1)加.first():
    (2)不加(是BaseQuery类型):
            SELECT 
                tb_student.id AS tb_student_id, tb_student.name AS tb_student_name, tb_student.age AS tb_student_age, tb_student.sex AS tb_student_sex, tb_student.money AS tb_student_money 
            FROM 
                tb_student 
            WHERE 
                tb_student.id = %s
        
    所以:
        更新和修改有两种方式:1.先查后改 2.直接根据条件改(乐观锁)
    '''
    # print(student.name,student.money)

    # 根据查询条件获取多条数据
    # 模型.query.filter(模型.字段==条件值).all()
    student_list = Student.query.filter(Student.id < 5).all()
    print(student_list, type(student_list))
    # """打印效果;
    # [xiaoming, xiaohong, xiaohui1号, xiaohui2号] 
    # """
    for student in student_list:
        print(student.name, student.money)
	return 'ok'

if __name__ == '__main__':
    app.run()

10.3.3改

1.有两种方式:1)先查后改;2)乐观锁

2.可以实现django中F函数的效果

@app.route('/')
def index():
        '''改'''
    # 方式1
    Student.query.filter(Student.name == "xyw").first().money += 999
    db.session.commit()
    # 方式2
    Student.query.filter(Student.name == "xyw").update({Student.sex: True})
    db.session.commit()
    # 实现F函数效果
    Student.query.filter(Student.name == "xyw").update({Student.money: Student.money+555})
    db.session.commit()
    return 'ok'

10.3.4删

@app.route('/')
def index():
        # 方式1
    db.session.delete( Student.query.filter(Student.name == "xiaohui6号").first() )
    # 方式2
    Student.query.filter(Student.name == "xiaohui5号").delete()
    db.session.commit()
    return 'ok'

10.3.5单表查询的拓展

1.filter支持的判断条件:== <= >= < > !=
student = Student.query.filter(Student.money != 1000.00).all()
print(student)
2.filter的模糊查询
    """
    模型.字段.like("%值%")  等价于  模型.字段.contains("值")    包含xxx
    模型.字段.like("值%")   等价于  模型.字段.startswith("值")  以xxx开头
    模型.字段.like("%值")   等价于  模型.字段.endswith("值")    以xxx结尾
    模型.字段.like("__")    值长度为2个字符的.几个下划线代表几个字符
    """
    student1 = Student.query.filter(Student.name.like('x%')).all()
    student2 = Student.query.filter(Student.name.endswith('w')).all()
    student3 = Student.query.filter(Student.name.like('___')).all()
    print(student1, student2, student3)
3.filter_by 设置精确条件查找(不用声明模型类名)
student = Student.query.filter_by(money=1000).all()
print(student)
4.filter多条件查询:and_ or_ not_
from sqlalchemy import and_, or_, not_
student1 = Student.query.filter(and_(Student.money == 1000, Student.name.like('x%'))).all()
student2 = Student.query.filter(or_(Student.money == 3208.00, Student.age < 18)).all()
student3 = Student.query.filter(not_(Student.age != 188)).all()
print(student1, student2, student3)
5.filter范围查询 模型.字段.in_([])
student = Student.query.filter(Student.age.in_([16, 18, 188])).all()
print(student)
6.order_by结果排序 (默认是升序)
"""
order_by(模型.字段.desc())或db.desc(模型.字段)    倒序
order_by(模型.字段.asc())或db.asc(模型.字段)      升序
"""
    student_list1 = Student.query.order_by(Student.age).all()
    student_list2 = Student.query.order_by(Student.age.desc()).all()
    student_list3 = Student.query.order_by(db.asc(Student.age)).all()
    print(student_list1, student_list2, student_list3)
7.count 统计结果
student_count = Student.query.filter(Student.age > 17).count()
print(student_count)  # 4
8.limit结果数量进行限制;offset指定查询的开始位置
student_list = Student.query.order_by(Student.age).offset(2).limit(3).all()
print(student_list1, student_list)
'''
    从索引为2的位置开始取,取3个
    [xiaohui1号, xiaohui2号, xiaohui3号, xiaohui4号, xyw] 
    [xiaohui3号, xiaohui4号, xyw]
 '''
9.paginate 分页器
    '''
    paginate(page=当前页码, per_page=每一页数据量, max_per_page=每一页最大数据量)
    1)当前页码,默认是从request.args["page"],如果当前参数没有值,则默认为1
    2)每一页数据量,默认是100条
    3)因为分页器有提供了一个  request.args.["per_page"]给客户端设置每一页数据量,
       所以再次限定客户端最多能设置的每一页数据量
    '''
    pagination = Student.query.filter(Student.sex==True).paginate(per_page=1)
    print('----', pagination)
    print(pagination.items)  # 获取当前页数据量
    print(pagination.has_next)  # 如果还有下一页数据,则结果为True
    print(pagination.has_prev)  # 如果有上一页数据,则结果为True
    print(pagination.page)  # 当前页页码 request.args.get("page",1)
    print(pagination.total)  # 本次查询结果的数据总量[被分页的数据量总数]
    print(pagination.pages)  # 总页码
    print('------', pagination.prev())  # 上一页的分页器对象,如果没有上一页,则默认为None
    '''
    ------ 
    '''
    print(pagination.next())  # 下一页的分页器对象,如果没有下一页,则默认为None
    if pagination.has_next:
        print(pagination.next().items)  # 下一页的数据列表
    # return render_template('list.html', pagination=pagination)
10.group_by 分组查询(func.max(), func.count(), having())
from sqlalchemy import func
# 查询男生和女生的最大年龄
ret = db.session.query(Student.sex, func.max(Student.age)).group_by(Student.sex).all()
print(ret)
# 查询男生和女生人数
ret1 = db.session.query(Student.sex, func.count(Student.sex)).group_by(Student.sex).all()
print(ret1)
11.执行原生sql语句(返回结果不是模型对象, 是列表和元祖)
   """
    db.session.execute('原生sql语句').fetchall()/fetchone()
    """
    ret = db.session.execute('select id,name,age,if(sex,"男","女") from tb_student').fetchall()
    ret = db.session.execute("select * from tb_student where id = 3").fetchone()
    print(ret)
    # 添加/修改/删除
    # db.session.execute("UPDATE tb_student SET money=(money + %s) WHERE age = %s" % (200, 22))
    # db.session.commit()
    # 查询出女生和男生中大于18岁的人数
    ret = db.session.execute(
        "SELECT IF(sex,'男','女'), count(id) from (SELECT id,name,age,sex FROM `tb_student` WHERE age>18) as stu group by sex"
    ).fetchall()
    print(ret)

完整代码:

mian.py:

from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)


class Config():
    DEBUG = True
    # 数据库链接配置
    SQLALCHEMY_DATABASE_URI = 'mysql://root:[email protected]:3306/students?charset=utf8mb4'
    # 动态追踪修改设置,如未设置只会提示警告
    SQLALCHEMY_TRACK_MODIFICATIONS = True
    # 查询时显示原始sql语句
    SQLALCHEMY_ECHO = True


# 加载配置
app.config.from_object(Config)
# 实例化数据库,关联
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(250), comment="姓名")
    age = db.Column(db.Integer, comment="年龄")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    money = db.Column(db.DECIMAL(8, 2), nullable=True, comment="钱包")

    def __repr__(self):
        return self.name


class Teacher(db.Model):
    __tablename__ = "tb_teacher"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    option = db.Column(db.Enum("讲师", "助教", "班主任"), default="讲师", comment="教职")

    def __repr__(self):
        return self.name


class Course(db.Model):
    __tablename__ = "tb_course"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), unique=True, comment="课程名称")
    price = db.Column(db.Numeric(6, 2))

    def __repr__(self):
        return self.name


@app.route('/')
def index():
    """1.filter支持的判断条件:== <= >= < > !="""
    student = Student.query.filter(Student.money != 1000.00).all()
    print(student)

    """2.filter的模糊查询
    模型.字段.like("%值%")  等价于  模型.字段.contains("值")    包含xxx
    模型.字段.like("值%")   等价于  模型.字段.startswith("值")  以xxx开头
    模型.字段.like("%值")   等价于  模型.字段.endswith("值")    以xxx结尾
    模型.字段.like("__")    值长度为2个字符的.几个下划线代表几个字符
    """
    student1 = Student.query.filter(Student.name.like('x%')).all()
    student2 = Student.query.filter(Student.name.endswith('w')).all()
    student3 = Student.query.filter(Student.name.like('___')).all()
    print(student1, student2, student3)

    """3.filter_by 设置精确条件查找(不用声明模型类名)"""
    student = Student.query.filter_by(money=1000).all()
    print(student)

    """4.filter多条件查询:and_ or_ not_"""
    from sqlalchemy import and_, or_, not_
    student1 = Student.query.filter(and_(Student.money == 1000, Student.name.like('x%'))).all()
    student2 = Student.query.filter(or_(Student.money == 3208.00, Student.age < 18)).all()
    student3 = Student.query.filter(not_(Student.age != 188)).all()
    print(student1, student2, student3)

    """5.filter范围查询 模型.字段.in_([])"""
    student = Student.query.filter(Student.age.in_([16, 18, 188])).all()
    print(student)

    """6.order_by结果排序 (默认是升序)
    order_by(模型.字段.desc())或db.desc(模型.字段)    倒序
    order_by(模型.字段.asc())或db.asc(模型.字段)      升序
    """
    student_list1 = Student.query.order_by(Student.age).all()
    student_list2 = Student.query.order_by(Student.age.desc()).all()
    student_list3 = Student.query.order_by(db.asc(Student.age)).all()
    print(student_list1, student_list2, student_list3)

    """7.count 统计结果"""
    student_count = Student.query.filter(Student.age > 17).count()
    print(student_count)  # 4

    """8.limit结果数量进行限制;offset指定查询的开始位置"""
    student_list = Student.query.order_by(Student.age).offset(2).limit(3).all()
    print(student_list1, student_list)
    '''
    从索引为2的位置开始取,取3个
    [xiaohui1号, xiaohui2号, xiaohui3号, xiaohui4号, xyw] 
    [xiaohui3号, xiaohui4号, xyw]
    '''

    """9.paginate 分页器"""
    '''
    paginate(page=当前页码, per_page=每一页数据量, max_per_page=每一页最大数据量)
    1)当前页码,默认是从request.args["page"],如果当前参数没有值,则默认为1
    2)每一页数据量,默认是100条
    3)因为分页器有提供了一个  request.args.["per_page"]给客户端设置每一页数据量,
       所以再次限定客户端最多能设置的每一页数据量
    '''
    pagination = Student.query.filter(Student.sex==True).paginate(per_page=1)
    print('----', pagination)
    print(pagination.items)  # 获取当前页数据量
    print(pagination.has_next)  # 如果还有下一页数据,则结果为True
    print(pagination.has_prev)  # 如果有上一页数据,则结果为True
    print(pagination.page)  # 当前页页码 request.args.get("page",1)
    print(pagination.total)  # 本次查询结果的数据总量[被分页的数据量总数]
    print(pagination.pages)  # 总页码
    print('------', pagination.prev())  # 上一页的分页器对象,如果没有上一页,则默认为None
    '''
    ------ 
    '''
    print(pagination.next())  # 下一页的分页器对象,如果没有下一页,则默认为None
    if pagination.has_next:
        print(pagination.next().items)  # 下一页的数据列表
    # return render_template('list.html', pagination=pagination)

    """10.group_by 分组查询(func.max(), func.count(), having())"""
    from sqlalchemy import func
    # 查询男生和女生的最大年龄
    ret = db.session.query(Student.sex, func.max(Student.age)).group_by(Student.sex).all()
    print(ret)
    # 查询男生和女生人数
    ret1 = db.session.query(Student.sex, func.count(Student.sex)).group_by(Student.sex).all()
    print(ret1)

    """11.执行原生sql语句(返回结果不是模型对象, 是列表和元祖)
    db.session.execute('原生sql语句').fetchall()/fetchone()
    """
    ret = db.session.execute('select id,name,age,if(sex,"男","女") from tb_student').fetchall()
    ret = db.session.execute("select * from tb_student where id = 3").fetchone()
    print(ret)
    # 添加/修改/删除
    # db.session.execute("UPDATE tb_student SET money=(money + %s) WHERE age = %s" % (200, 22))
    # db.session.commit()
    # 查询出女生和男生中大于18岁的人数
    ret = db.session.execute(
        "SELECT IF(sex,'男','女'), count(id) from (SELECT id,name,age,sex FROM `tb_student` WHERE age>18) as stu group by sex"
    ).fetchall()
    print(ret)

    return 'ok'

if __name__ == '__main__':
    # with app.app_context():
    #     db.drop_all()
    #     db.create_all()
    app.run()

list.html:


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
    <style>
    .page a,.page span{
        padding: 2px 6px;
        color: #fff;
        background: #6666ff;
        text-decoration: none;
    }
    .page span{
        color: #fff;
        background: orange;
    }

    style>
head>
<body>
    <table border="1" align="center" width="600">
        <tr>
           <th>IDth>
           <th>ageth>
           <th>nameth>
           <th>sexth>
           <th>moneyth>
        tr>
        {% for student in pagination.items %}
        <tr>
           <td>{{ student.id }}td>
           <td>{{ student.age }}td>
           <td>{{ student.name }}td>
           <td>{{ "男" if student.sex else "女" }}td>
           <td>{{ student.money }}td>
        tr>
        {% endfor %}
        <tr align="center">
            <td colspan="5" class="page">
                {% if pagination.has_prev %}
                首  页a>
                <a href="?page={{ pagination.prev_num }}">上一页a>
                <a href="?page={{ pagination.prev_num }}">{{ pagination.prev_num }}a>
                {% endif %}
                <span>{{ pagination.page }}span>
                {% if pagination.has_next %}
                <a href="?page={{ pagination.next_num }}">{{ pagination.next_num }}a>
                <a href="?page={{ pagination.next_num }}">下一页a>
                <a href="?page={{ pagination.pages }}">尾  页a>
                {% endif %}
            td>
        tr>
    table>
body>
html>

10.4 多表的操作之一对一

10.4.1 创建

1.创建一对一对象属性(学生表)

1.关联属性,是SQLAlchemy提供给开发者快速引用外键模型的一个对象属性,不存在于mySQL中
2.backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性
3.uselist=False表示关联一个数据

2.外键(学生详情信息表)

如果是一对一,则外键放在附加表对应的模型中
如果是一对多,则外键放在多的表对象的模型中
"""创建模型类"""
class Student(db.Model):
    __tablename__ = "tb_student"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    age = db.Column(db.Integer, comment="年龄")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    money = db.Column(db.DECIMAL(8, 2), nullable=True, comment="钱包")
    """创建一对一
    1.关联属性,是SQLAlchemy提供给开发者快速引用外键模型的一个对象属性,不存在于mySQL中
    2.backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性
    3.uselist=False表示关联一个数据
    """
    info = db.relationship('StudentInfo', backref='own', uselist=False)

    def __repr__(self):
        return 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='学生')
    address = db.Column(db.String(255), nullable=True, comment="家庭住址")
    mobile = db.Column(db.String(15), unique=True, comment="紧急联系电话")

    def __repr__(self):
        return self.own.name


class Teacher(db.Model):
    __tablename__ = "tb_teacher"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    option = db.Column(db.Enum("讲师", "助教", "班主任"), default="讲师", comment="教职")

    def __repr__(self):
        return self.name


class Course(db.Model):
    __tablename__ = "tb_course"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), unique=True, comment="课程名称")
    price = db.Column(db.Numeric(6, 2))

    def __repr__(self):
        return self.name

10.4.2 增删改查

@app.route('/')
def index():
    """添加数据"""
    student = Student(
        name='徐彦伟',
        age=13,
        sex=True,
        money=1000,
        info=StudentInfo(
            mobile='1383838438',
            address='北京市昌平区白沙路103号'
        )
    )
    db.session.add(student)
    db.session.commit()

    """查"""
    # 查 xyw 的电话
    mobile = Student.query.first().info.mobile
    print(mobile)
    # 查电话为1383838438的同学名称(反向查询)
    student = StudentInfo.query.filter(StudentInfo.mobile=='1383838438').first().own.name
    print(student)

    '''改'''
    student = Student.query.get(1)
    student.age = 188
    student.info.address = '北京市昌平区沙河镇白沙路130号'
    db.session.commit()

    Student.query.filter(Student.name == '徐彦伟').update({Student.age: 180})
    Student.query.filter(Student.name == '徐彦伟').first().info.address = '北京市昌平区沙河镇白沙路133号'
    db.session.commit()

    '''删'''
    student = Student.query.get(2)
    db.session.delete(student.info)  # 先删除外键模型,再删主模型
    db.session.delete(student)
    db.session.commit()

    return 'ok'

完整代码:

main.py:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)


class Config():
    # DEBUG调试模式
    DEBUG = True
    # 数据库链接配置
    # SQLALCHEMY_DATABASE_URI = "mysql://账号:密码@IP/数据库名?编码"
    SQLALCHEMY_DATABASE_URI = "mysql://root:[email protected]:3306/students?charset=utf8mb4"
    # 动态追踪修改设置,如未设置只会提示警告
    SQLALCHEMY_TRACK_MODIFICATIONS = True
    # 查询时会显示原始SQL语句
    SQLALCHEMY_ECHO = True
app.config.from_object(Config)
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(250), comment="姓名")
    age = db.Column(db.Integer, comment="年龄")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    money = db.Column(db.DECIMAL(8, 2), nullable=True, comment="钱包")
    """创建一对一
    1.关联属性,是SQLAlchemy提供给开发者快速引用外键模型的一个对象属性,不存在于mySQL中
    2.backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性
    3.uselist=False表示关联一个数据
    """
    info = db.relationship('StudentInfo', backref='own', uselist=False)

    def __repr__(self):
        return 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='学生')
    address = db.Column(db.String(255), nullable=True, comment="家庭住址")
    mobile = db.Column(db.String(15), unique=True, comment="紧急联系电话")

    def __repr__(self):
        return self.own.name


class Teacher(db.Model):
    __tablename__ = "tb_teacher"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    option = db.Column(db.Enum("讲师", "助教", "班主任"), default="讲师", comment="教职")

    def __repr__(self):
        return self.name


class Course(db.Model):
    __tablename__ = "tb_course"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), unique=True, comment="课程名称")
    price = db.Column(db.Numeric(6, 2))

    def __repr__(self):
        return self.name


@app.route('/')
def index():
    """添加数据"""
    # student = Student(
    #     name='徐彦伟',
    #     age=13,
    #     sex=True,
    #     money=1000,
    #     info=StudentInfo(
    #         mobile='1383838438',
    #         address='北京市昌平区白沙路103号'
    #     )
    # )
    # db.session.add(student)
    # db.session.commit()

    """查"""
    # 查 xyw 的电话
    mobile = Student.query.first().info.mobile
    print(mobile)
    # 查电话为1383838438的同学名称(反向查询)
    student = StudentInfo.query.filter(StudentInfo.mobile=='1383838438').first().own.name
    print(student)

    '''改'''
    # student = Student.query.get(1)
    # student.age = 188
    # student.info.address = '北京市昌平区沙河镇白沙路130号'
    # db.session.commit()
    #
    # Student.query.filter(Student.name == '徐彦伟').update({Student.age: 180})
    # Student.query.filter(Student.name == '徐彦伟').first().info.address = '北京市昌平区沙河镇白沙路133号'
    # db.session.commit()

    '''删'''
    # student = Student.query.get(2)
    # db.session.delete(student.info)  # 先删除外键模型,再删主模型
    # db.session.delete(student)
    # db.session.commit()

    return 'ok'

if __name__ == '__main__':
    # with app.app_context():
    #     db.drop_all()
    #     db.create_all()
    app.run()

10.5 多表的操作之一对多

10.5.1创建一对多关系

1.创建一对多关联属性

lazy:(决定了什么时候SQLALchemy从数据库中加载数据)
        1.lazy='subquery',
            查询当前数据模型时,采用子查询(subquery),把外键模型的属性也瞬间查询出来了
        2.lazy=True或lazy='select',
            查询当前数据模型时,不会把外键模型的数据查询出来,只有操作到外键关联属性时,才进行连表查询数据[执行SQL]
        3.lazy='dynamic',
            查询当前数据模型时,不会把外键模型的数据查询出来,只有操作到外键关联属性并操作外键模型具体属性时,才进行连表查询数据[执行SQL]

2.创建外键(一对多)

"""创建模型类"""
class Student(db.Model):
    __tablename__ = "tb_student"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    age = db.Column(db.Integer, comment="年龄")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    money = db.Column(db.DECIMAL(8, 2), nullable=True, comment="钱包")
    """创建一对一关联属性
    1.关联属性,是SQLAlchemy提供给开发者快速引用外键模型的一个对象属性,不存在于mySQL中
    2.backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性
    3.uselist=False表示关联一个数据
    """
    info = db.relationship('StudentInfo', backref='own', uselist=False)

    def __repr__(self):
        return 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='学生')
    address = db.Column(db.String(255), nullable=True, comment="家庭住址")
    mobile = db.Column(db.String(15), unique=True, comment="紧急联系电话")

    def __repr__(self):
        return self.own.name


class Teacher(db.Model):
    __tablename__ = "tb_teacher"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    option = db.Column(db.Enum("讲师", "助教", "班主任"), default="讲师", comment="教职")
    '''创建一对多关联属性
    lazy:(决定了什么时候SQLALchemy从数据库中加载数据)
        1.lazy='subquery',
            查询当前数据模型时,采用子查询(subquery),把外键模型的属性也瞬间查询出来了
        2.lazy=True或lazy='select',
            查询当前数据模型时,不会把外键模型的数据查询出来,只有操作到外键关联属性时,才进行连表查询数据[执行SQL]
        3.lazy='dynamic',
            查询当前数据模型时,不会把外键模型的数据查询出来,只有操作到外键关联属性并操作外键模型具体属性时,才进行连表查询数据[执行SQL]
    '''
    course_list = db.relationship('Course', uselist=True, backref='teacher', lazy='subquery')

    def __repr__(self):
        return self.name


class Course(db.Model):
    __tablename__ = "tb_course"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), unique=True, comment="课程名称")
    price = db.Column(db.Numeric(6, 2))
    '''创建外键(一对多)'''
    teacher_id = db.Column(db.Integer, db.ForeignKey(Teacher.id), comment='老师')

    def __repr__(self):
        return self.name

10.5.2 增删改查

@app.route('/')
def index():
    """添加数据"""
    # 从teacher表中增加
    teacher = Teacher(
        name='teacher01',
        option='班主任',
        course_list=[
            Course(name="抓羊", price="9.90"),
            Course(name="挨打",price="19.90"),
            Course(name="炸房子",price="29.90"),
        ]
    )
    db.session.add(teacher)
    db.session.commit()
    # 从课程表中增加
    course = Course(
        name='python从入门到入坟',
        price='199',
        teacher=Teacher(name='teacher02', option='讲师')
    )
    db.session.add(course)
    db.session.commit()

    '''查'''
    # 查询teacher01教哪些课
    course_list = Teacher.query.filter(Teacher.name == 'teacher01').first().course_list
    print(course_list, type(course_list))
    for course in course_list:
        print(course.price, course.name)
    # 查询 python从入门到入坟 这个课是哪个老师教(反向查询)
    teacher = Course.query.filter(Course.name == 'python从入门到入坟').first().teacher.name
    print(teacher)

    '''改'''
    # 将课程 挨打 改为 打人
    Course.query.filter(Course.name == '挨打').first().name = '打人'
    db.session.commit()
    # 将课程 打人 改为 teacher02 教
    Course.query.filter(Course.name == '打人').first().teacher_id = \
        Teacher.query.filter(Teacher.name == 'teacher02').first().id
    db.session.commit()

    '''删除数据'''
    # 将 teacher01 删除
    teacher = Teacher.query.filter(Teacher.name == 'teacher01').first()
    for course in teacher.course_list:
        db.session.delete(course)
    db.session.delete(teacher)
    db.session.commit()

    return 'ok'

完整代码:

main.py:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)


class Config():
    # DEBUG调试模式
    DEBUG = True
    # 数据库链接配置
    # SQLALCHEMY_DATABASE_URI = "mysql://账号:密码@IP/数据库名?编码"
    SQLALCHEMY_DATABASE_URI = "mysql://root:[email protected]:3306/students?charset=utf8mb4"
    # 动态追踪修改设置,如未设置只会提示警告
    SQLALCHEMY_TRACK_MODIFICATIONS = True
    # 查询时会显示原始SQL语句
    SQLALCHEMY_ECHO = True
app.config.from_object(Config)
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(250), comment="姓名")
    age = db.Column(db.Integer, comment="年龄")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    money = db.Column(db.DECIMAL(8, 2), nullable=True, comment="钱包")
    """创建一对一关联属性
    1.关联属性,是SQLAlchemy提供给开发者快速引用外键模型的一个对象属性,不存在于mySQL中
    2.backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性
    3.uselist=False表示关联一个数据
    """
    info = db.relationship('StudentInfo', backref='own', uselist=False)

    def __repr__(self):
        return 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='学生')
    address = db.Column(db.String(255), nullable=True, comment="家庭住址")
    mobile = db.Column(db.String(15), unique=True, comment="紧急联系电话")

    def __repr__(self):
        return self.own.name


class Teacher(db.Model):
    __tablename__ = "tb_teacher"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    option = db.Column(db.Enum("讲师", "助教", "班主任"), default="讲师", comment="教职")
    '''创建一对多关联属性
    lazy:(决定了什么时候SQLALchemy从数据库中加载数据)
        1.lazy='subquery',
            查询当前数据模型时,采用子查询(subquery),把外键模型的属性也瞬间查询出来了
        2.lazy=True或lazy='select',
            查询当前数据模型时,不会把外键模型的数据查询出来,只有操作到外键关联属性时,才进行连表查询数据[执行SQL]
        3.lazy='dynamic',
            查询当前数据模型时,不会把外键模型的数据查询出来,只有操作到外键关联属性并操作外键模型具体属性时,才进行连表查询数据[执行SQL]
    '''
    course_list = db.relationship('Course', uselist=True, backref='teacher', lazy='subquery')

    def __repr__(self):
        return self.name


class Course(db.Model):
    __tablename__ = "tb_course"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), unique=True, comment="课程名称")
    price = db.Column(db.Numeric(6, 2))
    '''创建外键(一对多)'''
    teacher_id = db.Column(db.Integer, db.ForeignKey(Teacher.id), comment='老师')

    def __repr__(self):
        return self.name

@app.route('/')
def index():
    """添加数据"""
    # 从teacher表中增加
    teacher = Teacher(
        name='teacher01',
        option='班主任',
        course_list=[
            Course(name="抓羊", price="9.90"),
            Course(name="挨打",price="19.90"),
            Course(name="炸房子",price="29.90"),
        ]
    )
    db.session.add(teacher)
    db.session.commit()
    # 从课程表中增加
    course = Course(
        name='python从入门到入坟',
        price='199',
        teacher=Teacher(name='teacher02', option='讲师')
    )
    db.session.add(course)
    db.session.commit()

    '''查'''
    # 查询teacher01教哪些课
    course_list = Teacher.query.filter(Teacher.name == 'teacher01').first().course_list
    print(course_list, type(course_list))
    for course in course_list:
        print(course.price, course.name)
    # 查询 python从入门到入坟 这个课是哪个老师教(反向查询)
    teacher = Course.query.filter(Course.name == 'python从入门到入坟').first().teacher.name
    print(teacher)

    '''改'''
    # 将课程 挨打 改为 打人
    Course.query.filter(Course.name == '挨打').first().name = '打人'
    db.session.commit()
    # 将课程 打人 改为 teacher02 教
    Course.query.filter(Course.name == '打人').first().teacher_id = \
        Teacher.query.filter(Teacher.name == 'teacher02').first().id
    db.session.commit()

    '''删除数据'''
    # 将 teacher01 删除
    teacher = Teacher.query.filter(Teacher.name == 'teacher01').first()
    for course in teacher.course_list:
        db.session.delete(course)
    db.session.delete(teacher)
    db.session.commit()

    return 'ok'

if __name__ == '__main__':
    # with app.app_context():
    #     db.drop_all()
    #     db.create_all()
    app.run()

10.6 多表操作之多对多

10.6.1 创建多对多关系

通常有两种方式:

1.创建多对多(对用属性只能写一个,不然会冲突)

db.Table(
    表名,
    db.Column("字段名",字段类型,外键声明),
    db.Column("字段名",字段类型,外键声明),
)

2.拆解成3个模型,其中tb_achievement作为单独模型存在(常用)(相当于两个多对多)

具体代码:

"""创建模型类"""
"""创建多对多(也可以拆解成3个模型,其中tb_achievement作为单独模型存在--常用)
    db.Table(
        表名,
        db.Column("字段名",字段类型,外键声明),
        db.Column("字段名",字段类型,外键声明),
    )
"""
'''方式一'''
# achievement = db.Table(
#     'tb_achievement',
#     # 相当于两个一对多
#     db.Column('student_id', db.Integer, db.ForeignKey('tb_student.id')),
#     db.Column("course_id", db.Integer, db.ForeignKey('tb_course.id')),
#
#     # 这里的表信息,在主键模型中,仅仅表达的是关联关系,所以中间表的字段,无法通过主模型来获取
#     db.Column("created_time", db.DateTime, comment="考试时间"),
#     db.Column("score", db.DECIMAL(5, 2), comment="成绩")
#     )
'''方式二'''
from datetime import datetime
class Achievement(db.Model):
    __tablename__ = "tb_achievement"
    id = db.Column(db.Integer, primary_key=True,comment="主键")
    student_id = db.Column(db.Integer, db.ForeignKey("tb_student.id"), comment="学生")
    course_id = db.Column(db.Integer, db.ForeignKey("tb_course.id"), comment="课程")
    score = db.Column(db.DECIMAL(5,2), nullable=True, comment="成绩分数")
    created_time = db.Column(db.DateTime, default=datetime.now(), comment="考试时间")

    def __repr__(self):
        return "[%s],%s进行了一次%s科目考试,成绩:%s" % (self.created_time,self.student.name,self.course.name,self.score)


class Student(db.Model):
    __tablename__ = "tb_student"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    age = db.Column(db.Integer, comment="年龄")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    money = db.Column(db.DECIMAL(8, 2), nullable=True, comment="钱包")
    """创建一对一关联属性
    1.关联属性,是SQLAlchemy提供给开发者快速引用外键模型的一个对象属性,不存在于mySQL中
    2.backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性
    3.uselist=False表示关联一个数据
    """
    info = db.relationship('StudentInfo', backref='own', uselist=False)

    # course_list = db.relationship("Course", secondary=achievement, backref="student_list", lazy="dynamic")
    achievement_list = db.relationship("Achievement", uselist=True, backref="student", lazy="select")

    def __repr__(self):
        return 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='学生')
    address = db.Column(db.String(255), nullable=True, comment="家庭住址")
    mobile = db.Column(db.String(15), unique=True, comment="紧急联系电话")

    def __repr__(self):
        return self.own.name


class Teacher(db.Model):
    __tablename__ = "tb_teacher"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    option = db.Column(db.Enum("讲师", "助教", "班主任"), default="讲师", comment="教职")
    '''创建一对多关联属性
    lazy:(决定了什么时候SQLALchemy从数据库中加载数据)
        1.lazy='subquery',
            查询当前数据模型时,采用子查询(subquery),把外键模型的属性也瞬间查询出来了
        2.lazy=True或lazy='select',
            查询当前数据模型时,不会把外键模型的数据查询出来,只有操作到外键关联属性时,才进行连表查询数据[执行SQL]
        3.lazy='dynamic',
            查询当前数据模型时,不会把外键模型的数据查询出来,只有操作到外键关联属性并操作外键模型具体属性时,才进行连表查询数据[执行SQL]
    '''
    course_list = db.relationship('Course', uselist=True, backref='teacher', lazy='subquery')

    def __repr__(self):
        return self.name


class Course(db.Model):
    __tablename__ = "tb_course"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), unique=True, comment="课程名称")
    price = db.Column(db.Numeric(6, 2))
    '''创建外键(一对多)'''
    teacher_id = db.Column(db.Integer, db.ForeignKey(Teacher.id), comment='老师')
    # student_list = db.relationship("Student", secondary=achievement, backref='course_list', lazy="dynamic")
    achievement_list = db.relationship("Achievement", backref="course", uselist=True, lazy="select")

    def __repr__(self):
        return self.name

10.6.2增删改查

@app.route('/')
def index():
    """添加数据"""
    # course1 = Course(name="坑爹", price="9.99", teacher=Teacher(name="灰太狼", option="讲师"))
    # course2 = Course(name="坑娘", price="9.99", teacher=Teacher(name="灰太狼", option="讲师"))
    # course3 = Course(name="和羊做朋友,一起坑爹", price="99.99", teacher=Teacher(name="喜洋洋", option="讲师"))
    # student = Student(
    #     name="xiaohuihui",
    #     age=5,
    #     sex=False,
    #     money=1000,
    #     info=StudentInfo(
    #         mobile="13066666666",
    #         address="狼村1号别墅",
    #     ),
    #     course_list = [
    #         course1,
    #         course2,
    #         course3,
    #     ]
    # )
    # db.session.add(student)
    # db.session.commit()

    """查"""
    # 查询 xiaohuihui 同学 选了哪几门课 注意:.all()
    # course_list = Student.query.filter(Student.name == 'xiaohuihui').first().course_list.all()
    # print(course_list)
    # 查询 "和羊做朋友,一起坑爹" 有那些同学选了
    """添加数据"""
    # course1 = Course(name="坑爹", price="9.99", teacher=Teacher(name="灰太狼", option="讲师"))
    # course2 = Course(name="坑娘", price="9.99", teacher=Teacher(name="灰太狼", option="讲师"))
    # course3 = Course(name="和羊做朋友,一起坑爹", price="99.99", teacher=Teacher(name="喜洋洋", option="讲师"))
    # student = Student(
    #     name="xiaohuihui",
    #     age=5,
    #     sex=False,
    #     money=1000,
    #     info=StudentInfo(
    #         mobile="13066666666",
    #         address="狼村1号别墅",
    #     ),
    #     achievement_list= [
    #         Achievement(course=course1,score=100),
    #         Achievement(course=course2,score=80),
    #         Achievement(course=course3,score=85),
    #     ]
    # )
    # db.session.add(student)
    # db.session.commit()
    #
    # course = Course.query.filter(Course.name=="坑爹").first()
    # student = Student.query.filter(Student.name=="xiaohuihui").first()
    # achievement = Achievement(
    #     course=course,
    #     student=student,
    #     score=78
    # )
    # db.session.add(achievement)
    # db.session.commit()
    achievement_list = Student.query.filter(Student.name=='xiaohuihui').first().achievement_list
    for i in achievement_list:
        if i.score > 80:
            print(i.course.name)
    return 'ok'

11.数据迁移

  • 在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。
  • 更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。
  • 在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。
  • 为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。

11.1 基本使用

安装:

pip install flask-migrate

1.使用flask_script启动项目

2.初始化SQLAlchemy,链接数据库

3.初始化Migrate实例

4.在flask_Srcipt中添加一个db命令

这样使用python manage.py db可以看到所有命令

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand

app = Flask(__name__)

class Config():
    DEBUG = True
    # 数据库链接配置
    # SQLALCHEMY_DATABASE_URI = "mysql://账号:密码@IP/数据库名?编码"
    SQLALCHEMY_DATABASE_URI = "mysql://root:[email protected]:3306/students?charset=utf8mb4"
    # 动态追踪修改设置,如未设置只会提示警告
    SQLALCHEMY_TRACK_MODIFICATIONS = True
    # 查询时会显示原始SQL语句
    SQLALCHEMY_ECHO = True


# 加载配置
app.config.from_object(Config)
# 使用flask_script启动项目
manager = Manager()
manager.app = app
# 初始化SQLAlchemy,链接数据库
db = SQLAlchemy()
db.init_app(app)
# 数据迁移
# 第一个参数是Flask的实例,第二个参数是SQLAlchemy数据库实例
migrate = Migrate(app, db)
# 在flask_Srcipt中添加一个db命令
manager.add_command('db', MigrateCommand)

"""创建模型类"""
from datetime import datetime
class Achievement(db.Model):
    __tablename__ = "tb_achievement"
    id = db.Column(db.Integer, primary_key=True,comment="主键")
    student_id = db.Column(db.Integer, db.ForeignKey("tb_student.id"), comment="学生")
    course_id = db.Column(db.Integer, db.ForeignKey("tb_course.id"), comment="课程")
    score = db.Column(db.DECIMAL(5,2), nullable=True, comment="成绩分数")
    created_time = db.Column(db.DateTime, default=datetime.now(), comment="考试时间")

    def __repr__(self):
        return "[%s],%s进行了一次%s科目考试,成绩:%s" % (self.created_time,self.student.name,self.course.name,self.score)

class Student(db.Model):
    __tablename__ = "tb_student"
    id = db.Column(db.Integer, primary_key=True,comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    avatar = db.Column(db.String(250), comment="头像")
    age = db.Column(db.Integer, comment="年龄")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    money = db.Column(db.DECIMAL(8,2), nullable=True, comment="钱包")
    achievement_list = db.relationship("Achievement",uselist=True, backref="student", lazy="select")
    # backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性
    info = db.relationship("StudentInfo", backref="own", uselist=False)
    def __repr__(self):
        return 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="学生")
    address = db.Column(db.String(255), nullable=True, comment="家庭住址")
    mobile = db.Column(db.String(15), unique=True, comment="紧急联系电话")

    def __repr__(self):
        return self.own.name

class Teacher(db.Model):
    __tablename__ = "tb_teacher"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    option = db.Column(db.Enum("讲师","助教","班主任"), default="讲师", comment="教职")
    course_list = db.relationship("Course",uselist=True, backref="teacher",lazy="subquery")
    def __repr__(self):
        return self.name

class Course(db.Model):
    __tablename__ = "tb_course"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), unique=True, comment="课程名称")
    price = db.Column(db.Numeric(6, 2))
    teacher_id = db.Column(db.Integer, db.ForeignKey(Teacher.id),comment="老师")
    achievement_list = db.relationship("Achievement", backref="course", uselist=True, lazy="select")
    def __repr__(self):
        return self.name

@app.route('/')
def index():
    return 'ok'

if __name__ == '__main__':
    manager.run()

11.2 常用命令

1. 初始化数据迁移的目录
python manage.py db init

2. 数据库的数据迁移版本初始化
python manage.py db migrate -m 'initial migration'

3. 升级版本[创建表/创建字段/修改字段]
python manage.py db upgrade 

4. 降级版本[删除表/删除字段/恢复字段](默认返回上一个版本)
python manage.py db downgrade
python manage.py db downgrade 版本号   # 返回到指定版本号对应的版本

5.可以根据history命令找到版本号,然后传给downgrade命令
python manage.py db history
输出格式: ->  版本号 (head), initial migration

12.session的保存

12.1redis保存session

安装:

pip install flask-Session

1.初始化FlaskRedis

2.初始化Session

3.加载相关配置

4.连接redis

5.连接session

from flask import Flask, session
from flask_redis import FlaskRedis
from flask_session import Session
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

redis = FlaskRedis()
session_store = Session()

db = SQLAlchemy()
class Config():
    # DEBUG调试模式
    DEBUG = True
    # 数据库链接配置
    # session秘钥
    SECRET_KEY = "*(%#4sxcz(^(#$#8423"
    # session存储方式为redis
    SESSION_TYPE = "redis"
    # session保存数据到redis时启用的链接对象
    SESSION_REDIS = redis
    # 如果设置session的生命周期是否是会话期, 为True,则关闭浏览器session就失效
    SESSION_PERMANENT = True
    # 是否对发送到浏览器上session的cookie值进行加密
    SESSION_USE_SIGNER = True
    # 保存到redis的session数的名称前缀
    SESSION_KEY_PREFIX = "session:"
    # redis 链接配置 (可以选择库)
    REDIS_URL = 'redis://127.0.0.1:6379/1'

app.config.from_object(Config)
# 连接redis
redis.init_app(app)
# 连接session
session_store.init_app(app)

完整测试代码:

from flask import Flask, session
from flask_redis import FlaskRedis
from flask_session import Session
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

redis = FlaskRedis()
session_store = Session()

db = SQLAlchemy()
class Config():
    # DEBUG调试模式
    DEBUG = True
    # 数据库链接配置
    # session秘钥
    SECRET_KEY = "*(%#4sxcz(^(#$#8423"
    # session存储方式为redis
    SESSION_TYPE = "redis"
    # session保存数据到redis时启用的链接对象
    SESSION_REDIS = redis
    # 如果设置session的生命周期是否是会话期, 为True,则关闭浏览器session就失效
    SESSION_PERMANENT = True
    # 是否对发送到浏览器上session的cookie值进行加密
    SESSION_USE_SIGNER = True
    # 保存到redis的session数的名称前缀
    SESSION_KEY_PREFIX = "session:"
    # redis 链接配置 (可以选择库)
    REDIS_URL = 'redis://127.0.0.1:6379/1'

app.config.from_object(Config)
# 连接redis
redis.init_app(app)
# 连接session
session_store.init_app(app)

@app.route('/')
def index():
    session['username'] = 'xyw'
    return 'ok'
@app.route('/get_session')
def get_session():
    print(session['username'])
    return 'ok'

@app.route("/redis1")
def set_redis():
    redis.set("username","xiaohuihui")
    redis.hset("brother","zhangfei","17")
    return "ok"

@app.route('/redis2')
def get_redis():
    user = redis.get('username').decode()
    print(user)
    brother = redis.hget('brother', 'zhangfei').decode()
    print(brother)
    redis.hdel('brother', 'zhangfei')
    return 'ok'


if __name__ == '__main__':
    # with app.app_context():
    #     db.create_all()
    app.run()

12.2mysql保存session

相关配置:

from flask import Flask, session
from flask_redis import FlaskRedis
from flask_session import Session
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

redis = FlaskRedis()
session_store = Session()
db = SQLAlchemy()


class Config():
    # DEBUG调试模式
    DEBUG = True
    # 数据库链接配置
    # session秘钥
    SECRET_KEY = "*(%#4sxcz(^(#$#8423"
    '''
    # session存储方式为redis
    SESSION_TYPE = "redis"
    # session保存数据到redis时启用的链接对象
    SESSION_REDIS = redis
    # 如果设置session的生命周期是否是会话期, 为True,则关闭浏览器session就失效
    SESSION_PERMANENT = True
    # 是否对发送到浏览器上session的cookie值进行加密
    SESSION_USE_SIGNER = True
    # 保存到redis的session数的名称前缀
    SESSION_KEY_PREFIX = "session:"
    # redis 链接配置 (可以选择库)
    REDIS_URL = 'redis://127.0.0.1:6379/1'
    '''
    # 数据库保存session
    # SQLALCHEMY_DATABASE_URI = "mysql://账号:密码@IP/数据库名?编码"
    SQLALCHEMY_DATABASE_URI = "mysql://root:[email protected]:3306/students?charset=utf8mb4"
    SESSION_TYPE = 'sqlalchemy'  # session类型为sqlalchemy
    SESSION_SQLALCHEMY = db  # SQLAlchemy对象
    SESSION_SQLALCHEMY_TABLE = 'tb_session'  # session要保存的表名称
    SESSION_PERMANENT = True  # 如果设置为True,则关闭浏览器session就失效。
    SESSION_USE_SIGNER = False  # 是否对发送到浏览器上session的cookie值进行加密
    SESSION_KEY_PREFIX = 'session:'  # 保存到session中的值的前缀
    # 动态追踪修改设置,如未设置只会提示警告
    SQLALCHEMY_TRACK_MODIFICATIONS = True
    # 查询时会显示原始SQL语句
    SQLALCHEMY_ECHO = True


app.config.from_object(Config)
# 连接redis
redis.init_app(app)
# 连接session
session_store.init_app(app)
# 连接数据库
db.init_app(app)

@app.route('/')
def index():
    session['username'] = 'xyw'
    return 'ok'
@app.route('/get_session')
def get_session():
    print(session['username'])
    return 'ok'

@app.route("/redis1")
def set_redis():
    redis.set("username","xiaohuihui")
    redis.hset("brother","zhangfei","17")
    return "ok"

@app.route('/redis2')
def get_redis():
    user = redis.get('username').decode()
    print(user)
    brother = redis.hget('brother', 'zhangfei').decode()
    print(brother)
    redis.hdel('brother', 'zhangfei')
    return 'ok'


if __name__ == '__main__':
    # with app.app_context():
    #     db.create_all()
    app.run()

13.蓝图Blueprint

什么是蓝图?

随着flask程序越来越复杂,我们需要对程序进行模块化的处理,之前学习过python的模块化管理,于是针对一个简单的flask程序进行模块化处理

简单来说,Blueprint 是一个存储视图方法的容器,这些操作在这个Blueprint 被注册到一个应用之后就可以被调用,Flask 可以通过Blueprint来组织URL以及处理请求。

Flask使用Blueprint让应用实现模块化,在Flask中,Blueprint具有如下属性:

  • 一个项目可以具有多个Blueprint
  • 可以将一个Blueprint注册到任何一个未使用的URL下比如 “/”、“/sample”或者子域名
  • 在一个应用中,一个模块可以注册多次
  • Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法,它并不是必须要实现应用的视图和函数的
  • 在一个应用初始化时,就应该要注册需要使用的Blueprint

但是一个Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。

Blueprint对象用起来和一个应用/Flask对象差不多,最大的区别在于一个 蓝图对象没有办法独立运行,必须将它注册到一个应用对象上才能生效

13.1 创建一个蓝图

通常有4步:

1.初始化蓝图对象(/应用/__init__.py

2.编写视图(/应用/views.py)

3.注册视图(/应用/__init__.py

4.注册蓝图(/main.py)

如果要关联数据库:

1.初始化SQLAlchemy(/database.py)

2.写模型类对象(/应用/models.py)

3.连接数据库并导入模型(/main.py)

/应用/__init__.py:

from flask import Blueprint
# 1.初始化蓝图对象
users_blue = Blueprint(
    'users', __name__,
    template_folder='users_templates',  # 页面存放的目录
    static_folder='users_static',     # 静态文件存放的目录
    static_url_path='/static',      # 提供给外部访问的路径
)

# 3. 注册视图
from .views import *

/应用/views.py:

from . import users_blue
from flask import render_template
# 2.编写视图
@users_blue.route('/')
def index():
    return render_template('index.html', title='users主页面')

main.py:

from flask import Flask
app = Flask(__name__)
class Config():
    DEBUG = True
    # 数据库链接配置
    # SQLALCHEMY_DATABASE_URI = "mysql://账号:密码@IP/数据库名?编码"
    SQLALCHEMY_DATABASE_URI = "mysql://root:[email protected]:3306/students?charset=utf8mb4"
    # 动态追踪修改设置,如未设置只会提示警告
    SQLALCHEMY_TRACK_MODIFICATIONS = True
    # 查询时会显示原始SQL语句
    SQLALCHEMY_ECHO = True


app.config.from_object(Config)
# 连接数据库
from database import db
db.init_app(app)
from users.models import Member

# 4. 注册蓝图
from users import users_blue
app.register_blueprint(users_blue,url_prefix='/users')

if __name__ == '__main__':
    # with app.app_context():
    #     db.create_all()
    app.run()

database.py:

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()

/应用/models.py:

from database import db


class Member(db.Model):
    __tablename__ = "tb_member"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(250), comment="姓名")
    avatar = db.Column(db.String(250), comment="头像")
    age = db.Column(db.Integer, comment="年龄")
    sex = db.Column(db.Boolean, default=False, comment="性别")
    money = db.Column(db.DECIMAL(8,2), nullable=True, comment="钱包")
    achievement_list = db.relationship("Achievement", uselist=True, backref="student", lazy="select")
    # backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性
    info = db.relationship("StudentInfo", backref="own", uselist=False)

    def __repr__(self):
        return self.name

index.html:


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
    <p>蓝图下的模板p>
    <h1>{{ title }}h1>
    <p><img src="/users/static/1.jpg" alt="">p>
body>
html>

目录结构:
一篇博客搞定flask基础(完结)_第5张图片

13.2蓝图的运行机制

  • 蓝图是保存了一组将来可以在应用对象上执行的操作,注册路由就是一种操作
  • 当在app对象上调用 route 装饰器注册路由时,这个操作将修改对象的url_map路由表
  • 然而,蓝图对象根本没有路由表,当我们在蓝图对象上调用route装饰器注册路由时,它只是在内部的一个延迟操作记录列表defered_functions中添加了一个项
  • 当执行app对象的 register_blueprint() 方法时,应用对象将从蓝图对象的 defered_functions 列表中取出每一项,并以自身作为参数执行该匿名函数,即调用应用对象的 add_url_rule() 方法,这将真正的修改应用对象的usr_map路由表

14.用flask框架仿照django框架搭建项目

1 搭建目录结构

一篇博客搞定flask基础(完结)_第6张图片

2 如何找到配置文件(manager对象)

manager.py:

from application import init_app

manager = init_app('application.settings.dev')
if __name__ == '__main__':
    manager.run()

application/__init__.py
一篇博客搞定flask基础(完结)_第7张图片
load_config:(utils/__init__.py

def load_config(config_path):
    # import_module 根据字符串路径直接进行模块导包,识别模块中的变量成员
    # "application.settings" / "application.settings.dev(prod)"
    module = import_module(config_path)
    config_file_name = config_path.split('.')[-1]
    if config_file_name == 'settings':
        return module.InitConfig
    return module.Config

settings/__init__.py:(默认配置)

class InitConfig():
    DEBUG = True
    # 总路由的地址
    URL_PATH = "application.urls"
    # 蓝图列表
    INSTALLED_APPS = [

    ]

    # 数据库链接配置
    # SQLALCHEMY_DATABASE_URI = "mysql://账号:密码@IP/数据库名?编码"
    SQLALCHEMY_DATABASE_URI = ""
    # 动态追踪修改设置,如未设置只会提示警告
    SQLALCHEMY_TRACK_MODIFICATIONS = True
    # 查询时会显示原始SQL语句
    SQLALCHEMY_ECHO = True

settings/dev.py:(继承默认配置,开发配置)

from . import InitConfig
class Config(InitConfig):
    # 注册应用
    INSTALLED_APPS = [
    'application.apps.home',
    ]
    # 连接数据库配置
    SQLALCHEMY_DATABASE_URI = "mysql://root:[email protected]:3306/students?charset=utf8mb4"

3 如何通过命令创建子应用和数据库迁移指令

通过自定义方法(load_command):
一篇博客搞定flask基础(完结)_第8张图片
utils/__init__.py:

def load_command(manager, command_path):
    module = import_module(command_path)
    # 搜索当前模块下的所有类
    class_list = inspect.getmembers(module, inspect.isclass)
    for class_name, class_object in class_list:
        if issubclass(class_object, Command) and class_name != 'Command':
            #                        别名               对象
            manager.add_command(class_object.name, class_object)

utils/commands.py:

from flask_script import Command, Option
import os
class BluePrintCommand(Command):

    name = "blue"  # 命令的调用别名
    option_list = [
        Option("--name","-n",help="蓝图名称")
    ]

    def run(self,name=None):
        if name is None:
            print("蓝图名称不能为空!")
            return
        if not os.path.isdir(name):
            os.mkdir(name)
        open("%s/views.py" % name,"w")
        open("%s/models.py" % name,"w")
        with open("%s/urls.py" % name,"w") as f:
            f.write("""from . import views
urlpatterns = [

]
""")

4 如何注册蓝图(通过路由找到蓝图)

一篇博客搞定flask基础(完结)_第9张图片
utils/__init__.py:

def include(url, blueprint_func):
    return {'url_prefix': url, 'path': blueprint_func}

def path(url, view_func):
    return {'rule': url, 'view_func': view_func}

def load_blueprint(app,db):
    # 获取总路由的路由列表( [include('/home', 'home.urls')]=>[{'url_prefix': /home, 'path': home.urls}] )
    app_url_list = import_module(app.config.get('URL_PATH')).urlpatterns
    # 获取蓝图名称和应用( ['application.apps.home', ....] )
    for blueprint_path in app.config.get('INSTALLED_APPS'):
        blueprint_name = blueprint_path.split('.')[-1]
        for blueprint_url in app_url_list:
            if blueprint_url.get('path') == blueprint_name + '.urls':
                url_prefix = blueprint_url.get('url_prefix')
                break
        # 创建蓝图对象
        blue = Blueprint(blueprint_name, blueprint_path)
        # 蓝图对象注册路由 (urlpatterns=[path('/index', views.index)...]=>{'rule':/index,'view_func':views.index})
        blue_urls_module = import_module(blueprint_path+'.urls')
        for urls_item in blue_urls_module.urlpatterns:
            blue.add_url_rule(**urls_item)

        # 注册蓝图
        app.register_blueprint(blue, url_prefix=url_prefix)
        # 加载当前蓝图下的模型
        import_module(blueprint_path+'.models')

总路由(urls.py):

from application.utils import include
urlpatterns = [
    include('/home', 'home.urls')
]

apps/home/urls.py:

from . import views
from application.utils import path
urlpatterns = [
    path('/index', views.index)
]

最终效果:

一篇博客搞定flask基础(完结)_第10张图片
代码:

https://gitee.com/wangfan741/flag-framework

你可能感兴趣的:(flask,flask,python)