• 【自学开发之旅】Flask-标准化返回-连接数据库-分表-orm-migrate-增删改查(三)


    业务逻辑不能用http状态码判断,应该有自己的逻辑判断。想要前端需要判断(好多if…else),所以需要标准化,标准化返回。
    json标准化返回:
    最外面:data,message,code三个字段。
    data:返回的数据
    code:应用状态码:先设计好,成功-0,失败–登录失败1,注册失败2
    msg:返回的说明
    我们写的接口也要按照这个格式来

    添加libs/response.py

    def generate_response(data = None, msg = "success!", code = 10000):
        # 约定返回的数据格式
        if data is None:
            data = []
    
        return {
            "code": code,
            "msg": msg,
            "data": data
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    然后修改返回:
    login.py

    from flask import Blueprint, request
    from config.settings import user_dict
    from libs.response import generate_response
    
    login_bp = Blueprint("login_bp", __name__, url_prefix="/v1")
    
    @login_bp.route("login")
    def login():
        user = request.json.get("username")
        passwd = request.json.get("passwd")
        local_user_passwd = user_dict.get(user)
        if local_user_passwd and passwd == local_user_passwd:
            return generate_response(msg="success")
        return generate_response(msg="login fail!", code=10001)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    register.py

    from flask import Blueprint, request
    from config.settings import user_dict
    from libs.response import generate_response
    
    register_bp = Blueprint("register_bp", __name__, url_prefix="/v1")
    
    @register_bp.route("register")
    def register():
        username = request.json.get("username")
        passwd = request.json.get("passwd")
        re_passwd = request.json.get("re_passwd")
        if not (username and passwd and re_passwd):
            return generate_response(msg="参数传递不完整", code=3)
        elif passwd != re_passwd:
            return generate_response(msg="注册密码不一致", code=2)
        elif username in user_dict:
            return generate_response(msg="用户已注册",code=1)
        else:
            user_dict[username] = passwd
            print(f"user_dict is {user_dict}")
            return generate_response(msg="register success!", code=10000)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    连接数据库(为了避免频繁的打开关闭消耗过多资源)

    libs/conn_mysql.py

    import pymysql
    from config.settings import DB_PASS, DB_PORT, DB_SCHEM, DB_USER, DB_HOST
    
    
    def conn_mysql():
        conn = pymysql.connect(
            host = DB_HOST,
            port = DB_PORT,
            user = DB_USER,
            password = DB_PASS,
            db = DB_SCHEM
        )
        return conn
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    为了只连一次,绑到app上
    app.py添加:
    上面返回了一个连接对象conn,把他作为一个属性交给了sq_app对象,再给sq_app对象随意的可以设置属性,自己定义(mysql_db)。所以把连接交给了app。

    def create_app():
    	#连接数据库
        sq_app.mysql_db = conn_mysql()
    
    • 1
    • 2
    • 3

    刚好flask提供了一个current_app,在你请求过来的时候,会把你当前的app的上下文内容放在current_app里。
    router/product_view/product.py

    from . import product_bp
    from flask import current_app
    from libs.response import generate_response
    
    @product_bp.route("/product/get")
    def get_product():
        # import pymysql
    
        # db = pymysql.connect(host='192.168.1.150',
        #                      user='jiangda97',
        #                      password='Jiangda123#',
        #                      database='sq-flask')
        cursor = current_app.mysql_db.cursor()
        cursor.execute("select * from product_info")
        data = cursor.fetchall()
        print(data)
        # db.close()
        if data:
            return generate_response(data=data, msg="get product info success!")
        else:
            return generate_response(msg="get data empty", code = 4)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    router/product_view/__init__.py

    from flask import Blueprint
    product_bp = Blueprint("product_bp", __name__, url_prefix="/v1")
    
    from . import product
    
    • 1
    • 2
    • 3
    • 4

    分表
    优点:节省空间,避免数据不必要的膨胀。
    缺点:

    新增了一个product_kind_table表

    select product_info.product_id, product_info.product_name, product_kind_table.kind, product_info.product_price, product_info.product_address
    from product_info inner join product_kind_table
    on product_kind = id
    where product_id = 1
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述
    修改代码router/product_view/product.py

        # 通过url携带参数来传递id
        id = request.args.get("id")
        if id is None:
            sql_str = f"select product_info.product_id, product_info.product_name, product_kind_table.kind, product_info.product_price, product_info.product_address \
                        from product_info inner join product_kind_table \
                        on product_kind = id\
                        where product_id = {id}"
        else:
            sql_str = f"select product_info.product_id, product_info.product_name, product_kind_table.kind, product_info.product_price, product_info.product_address \
                        from product_info inner join product_kind_table \
                        on product_kind = id\
                        where product_id = {id}"
    
        cursor = current_app.mysql_db.cursor()
        cursor.execute(sql_str)
        data = cursor.fetchall()
        # print(data)
        # db.close ()
        if data:
            return generate_response(data=data, msg="get product info success!")
        else:
            return generate_response(msg="get data empty", code = 4)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述

    ORM

    object relation mapping对象关系映射
    请添加图片描述
    orm对象持久化对象

    数据库的表 – 类

    表中的字段 – 属性

    一行行记录 – 对象

    models/__init__.py

    from flask_sqlalchemy import SQLAlchemy
    
    #生成对象映射实例(db就是我们的中间层)
    db = SQLAlchemy()
    
    def init_app_db(app):
        db.init_app(app)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    models/product.py

    from . import db
    
    class ProductInfo(db.Model):
        __tablename__ = "product_info"
        product_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        product_name = db.Column(db.String(256))
        product_kind = db.Column(db.Integer)
        product_price = db.Column(db.Float)
        product_address = db.Column(db.String(128))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    都得运行:init文件添加from . import product

    绑定到核心对象:app.py文件添加

        import models
        models.init_app_db(sq_app)
    
    
    • 1
    • 2
    • 3

    最后运行报错:

    RuntimeError: Either ‘SQLALCHEMY_DATABASE_URI’ or ‘SQLALCHEMY_BINDS’
    must be set.

    意思是需要设置这两个变量,即orm映射的数据库信息。

    config/settings.py添加

    SQLALCHEMY_DATABASE_URI = "mysql+pymysql://jiangda97:Jiangda123#@192.168.1.150:3306/sq-flask"
    
    • 1

    之前我们在app.py将settings都读入sq_app.config里了,且是都大写的key。
    刚好我们的SQLAchemy底层就是会自动读取sq_app.config里的关于连接数据库的操作。

    SQLALCHEMY_DATABASE_URI = “mysql+pymysql://jiangda97:Jiangda123#@192.168.1.150:3306/sq-flask”
    底层+用的连接方式://用户名:密码@host:port/数据库名

    然后我们准备用它来完成一个增加操作,在router/product_view/product.py

    from models.product import ProductInfo
    from models import db
    
    # 新增数据库记录
    @product_bp.route("/product/add", methods=['POST'])
    def product_add():
        # 接收客户端的传递
        pro_name = request.json.get("proname")
        pro_kind = request.json.get("prokind")
        pro_price = request.json.get("proprice")
        pro_address = request.json.get("proadd")
    
        # 实例化类成对象
        proinfo = ProductInfo()
        # 设置属性
        proinfo.product_name = pro_name
        proinfo.product_kind = pro_kind
        proinfo.product_price = pro_price
        proinfo.product_address = pro_address
    
    	# 实例化并设置属性也可以这么写
        # proinfo = ProductInfo(product_name = pro_name,
        #                       product_kind = pro_kind,
        #                       product_price = pro_price,
        #                       product_address = pro_address)
      
        # 生效到数据库
        db.session.add(proinfo)
        db.session.commit()
    
        return generate_response(msg="add success!")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    在这里插入图片描述

    migrate

    添加models/product.py
    在该类下

        add_time = db.Column(db.DateTime, default=datetime.datetime.now())
    
    • 1

    数据库迁移工具,版本管理 – flask-migrate

    改server.py

    # 数据库迁移工具,版本管理 -- flask-migrate
    from flask_migrate import Migrate
    from models import db
    
    migrate = Migrate(sq_app, db)
    
    if __name__ == '__main__':
        sq_app.run(host = sq_app.config['HOST'],
                   port = sq_app.config['PORT'],
                   debug = sq_app.config['DEBUG'])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    方便开发,不改变应用逻辑,只是方便我们把orm映射的类,这个添加的字段生效到数据库,不需要自己修改数据库了。

    terminal中输入该命令,(在命令行操控flask – flask cli)

    (venv) D:\sq-flask>flask --app server:sq_app db init
    Creating directory 'D:\\sq-flask\\migrations' ...  done
    Creating directory 'D:\\sq-flask\\migrations\\versions' ...  done
    Generating D:\sq-flask\migrations\alembic.ini ...  done
    Generating D:\sq-flask\migrations\env.py ...  done
    Generating D:\sq-flask\migrations\README ...  done
    Generating D:\sq-flask\migrations\script.py.mako ...  done
    Please edit configuration/connection/logging settings in 'D:\\sq-flask\\migrations\\alembic.ini' befor
    e proceeding.
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    然后就会产生一个migrations的文件夹
    在这里插入图片描述
    migrate单独用不了,借助flask cli命令行工具,migrate绑定好app后,自动创建好db命令。
    初始化flask --app server:sq_app db init

    –app 指定运行哪个app
    初始化会创建migrations的文件夹

    可以随时删,再init,做了修改,提交版本!

    (venv) D:\sq-flask>flask --app server:sq_app db migrate -m "add time"
    INFO  [alembic.runtime.migration] Context impl MySQLImpl.
    INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
    INFO  [alembic.autogenerate.compare] Detected removed table 'product_kind_table'
    INFO  [alembic.autogenerate.compare] Detected added column 'product_info.add_time'
    INFO  [alembic.autogenerate.compare] Detected NULL on column 'product_info.product_name'
    INFO  [alembic.autogenerate.compare] Detected NULL on column 'product_info.product_kind'
    INFO  [alembic.autogenerate.compare] Detected NULL on column 'product_info.product_price'
    INFO  [alembic.autogenerate.compare] Detected NULL on column 'product_info.product_address'
    Generating D:\衡山\2023-文老师\sq-flask\migrations\versions\43aac3b3bb51_add_time.py ...  done
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    upgrade就可以生效了

    (venv) D:\sq-flask>flask --app server:sq_app db upgrade
    INFO  [alembic.runtime.migration] Context impl MySQLImpl.
    INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
    INFO  [alembic.runtime.migration] Running upgrade  -> 43aac3b3bb51, add time
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    严格按照orm定义好的模型,保持数据库和模型一致,如果数据库有,orm定义的模型没有,则会把数据库多出来的删掉。

    回退:flask --app server:sq_app db downgrade

    命令行进入上下文环境:(用来测试调试代码)
    flask --app server:sq_app shell

    (venv) D:\sq-flask>flask --app server:sq_app shell
    Python 3.9.1 (tags/v3.9.1:1e5d33e, Dec  7 2020, 17:08:21) [MSC v.1927 64 bit (AMD64)] on win32
    App: app
    Instance: D:\sq-flask\instance
    >>> from models.product import ProductInfo
    >>> p1 = ProductInfo()
    >>> p1.product_name = "3333"
    >>> p1.product_kind =  2
    >>> p1.product_price = 22
    >>> p1.product_address = "山东"
    >>> from models import db
    >>> db.session.add(p1)
    >>> db.session.commit()
    >>>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    查询和修改:
    修改其属性。

    >>> p2 = ProductInfo.query.get(3)
    >>> p2
    <ProductInfo 3>
    >>> dir(p2)
    ['__abstract__', '__annotations__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__for
    mat__', '__fsa__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__',
    '__lt__', '__mapper__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__
    ', '__sizeof__', '__str__', '__subclasshook__', '__table__', '__tablename__', '__weakref__', '_sa_class_manager',
    '_sa_instance_state', '_sa_registry', 'add_time', 'metadata', 'product_address', 'product_id', 'product_kind', 'pr
    oduct_name', 'product_price', 'query', 'query_class', 'registry']
    >>> p2.product_name
    '牛肉'
    >>> p2.product_name = "牛肌肉"
    >>> db.session.add(p2)
    >>> db.session.commit()
    >>>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    删除:

    >>> p3 = ProductInfo.query.get(4)
    >>> db.session.delete(p3)
    >>> db.session.commit()
    
    
    • 1
    • 2
    • 3
    • 4

    综合:id通过url携带参数传递,完成修改和删除
    删除:/product/modify – PUT
    删除:/product/delete – DELETE

    router/product_view/product.py

    @product_bp.route("/product/modify", methods=['PUT'])
    def product_modify():
        # 接收客户端的传递携带的参数
        id = request.args.get("id")
        p1 = ProductInfo.query.get(id)
        if p1:
            # 接收客户端的传递
            pro_name = request.json.get("proname")
            pro_kind = request.json.get("prokind")
            pro_price = request.json.get("proprice")
            pro_address = request.json.get("proadd")
    
            p1.product_name = pro_name
            p1.product_kind = pro_kind
            p1.product_price = pro_price
            p1.product_address = pro_address
    
            db.session.add(p1)
            db.session.commit()
            return generate_response(msg="modify success!")
        else:
            return generate_response(msg="no such product!", code=5)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    修改:尽管你修改一个,但你提交的时候得提交全部的字段

    删除:

    @product_bp.route("/product/delete", methods=['DELETE'])
    def product_delete():
        id = request.args.get("id")
        p2 = ProductInfo.query.get(id)
        if p2:
            db.session.delete(p2)
            db.session.commit()
            return generate_response(msg="delete success")
        else:
            return generate_response(msg="no such product", code=6)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    查询:
    query.get() 一般用来查询主键
    query.all() 查询所有(列表类型)

    >>> ProductInfo.query.filter_by(product_kind=1).all()
    [<ProductInfo 1>, <ProductInfo 2>]
    
    >>> ProductInfo.query.filter(ProductInfo.product_kind == 1).all()
    [<ProductInfo 1>, <ProductInfo 2>]
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    windows下使用vs2010编译支持https的curl
    (三十二)巧用CSS3之金粒摇
    JAVA开发管理(比敏捷更快的DevOps)
    一条命令彻底卸载Linux自带多个版本jdk
    MySQL 中的索引
    电力能源指挥中心调度台解决方案主要关注的问题
    seata-分布式事务
    展会预热 | 建模助手亮相住博会,亮点抢先看
    Bootstrap Blazor 实战 Menu 导航菜单使用(2)
    Odoo 15开发手册第六章 模型 - 结构化应用数据
  • 原文地址:https://blog.csdn.net/Stephen_Daa/article/details/132724570