美文网首页程序员flask高级编程-鱼书FLASK入门
慕课网Flask高级编程实战-3.蓝图、模型与CodeFirst

慕课网Flask高级编程实战-3.蓝图、模型与CodeFirst

作者: 9c0ddf06559c | 来源:发表于2018-06-04 12:57 被阅读54次

    专题章节目录

    慕课网Flask高级编程实战-知识点思维导图

    慕课网Flask高级编程实战-1.项目准备 和 Flask入门

    慕课网Flask高级编程实战-2.搜索书籍路由编写

    慕课网Flask高级编程实战-3.蓝图、模型与CodeFirst

    慕课网Flask高级编程实战-4.flask核心机制

    慕课网Flask高级编程实战-5.书籍详情页面的构建

    慕课网Flask高级编程实战-6.书籍详情页面的构建

    慕课网Flask高级编程实战-7.静态文件、模板、消息闪现与Jinja2

    慕课网Flask高级编程实战-8.用户登录与注册

    慕课网Flask高级编程实战-9.书籍交易模型(数据库事务、重写Flask中的对象)

    慕课网Flask高级编程实战-10.鱼书业务处理

    慕课网Flask高级编程实战-11.Python与Flask的结合应用

    慕课网Flask构建可扩展的RESTful API-1. 起步与红图

    慕课网Flask构建可扩展的RESTful API-2. REST基本特征

    慕课网Flask构建可扩展的RESTful API-3. 自定义异常对象

    慕课网Flask构建可扩展的RESTful API-5. Token与HTTPBasic验证 —— 用令牌来管理用户

    慕课网Flask构建可扩展的RESTful API-6. 模型对象的序列化

    慕课网Flask构建可扩展的RESTful API-7. 权限控制

    3.1 应用、蓝图与视图函数

    1.Flask的层级关系

    image.png
    • Flask最上层是app核心对象
    • 在这个核心对象上可以插入很多蓝图,这个蓝图是不能单独存在的,必须将app作为插板插入app
    • 在每一个蓝图上,可以注册很多静态文件,视图函数,模板
    • 一个业务模块可以做为一个蓝图,比如book,user。可以把视图函数注册到蓝图上再插入app。以此来达到之前分文件的目的
    • 之前的book.py 放到了app/web/路径下,就是考虑到了蓝图。app属于是整个Flask应用层。web属于是蓝图

    2.代码规范化

    应该讲一些初始化工作,放在对应层级的包的初始化文件 __init__.py 中。比如Flask核心应用app对象初始化应该放在应用层级app包的 __init__.py 中。蓝图的初始化,应该放在对应蓝图层级web包的__init__.py中,并且所有蓝图对应的试图函数都应该放在web目录下

    image.png
    # app/__init__.py
    
    from flask import Flask
    
    def create_app():
    app = Flask(__name__)
    
    app.config.from_object("config")
    return app
    
    

    修改后的fisher.py

    from app import create_app
    
    
    __author__ = "gaowenfeng"
    
    app = create_app()
    
    if __name__ == "__main__":
    print("id为" + str(id(app)) + "的app启动")
    app.run(host=app.config["HOST"], debug=app.config["DEBUG"], port=app.config["PORT"])
    

    3.2 用蓝图注册试图函数

    1.在蓝图中注册视图函数

    # 实例化蓝图
    # 第一个参数为蓝图所在模块名
    # 应用名称
    web = Blueprint('web', __name__)
    
    
    @web.route("/book/search/<q>/<page>")
    def search(q, page):
    """
    搜索书籍路由
    :param q: 关键字 OR isbn
    :param page: 页码
    """
    isbn_or_key = is_isbn_or_key(q)
    if isbn_or_key == 'isbn':
    result = YuShuBook.search_by_isbn(q)
    else:
    result = YuShuBook.search_by_key(q)
    
    return jsonify(result)
    

    2.蓝图是不能替代app应用的,在蓝图中注册了视图函数后,还需要将蓝图插入app

    def create_app():
    app = Flask(__name__)
    app.config.from_object("config")
    register_blueprint(app)
    
    return app
    
    
    def register_blueprint(app):
    from app.web.book import web
    app.register_blueprint(web)
    

    3.3 单蓝图多模块拆分视图函数

    蓝图,他的出发点,是为了分模块的。什么是模块级别的呢,比如一个web系统属于一个web模块;一个提供给移动端使用的api是一个api模块;一个内容管理系统是一个CMS。

    我们不应该讲book,user这样的不同类别的py文件,做成多个蓝图(这样不是不行,只是小题大做了)

    正确的方式是,在一个模块的初识文件中定义蓝图对象,这个模块的不同文件都引入这个蓝图对象来注册路由函数。并在模块的初始化文件中引入这些py文件来完成试图函数注册代码的执行

    book.py

    from flask import jsonify
    from helper import is_isbn_or_key
    from yushu_book import YuShuBook
    
    # 引入web模块
    from . import web
    
    
    __author__ = "gaowenfeng"
    
    
    @web.route("/book/search/<q>/<page>")
    def search(q, page):
    """
    搜索书籍路由
    :param q: 关键字 OR isbn
    :param page: 页码
    """
    isbn_or_key = is_isbn_or_key(q)
    if isbn_or_key == 'isbn':
    result = YuShuBook.search_by_isbn(q)
    else:
    result = YuShuBook.search_by_key(q)
    
    return jsonify(result)
    
    
    

    后面还会有user模块。我们这里先建立一段伪代码
    user.py

    
    from . import web
    
    __author__ = "gaowenfeng"
    
    
    @web.route("/user/login")
    def login():
    return "success"
    

    web/__init__.py

    from flask import Blueprint
    
    __author__ = "gaowenfeng"
    
    
    web = Blueprint('web', __name__)
    
    # 在这里导入不同文件,完成视图函数的注册
    from app.web import book
    from app.web import user
    

    3.4 Request对象

    之前我们定义的url请求路径是rest风格的 /book/search/<q>/<page>,Flask会将<>里的值自动映射成视图函数方法的参数。

    但是如果需要将方法参数做为请求参数传入进来。就需要用到Flask内置的Request了。Request里包含了HTTP请求的详细信息,比如param,method,url,remote ip等。

    下面看一下我们之前search函数的改造来了解一下Requset获取请求参数的基本用法

    from flask import request
    
    ...
    ...
    @web.route("/book/search/")
    def search():
    """
    搜索书籍路由
    :param q: 关键字 OR isbn
    :param page: 页码
    """
    q = request.args['q']
    page = request.args['page']
    isbn_or_key = is_isbn_or_key(q)
    if isbn_or_key == 'isbn':
    result = YuShuBook.search_by_isbn(q)
    else:
    result = YuShuBook.search_by_key(q)
    
    return jsonify(result)
    

    Request 的args属性是一个不可变字典(继承了python内置的dict)immutableDict。里面放的就是http请求的参数。可以使用to_dict()方法获取请求参数的原生可变字典request.args.to_dict()

    注意,Flask的request是基于代理模式实现的。想让request正常使用,必须确保是http请求触发的函数或视图函数中使用

    3.5 WTForms参数验证

    WTForms 是一款优秀的参数验证框架。可以将参数验证抽离出一个模块。与业务代码解耦。

    使用pipenv引入WTForms

    pipenv install wtforms
    

    使用WTForms需要自定义一个类继承wtforms提供的Form类,然后定义参数校验规则

    from wtforms import Form, StringField, IntegerField
    from wtforms.validators import Length, NumberRange
    
    __author__ = "gaowenfeng"
    
    
    class SearchForm(Form):
    # 参数校验规则:
    # 1.定义的属性名q,page要与要校验的参数同名
    # 2.根据要传入的参数类型选择不同的Field类进行实例化
    # 3.传入一个数组,作为校验规则validators
    # 4.可以设置默认值
    q = StringField(validators=[DataRequired(), Length(min=1, max=30,message="查询关键字长度必须在1-30之间")], )
    page = IntegerField(validators=[NumberRange(min=1, max=99)], default=1)
    

    使用WTForms
    book.py

    @web.route("/book/search/")
    def search():
    """
    搜索书籍路由
    """
    # 实例化我们自定义的SearchForm,需要传入一个字典作为要校验的参数
    form = SearchForm(request.args)
    # validate()方法返回True/False来标示是否校验公国
    if not form.validate():
    # errors为错误信息提示(上面定义的message)
    return jsonify(form.errors)
    
    # 从form中获取校验后的参数,不从request里拿,因为我们可能会对数据进行预处理或者默认值的给定
    q = form.q.data.strip()
    page = form.page.data
    isbn_or_key = is_isbn_or_key(q)
    if isbn_or_key == 'isbn':
    result = YuShuBook.search_by_isbn(q)
    else:
    result = YuShuBook.search_by_key(q)
    
    return jsonify(result)
    

    3.6 配置文件拆分

    我们之前的YuShuBook访问api分页获取数据的时候,count和start是写死的。现在来完善这一部分操作。

    首先看我们之前的代码,接受了count,start两个参数

    @classmethod
    def search_by_key(cls, q, count=15, start=0):
    url = cls.search_by_key_url.format(q, count, start)
    return HTTP.get(url)
    

    考虑以下几点:
    1.我们的试图函数接受的参数是page,考虑到代码的封装性,应该尽可能的隐藏细节,我们应该把计算count,start的过程放到YuShuBook的search_by_key方法中来做

    2.虽然计算start的方法很简单。但是这是一个单独的逻辑过程,不应该将这段过程放在访问api获取数据的方法中。而应该封装成一个方法,以方法名来代替这段逻辑

    3.count的值应该放到配置文件中,这样方便修改。但是考虑到我们之前的配置DEGUG,IP,PORT等都属于私密配置,包括以后会使用的数据库信息等。而COUNT的值属于可公开的配置,所以应该把配置文件拆分成secure.py和settings.py。secure.py保存私有配置,在上传git的时候不应该上传此文件,settings.py是共有配置

    下面来看修改完后的代码
    yushu_book.py

    ...
    ...
    # flask提供了获取当前app的方法
    from flask import current_app
    ...
    ...
    @classmethod
    def search_by_key(cls, q, page=1):
    url = cls.search_by_key_url.format(q, current_app.config["PRE_PAGE"],
    cls.calculate_start(page))
    return HTTP.get(url)
    
    @staticmethod
    def calculate_start(page):
    return (page-1) * current_app.config["PRE_PAGE"]
    

    app/web/settings.py

    PRE_PAGE = 15
    

    app/web/secure.py

    DEBUG = True
    HOST = "0.0.0.0"
    PORT = 8090
    

    app/__init__.py

    def create_app():
    app = Flask(__name__)
    app.config.from_object("app.secure")
    app.config.from_object("app.settings")
    register_blueprint(app)
    
    return app
    

    3.7 数据表创建方式

    1.模块分成

    首先先将项目的层级结构重新调整一下,竟helper,httper这些放在libs目录下,作为常用库函数。将yushu_book放在spider目录下,因为这里涉及到的访问外部api,或者访问数据库,都更像一个小的爬虫

    更新完了目录结构为


    image.png
    • web flask 的web视图函数蓝图
    • libs 库函数
    • form 参数验证
    • spider 数据爬取

    2.数据表创建方式

    1.database first

    是最普标的。直接在数据库中编写DML语句,建表。

    2.model first

    使用建模工具,根据绘制的数据模型,生成数据表。DMA最爱

    3.code first

    在代码中创建业务模型(实体类),自动反向生成数据表。程序员最爱
    可以专注业务模型的设计,而不是数据库的设计
    不需要关心数据库表以及数据库表是如何创建的,简化思维逻辑
    数据库只是用来存储数据的,他的表之间的关系应该有业务来决定

    3.ORM与Code first的区别

    • Code first关注的是相关的数据表是怎么创建的,他解决的是创建数据的问题
    • ORM(Object relation Map)不仅仅是解决数据创建的问题,还包含了数据的查询,更新,添加,删除。ORM希望我们通过操作一个个模型来间接操作数据库,所以说他的范围是更加广阔的。我们后面的所有的数据库操作都是通过ORM来操作的

    3.8 定义第一个模型类以及反向生成

    新建一个模块model,用于存储数据库表对应的业务模型,在编写model层的模型时,一定要忘记数据库表,重点要放在业务模型的抽象中来

    • sqlalchemy 是一个类库,用于根据定义的model反向生成数据库表
    • Flask_SqlAlchemy 是Flask在sqlalchemy基础上封装的一个组件。提供了更加人性化的API来操作数据库
    • pipenv 按照 pipenv install flask-sqlalchemy
      image.png

    1.编写模型类

    from sqlalchemy import Column,Integer,String
    
    __author__ = "gaowenfeng"
    
    
    class Book():
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=True)
    author = Column(String(30), default="未名")
    binding = Column(String(20))
    publisher = Column(String(50))
    price = Column(String(20))
    pages = Column(Integer)
    isbn = Column(String(15), nullable=True, unique=True)
    summary = Column(String(1000))
    image = Column(String(50))
    

    2.将模型映射到数据库中

    1.在模型类中引入Flask_SqlAlchemy,并做相关声明

    app/models/book.py

    from sqlalchemy import Column, Integer, String
    from flask_sqlalchemy import SQLAlchemy
    
    __author__ = "gaowenfeng"
    
    db = SQLAlchemy()
    
    # 继承db.Model
    class Book(db.Model):
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(50), nullable=True)
    author = Column(String(30), default="未名")
    binding = Column(String(20))
    publisher = Column(String(50))
    price = Column(String(20))
    pages = Column(Integer)
    isbn = Column(String(15), nullable=True, unique=True)
    summary = Column(String(1000))
    image = Column(String(50))
    

    2.在app中插入Flask_SqlAlchemy对象

    app/__init__.py

    def create_app():
    app = Flask(__name__)
    app.config.from_object("app.secure")
    app.config.from_object("app.settings")
    register_blueprint(app)
    
    # 将db插入app
    db.init_app(app)
    # 创建所有的表
    db.create_all()
    return app
    

    3.书写配置文件

    app/secure.py

    ...
    ...
    # key-SQLALCHEMY_DATABASE_URI不能随意修改
    # URI规则:数据库类型+驱动://账号:密码@host:port/dbname
    SQLALCHEMY_DATABASE_URI = "mysql+cymysql://root:root@localhost:3306/fisher"
    ...
    ...
    

    cysql驱动需要安装

    pipenv install cymysql
    

    上面的操作完成以后启动项目,会报如下错误

    /Users/gaowenfeng/.local/share/virtualenvs/fisher-4xlkyzha/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:794: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True or False to suppress this warning.
    'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
    Traceback (most recent call last):
    File "/Users/gaowenfeng/project/pycharm/fisher/fisher/fisher.py", line 10, in <module>
    app = create_app()
    File "/Users/gaowenfeng/project/pycharm/fisher/fisher/app/__init__.py", line 14, in create_app
    db.create_all()
    File "/Users/gaowenfeng/.local/share/virtualenvs/fisher-4xlkyzha/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py", line 963, in create_all
    self._execute_for_all_tables(app, bind, 'create_all')
    File "/Users/gaowenfeng/.local/share/virtualenvs/fisher-4xlkyzha/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py", line 940, in _execute_for_all_tables
    app = self.get_app(app)
    File "/Users/gaowenfeng/.local/share/virtualenvs/fisher-4xlkyzha/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py", line 912, in get_app
    'No application found. Either work inside a view function or push'
    RuntimeError: No application found. Either work inside a view function or push an application context. See http://flask-sqlalchemy.pocoo.org/contexts/.
    
    Process finished with exit code 1
    

    这是因为在Flask中,不是实例化了app核心对象,其他的代码就可以直接用到。所以在上面第二部create_all()方法中,应该将app传入

    db.create_all(app=app)
    

    通过以上修改后,数据库中成功生成了数据库表

    相关文章

      网友评论

        本文标题:慕课网Flask高级编程实战-3.蓝图、模型与CodeFirst

        本文链接:https://www.haomeiwen.com/subject/cnllsftx.html