虚拟环境

使用pipenv或者virtualenv 新建一个虚拟环境用来flask项目的开发,参照前面的文档,不展开。

版本控制

项目目录下git init
新建.gitignore文件

  1. .idea/ # vscodeide会产生的文件
  2. __pycache__/ # pycharm运行的时候的缓存
  3. log/ #运行错误信息
  4. case_log/ # case
  5. report/ # 测试报告
  6. secret* # 敏感配置文件

仓库架构

  1. app
  2. app包结构
  3. -- init.py
  4. 详细看app目录下 init方法
  5. -- config.py/config
  6. -- static/
  7. -- templates/
  8. -- main/
  9. -- views # 如果视图数量比较少的话也可以用views.py进行处理
  10. doc
  11. --主要进行项目相关文档的存放
  12. readme.md 文件
  13. -- 项目介绍信息
  14. app.py 文件
  15. -- 项目本地开发,调试的时候使用的执行入口

app/init.py文件

  1. init文件下为当该模块被导入时自动调用执行的python代码块,利用这一特性可以对app核心对象进行一些配置,下看代码
app/__init__.py
'''
初始化app 核心对象

配置相关的一些插件
'''

from flask import Flask
from app.config import config, DevelopConfig

app = Flask(__name__)

# 初始化一些项目环境所用的配置
# config = DevelopConfig  # 通过导包来引入配置属性,但是当配置多起来的时候不便于管理

def create_app():  # create_app 返回的app没有传入到app.py中, 成为导致配置属性未生效的原因
    '''初始化'''
    # 初始化 db
    # 初始化配置文件
    app.config.from_object(config)  # 从一个对象里面导入
    # 自定义的错误处理机制
    # 模板过滤器

    return app
#-----------------------------------------------
# 程序主入口 app.py下

from app import create_app
# 将app init下app导入进来
app = create_app()  # 通过初始化方法产生的app ,配置类属性生效

if __name__ == '__main__':
    app.run(debug=app.config['DEBUG'])

app/config.py文件

主要用来存放各种配置文件

# 各种各样的配置,在项目规模小的时候可以单独使用一个py文件用来储存配置
# 项目规模大的时候,最好使用一个包,里面存放各种环境下的配置
# 当有共有配置比较多的时候,使用一个类,然后让其他类进行继承即可
# 配置项要大写 BEBUG
# 通过pycharm的flask配置启动的话不会走app.py ,需要单独配置
# 配置需要注意的点
# 1,大写  2,几种导入方式
class BaseConfig:
    PER_PAGE = 10  # 每页显示数据数
    DEBUG = False

class DevelopConfig(BaseConfig):
    DEBUG = True


# 引用方法,1, 定义一个变量来进行导入使用
config = DevelopConfig()
# 引用方法  2,初始化的时候进行

依赖整理

pip freeze > requirements.txt

蓝图使用

一个application可能会分成多个模块。例如,前台系统,后台系统。api 同样存在多个版本的问题,为了解决这样的需求问题,flask提供了蓝图,通过将各种各样的蓝图将子系统配到application中。

  • 可以共享一些app的设置和资源,但是各个蓝又拥有各自的执行逻辑

    使用流程

        定义一个蓝图
    
    ```python 定义一个蓝图 from flask import Blueprint web = Blueprint(“web”,name) # 1参为蓝图的名字,2参为固定的name,3url_prefix urlpattern的前缀 定义之后想要使用需要在 核心对象初始化的时候进行注册 app.register_blueprint() 参数即是 蓝图对象名——- app根目录下的init.py文件 给蓝图进行路由注册的时候不再使用@app.route() 而是使用 @蓝图名.route()

要注意一点

蓝图下有多个视图的时候,需要将各个视图导入,否则的话蓝图将未载入视图而导致访问不到。

进行初始化蓝图

from flask import Blueprint

main = Blueprint(“main”, name)

路由 需要导入,,不然访问不到

from .views import index, cases, modules, projects

需要挂在到各个不同的视图函数,即路由

蓝图可以共享app的静态资源与模板

<a name="qHt3I"></a>
###     注册蓝图
```python
在核心对象初始化的时候进行注册  app/init.py


def create_app():  # create_app 返回的app没有传入到app.py中, 成为导致配置属性未生效的原因
    # 初始化 db
    app.config.from_object(config)  # 从一个对象里面导入
    # 配置文件的加载需要放在前面

    db.init_app(app)
    # 初始化配置文件
    # migrate init
    migrate.init_app(app,db)
    # 自定义的错误处理机制
    # 模板过滤器
    # ------7.11----------
    # 蓝图定义之后需要注册  蓝图只需要使用一次,则即用即导入,避免出现循环导入
    from app.main import main
    app.register_blueprint(main)

    return app
之后views模块中编写蓝图下的视图时,进行注册不在是app.route('url')
而是 @蓝图名.route('url')
from app.web import web

@web.route('/')
def index():
    return 'here is a blueprint'

DB绑定初始化,实体类

需求:在蓝图下编写该蓝图所需要的数据库信息,实体类

思维养成与实体类定义

当多个实体类中有共有的字段的时候,可以将共有的字段抽出封装,其他实体类进行继承即可。
from flask_sqlalchemy import SQLAlchemy
import time
db = SQLAlchemy()
# 还没有对app进行绑定,可以延时进行绑定,先进行实体类的编写

# 这里是将每个模型类都会创建的字段进行单独封装,然后之后通过继承提高代码复用
# 但是这个Base并不需要建表,使用__abstract__=True
# 加上之后就不会在进行创建表了
class Base(db.Model):
    __abstract__ = True
    created_at = db.Column(db.Integer,default=int(time.time()))
    # 数据生成时间,数据修改时间
    updated_at = db.Column(db.Integer,default=int(time.time())
                           ,onupdate=int(time.time()))
    # status
    status = db.Column(db.SmallInteger,default=1)

class User(db.Model,Base):
    id = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String(32),nullable=False,default='',unique=True)

DB配置与初始化绑定app

可以在之前的 app/config.py文件中进行数据库初始配置

class BaseConfig:
    PER_PAGE = 10  # 每页显示数据数
    DEBUG = False
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:123456@localhost:3306/demo'

class DevelopConfig(BaseConfig):
    DEBUG = True

绑定app,初始化建表


from flask import Flask
from app.config import config, DevelopConfig
from app.main.models import db
from flask_migrate import Migrate
app = Flask(__name__)
migrate = Migrate()

# 初始化一些项目环境所用的配置
# config = DevelopConfig  # 通过导包来引入配置属性,但是当配置多起来的时候不便于管理


def create_app():  # create_app 返回的app没有传入到app.py中, 成为导致配置属性未生效的原因
    # 初始化 db
    app.config.from_object(config)  # 从一个对象里面导入
    # 配置文件的加载需要放在前面

    db.init_app(app)
    # 初始化配置文件
    # migrate init
    migrate.init_app(app,db)
    # 自定义的错误处理机制
    # 模板过滤器
    # ------7.11----------
    # 蓝图定义之后需要注册  蓝图只需要使用一次,则即用即导入,避免出现循环导入
    from app.main import main
    app.register_blueprint(main)

    return app

目录分包

目录.png
errors 自定义异常类整合处理。
static 静态资源存放。
templates html模板存放。
views 母应用视图存放。
config 全局配置
run 运行主入口。
web ——此为本次编写的蓝图目录
—-forms 表单验证类
—-models 实体类
—-templates_env 用来存放一些前端页面上使用的过滤器
—-views
demos 编写实例测试

app/run.py

'''
web 服务器运行
选用 gunicorn uwsgi waitress
'''
from flask import url_for
from app import create_app
# 将app init下app导入进来
app = create_app()  # 通过初始化方法产生的app ,配置类属性生效
print(app.url_map)
# url_for()

if __name__ == '__main__':
    # print("测试输出配置项" + app.config['DEBUG'])
    app.run(debug=app.config['DEBUG'])

    # run 是falsk项目启动的主入口

app/init.py

# 初始化核心对象,进行一些项目中的配置
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import CSRFProtect

from app.config import config, DevelopConfig
from flask_migrate import Migrate
from app.web.models import db

# 初始化一些项目环境所用的配置
# config = DevelopConfig  # 通过导包来引入配置属性,但是当配置多起来的时候不便于管理
from app.web.templates_env import str_time

csrf = CSRFProtect()


def create_app():  # create_app 返回的app没有传入到app.py中, 成为导致配置属性未生效的原因
    app = Flask(__name__)
    app.config.from_object(config)  # 从一个对象里面导入
    # 初始化 db
    # 配置文件的加载需要放在前面
    db.init_app(app)
    # 初始化配置文件
    # migrate init
    migrate = Migrate(app, db)
    # migrate.init_app(app, db)

    # 自定义的错误处理机制
    # 模板过滤器
    # ------7.11----------
    # 蓝图定义之后需要注册  蓝图只需要使用一次,则即用即导入,避免出现循环导入
    from app.web import web
    app.register_blueprint(web)
    # -------7.13
    # 添加过滤器
    app.add_template_filter(str_time)

    # csrf  flask 的任何插件在使用的时候都要与app进行绑定
    # 代码的话就是任何插件都会有init_app方法来进行绑定初始化
    csrf.init_app(app)
    return app
# --------------------- 7.12

编写思路。app/init下进行初始化核心对象,进行一些项目中的配置。
def create_app() 下进行对app的一系列配置加载
诸如 初始化 db,加载配置文件,注册蓝图,添加过滤器,加载插件,最后将app作为对象
返回到调用处,即上面的run.py中

web/init.py

# 进行初始化蓝图
from flask import Blueprint

web = Blueprint("web", __name__)

# 视图路由 需要导入,,不然访问不到
from .views import index, cases, modules, projects,suites
# 需要挂在到各个不同的视图函数,即路由project
# 蓝图可以共享app的静态资源与模板

web/models/init.py

from flask_sqlalchemy import SQLAlchemy, BaseQuery
from flask import current_app
import time
# 还没有对app进行绑定,可以延时进行绑定,先进行实体类的编写

# 这里是将每个模型类都会创建的字段进行单独封装,然后之后通过继承提高代码复用
# 但是这个Base并不需要建表,使用__abstract__=True
# 加上之后就不会在进行创建表了
from sqlalchemy import desc

from app.errors.DataBbaseException import DataBaseException, UpdateDataException, DeleteDataException

class MyBaseQuery(BaseQuery):
    def filter_by(self, **kwargs):
        kwargs.setdefault('status',1)
        # 设置默认值,会去查找status有没有值,没有默认设置成1
        return super(MyBaseQuery, self).filter_by(**kwargs)

db = SQLAlchemy(query_class=MyBaseQuery)  # 定义之后仍然需要在db对象初始化的时候进行指定

class Base(db.Model):
    __abstract__ = True
    id = db.Column(db.Integer, primary_key=True)
    created_at = db.Column(db.Integer, default=int(time.time()))
    # 数据生成时间,数据修改时间
    updated_at = db.Column(db.Integer, default=int(time.time())
                           , onupdate=int(time.time()))
    # status
    status = db.Column(db.SmallInteger, default=1)

    # 思考都有哪些共有的方法需要写在base下被各个实体类继承
    # 查询所有属性
    # 分页
    @classmethod
    def all(cls):
        return cls.query.filter_by().order_by(desc(cls.updated_at)).all()

    @classmethod
    def paginate(cls, page):
        # 在这里没有进行过滤,所以进行了逻辑删除之后的项目依然会显示在页面上
        #  所以先做一个过滤,滤掉已经删除了的项目 但是很多地方都需要进行查询,
        # 如果都写query.filter_by的话工作量巨大且不效率不现实
        # 可以重写query方法  这个涉及到orm框架的二次开发
        return cls.query.filter_by().paginate(
            page=int(page), per_page=current_app.config['PER_PAGE'],
            error_out=False
        )


class User(Base):
    username = db.Column(db.String(32), nullable=False, default='', unique=True)

class ProjectInfo(Base):
    project_name = db.Column(db.String(32), nullable=False, default='', unique=True)
    simple_desc = db.Column(db.String(50), nullable=False, default='', unique=True)
    # 在这里设置一个关系声明,关联到moudles
    modules = db.relationship("ModuleInfo",backref= 'project',lazy='dynamic')
    # dynamic 返回一个query对象,还需要调用filter_by().all()方法
    # select 直接返回查询到的数据
    # 删除状态
    DELETE_STATUS = 0

    @classmethod
    def insert(cls, data):
        # 添加新的项目
        # data {"project_name":}
        project = ProjectInfo(
            project_aname=data.get("project_name", ''),  # 获取到 project_name 没获取到就按照后面空值
            simple_desc=data.get("simple_desc", '')
        )
        try:
            db.session.add(project)
            db.session.commit()
        except Exception as e:
            # logger记录
            # current_app.logger.error('project 保存出错')
            # 抛出异常  如果需要自己控制异常的话,需要在app下创建一个进行自定义异常控制
            raise DataBaseException("project数据异常")

    #   这里抛出异常是让后端去进行处理
    # return null
    # db.session.rollback()

    def update(self, data):
        # 更新项目信息  因为调用处上下文中已经获得了project独享
        # 便不需要再写成类方法
        self.project_aname = data.get("project_name", ''),  # 获取到 project_name 没获取到就按照后面空值
        self.simple_desc = data.get("simple_desc", '')
        try:
            db.session.add(self)
            db.session.commit()
            return self
        except Exception as e:
            # logger记录
            current_app.logger.error('project 更新出错')
            # 抛出异常  如果需要自己控制异常的话,需要在app下创建一个进行自定义异常控制
            raise UpdateDataException("project更新异常")

    #   这里抛出异常是让后端去进行处理
    # return null
    # db.session.rollback()

    def delete(self):
        # 删除记录 逻辑删除
        # bug会存在什么问题
        # 删除了之后项目还会展示出来
        # 删除完了之后 module,case 还能用嘛? 级联删除
        self.status = self.DELETE_STATUS

        try:
            db.session.add(self)
            db.session.commit()
            return self
        except Exception as e:
            # logger记录
            current_app.logger.error('project 删除出错')
            # 抛出异常  如果需要自己控制异常的话,需要在app下创建一个进行自定义异常控制
            raise DeleteDataException("project删除发生异常")
# 二次开发的思路
# 在学完用法之后,还需要去看原码,不看原码的话,只能被框架支配,
# 不能实现定制个性化功能,代码冗余

# 接口和项目是多对一关系
class ModuleInfo(Base):
    module_name = db.Column(db.String(20),nullable=False)
    simple_desc = db.Column(db.String(20),default='')
    project_id = db.Column(db.INT,db.ForeignKey("project_info.id"))

    @property
    def case_records(self):
        # 获取module 可用的测试用例
        return self.test_case.filter_by().all()


# 测试用例和接口也是多对一的关系
class CaseInfo(Base):
    # 测试用例表
    type = db.Column(db.SmallInteger,default=1)
    name = db.Column(db.String(20),nullable=False)
    include = db.Column(db.String(512))
    request = db.Column(db.Text)
    module_id = db.Column(db.INT,db.ForeignKey('module_info.id'))

# 全局csrf 校验
# 在初始化的时候设置一 csrfprotect()
# 在ajax中使用全局csrftoken验证的时候需要

web/templates_env/init

这个文件夹用来存放一些前端页面上使用的过滤器,对字段进行一些过滤操作显示,
之后前往app下的init模块去添加过滤器

from datetime import datetime

def str_time(ts):
    return datetime.fromtimestamp(ts)