虚拟环境

使用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核心对象进行一些配置,下看代码
  1. app/__init__.py
  2. '''
  3. 初始化app 核心对象
  4. 配置相关的一些插件
  5. '''
  6. from flask import Flask
  7. from app.config import config, DevelopConfig
  8. app = Flask(__name__)
  9. # 初始化一些项目环境所用的配置
  10. # config = DevelopConfig # 通过导包来引入配置属性,但是当配置多起来的时候不便于管理
  11. def create_app(): # create_app 返回的app没有传入到app.py中, 成为导致配置属性未生效的原因
  12. '''初始化'''
  13. # 初始化 db
  14. # 初始化配置文件
  15. app.config.from_object(config) # 从一个对象里面导入
  16. # 自定义的错误处理机制
  17. # 模板过滤器
  18. return app
  19. #-----------------------------------------------
  20. # 程序主入口 app.py下
  21. from app import create_app
  22. # 将app init下app导入进来
  23. app = create_app() # 通过初始化方法产生的app ,配置类属性生效
  24. if __name__ == '__main__':
  25. app.run(debug=app.config['DEBUG'])

app/config.py文件

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

  1. # 各种各样的配置,在项目规模小的时候可以单独使用一个py文件用来储存配置
  2. # 项目规模大的时候,最好使用一个包,里面存放各种环境下的配置
  3. # 当有共有配置比较多的时候,使用一个类,然后让其他类进行继承即可
  4. # 配置项要大写 BEBUG
  5. # 通过pycharm的flask配置启动的话不会走app.py ,需要单独配置
  6. # 配置需要注意的点
  7. # 1,大写 2,几种导入方式
  8. class BaseConfig:
  9. PER_PAGE = 10 # 每页显示数据数
  10. DEBUG = False
  11. class DevelopConfig(BaseConfig):
  12. DEBUG = True
  13. # 引用方法,1, 定义一个变量来进行导入使用
  14. config = DevelopConfig()
  15. # 引用方法 2,初始化的时候进行

依赖整理

pip freeze > requirements.txt

蓝图使用

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

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

    使用流程

    1. 定义一个蓝图
    ```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的静态资源与模板

  1. <a name="qHt3I"></a>
  2. ### 注册蓝图
  3. ```python
  4. 在核心对象初始化的时候进行注册 app/init.py
  5. def create_app(): # create_app 返回的app没有传入到app.py中, 成为导致配置属性未生效的原因
  6. # 初始化 db
  7. app.config.from_object(config) # 从一个对象里面导入
  8. # 配置文件的加载需要放在前面
  9. db.init_app(app)
  10. # 初始化配置文件
  11. # migrate init
  12. migrate.init_app(app,db)
  13. # 自定义的错误处理机制
  14. # 模板过滤器
  15. # ------7.11----------
  16. # 蓝图定义之后需要注册 蓝图只需要使用一次,则即用即导入,避免出现循环导入
  17. from app.main import main
  18. app.register_blueprint(main)
  19. return app
  20. 之后views模块中编写蓝图下的视图时,进行注册不在是app.route('url')
  21. 而是 @蓝图名.route('url')
  22. from app.web import web
  23. @web.route('/')
  24. def index():
  25. return 'here is a blueprint'

DB绑定初始化,实体类

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

思维养成与实体类定义

  1. 当多个实体类中有共有的字段的时候,可以将共有的字段抽出封装,其他实体类进行继承即可。
  2. from flask_sqlalchemy import SQLAlchemy
  3. import time
  4. db = SQLAlchemy()
  5. # 还没有对app进行绑定,可以延时进行绑定,先进行实体类的编写
  6. # 这里是将每个模型类都会创建的字段进行单独封装,然后之后通过继承提高代码复用
  7. # 但是这个Base并不需要建表,使用__abstract__=True
  8. # 加上之后就不会在进行创建表了
  9. class Base(db.Model):
  10. __abstract__ = True
  11. created_at = db.Column(db.Integer,default=int(time.time()))
  12. # 数据生成时间,数据修改时间
  13. updated_at = db.Column(db.Integer,default=int(time.time())
  14. ,onupdate=int(time.time()))
  15. # status
  16. status = db.Column(db.SmallInteger,default=1)
  17. class User(db.Model,Base):
  18. id = db.Column(db.Integer,primary_key=True)
  19. name = db.Column(db.String(32),nullable=False,default='',unique=True)

DB配置与初始化绑定app

  1. 可以在之前的 app/config.py文件中进行数据库初始配置
  2. class BaseConfig:
  3. PER_PAGE = 10 # 每页显示数据数
  4. DEBUG = False
  5. SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:123456@localhost:3306/demo'
  6. class DevelopConfig(BaseConfig):
  7. DEBUG = True

绑定app,初始化建表

  1. from flask import Flask
  2. from app.config import config, DevelopConfig
  3. from app.main.models import db
  4. from flask_migrate import Migrate
  5. app = Flask(__name__)
  6. migrate = Migrate()
  7. # 初始化一些项目环境所用的配置
  8. # config = DevelopConfig # 通过导包来引入配置属性,但是当配置多起来的时候不便于管理
  9. def create_app(): # create_app 返回的app没有传入到app.py中, 成为导致配置属性未生效的原因
  10. # 初始化 db
  11. app.config.from_object(config) # 从一个对象里面导入
  12. # 配置文件的加载需要放在前面
  13. db.init_app(app)
  14. # 初始化配置文件
  15. # migrate init
  16. migrate.init_app(app,db)
  17. # 自定义的错误处理机制
  18. # 模板过滤器
  19. # ------7.11----------
  20. # 蓝图定义之后需要注册 蓝图只需要使用一次,则即用即导入,避免出现循环导入
  21. from app.main import main
  22. app.register_blueprint(main)
  23. return app

目录分包

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

app/run.py

  1. '''
  2. web 服务器运行
  3. 选用 gunicorn uwsgi waitress
  4. '''
  5. from flask import url_for
  6. from app import create_app
  7. # 将app init下app导入进来
  8. app = create_app() # 通过初始化方法产生的app ,配置类属性生效
  9. print(app.url_map)
  10. # url_for()
  11. if __name__ == '__main__':
  12. # print("测试输出配置项" + app.config['DEBUG'])
  13. app.run(debug=app.config['DEBUG'])
  14. # run 是falsk项目启动的主入口

app/init.py

  1. # 初始化核心对象,进行一些项目中的配置
  2. from flask import Flask
  3. from flask_sqlalchemy import SQLAlchemy
  4. from flask_wtf import CSRFProtect
  5. from app.config import config, DevelopConfig
  6. from flask_migrate import Migrate
  7. from app.web.models import db
  8. # 初始化一些项目环境所用的配置
  9. # config = DevelopConfig # 通过导包来引入配置属性,但是当配置多起来的时候不便于管理
  10. from app.web.templates_env import str_time
  11. csrf = CSRFProtect()
  12. def create_app(): # create_app 返回的app没有传入到app.py中, 成为导致配置属性未生效的原因
  13. app = Flask(__name__)
  14. app.config.from_object(config) # 从一个对象里面导入
  15. # 初始化 db
  16. # 配置文件的加载需要放在前面
  17. db.init_app(app)
  18. # 初始化配置文件
  19. # migrate init
  20. migrate = Migrate(app, db)
  21. # migrate.init_app(app, db)
  22. # 自定义的错误处理机制
  23. # 模板过滤器
  24. # ------7.11----------
  25. # 蓝图定义之后需要注册 蓝图只需要使用一次,则即用即导入,避免出现循环导入
  26. from app.web import web
  27. app.register_blueprint(web)
  28. # -------7.13
  29. # 添加过滤器
  30. app.add_template_filter(str_time)
  31. # csrf flask 的任何插件在使用的时候都要与app进行绑定
  32. # 代码的话就是任何插件都会有init_app方法来进行绑定初始化
  33. csrf.init_app(app)
  34. return app
  35. # --------------------- 7.12

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

web/init.py

  1. # 进行初始化蓝图
  2. from flask import Blueprint
  3. web = Blueprint("web", __name__)
  4. # 视图路由 需要导入,,不然访问不到
  5. from .views import index, cases, modules, projects,suites
  6. # 需要挂在到各个不同的视图函数,即路由project
  7. # 蓝图可以共享app的静态资源与模板

web/models/init.py

  1. from flask_sqlalchemy import SQLAlchemy, BaseQuery
  2. from flask import current_app
  3. import time
  4. # 还没有对app进行绑定,可以延时进行绑定,先进行实体类的编写
  5. # 这里是将每个模型类都会创建的字段进行单独封装,然后之后通过继承提高代码复用
  6. # 但是这个Base并不需要建表,使用__abstract__=True
  7. # 加上之后就不会在进行创建表了
  8. from sqlalchemy import desc
  9. from app.errors.DataBbaseException import DataBaseException, UpdateDataException, DeleteDataException
  10. class MyBaseQuery(BaseQuery):
  11. def filter_by(self, **kwargs):
  12. kwargs.setdefault('status',1)
  13. # 设置默认值,会去查找status有没有值,没有默认设置成1
  14. return super(MyBaseQuery, self).filter_by(**kwargs)
  15. db = SQLAlchemy(query_class=MyBaseQuery) # 定义之后仍然需要在db对象初始化的时候进行指定
  16. class Base(db.Model):
  17. __abstract__ = True
  18. id = db.Column(db.Integer, primary_key=True)
  19. created_at = db.Column(db.Integer, default=int(time.time()))
  20. # 数据生成时间,数据修改时间
  21. updated_at = db.Column(db.Integer, default=int(time.time())
  22. , onupdate=int(time.time()))
  23. # status
  24. status = db.Column(db.SmallInteger, default=1)
  25. # 思考都有哪些共有的方法需要写在base下被各个实体类继承
  26. # 查询所有属性
  27. # 分页
  28. @classmethod
  29. def all(cls):
  30. return cls.query.filter_by().order_by(desc(cls.updated_at)).all()
  31. @classmethod
  32. def paginate(cls, page):
  33. # 在这里没有进行过滤,所以进行了逻辑删除之后的项目依然会显示在页面上
  34. # 所以先做一个过滤,滤掉已经删除了的项目 但是很多地方都需要进行查询,
  35. # 如果都写query.filter_by的话工作量巨大且不效率不现实
  36. # 可以重写query方法 这个涉及到orm框架的二次开发
  37. return cls.query.filter_by().paginate(
  38. page=int(page), per_page=current_app.config['PER_PAGE'],
  39. error_out=False
  40. )
  41. class User(Base):
  42. username = db.Column(db.String(32), nullable=False, default='', unique=True)
  43. class ProjectInfo(Base):
  44. project_name = db.Column(db.String(32), nullable=False, default='', unique=True)
  45. simple_desc = db.Column(db.String(50), nullable=False, default='', unique=True)
  46. # 在这里设置一个关系声明,关联到moudles
  47. modules = db.relationship("ModuleInfo",backref= 'project',lazy='dynamic')
  48. # dynamic 返回一个query对象,还需要调用filter_by().all()方法
  49. # select 直接返回查询到的数据
  50. # 删除状态
  51. DELETE_STATUS = 0
  52. @classmethod
  53. def insert(cls, data):
  54. # 添加新的项目
  55. # data {"project_name":}
  56. project = ProjectInfo(
  57. project_aname=data.get("project_name", ''), # 获取到 project_name 没获取到就按照后面空值
  58. simple_desc=data.get("simple_desc", '')
  59. )
  60. try:
  61. db.session.add(project)
  62. db.session.commit()
  63. except Exception as e:
  64. # logger记录
  65. # current_app.logger.error('project 保存出错')
  66. # 抛出异常 如果需要自己控制异常的话,需要在app下创建一个进行自定义异常控制
  67. raise DataBaseException("project数据异常")
  68. # 这里抛出异常是让后端去进行处理
  69. # return null
  70. # db.session.rollback()
  71. def update(self, data):
  72. # 更新项目信息 因为调用处上下文中已经获得了project独享
  73. # 便不需要再写成类方法
  74. self.project_aname = data.get("project_name", ''), # 获取到 project_name 没获取到就按照后面空值
  75. self.simple_desc = data.get("simple_desc", '')
  76. try:
  77. db.session.add(self)
  78. db.session.commit()
  79. return self
  80. except Exception as e:
  81. # logger记录
  82. current_app.logger.error('project 更新出错')
  83. # 抛出异常 如果需要自己控制异常的话,需要在app下创建一个进行自定义异常控制
  84. raise UpdateDataException("project更新异常")
  85. # 这里抛出异常是让后端去进行处理
  86. # return null
  87. # db.session.rollback()
  88. def delete(self):
  89. # 删除记录 逻辑删除
  90. # bug会存在什么问题
  91. # 删除了之后项目还会展示出来
  92. # 删除完了之后 module,case 还能用嘛? 级联删除
  93. self.status = self.DELETE_STATUS
  94. try:
  95. db.session.add(self)
  96. db.session.commit()
  97. return self
  98. except Exception as e:
  99. # logger记录
  100. current_app.logger.error('project 删除出错')
  101. # 抛出异常 如果需要自己控制异常的话,需要在app下创建一个进行自定义异常控制
  102. raise DeleteDataException("project删除发生异常")
  103. # 二次开发的思路
  104. # 在学完用法之后,还需要去看原码,不看原码的话,只能被框架支配,
  105. # 不能实现定制个性化功能,代码冗余
  106. # 接口和项目是多对一关系
  107. class ModuleInfo(Base):
  108. module_name = db.Column(db.String(20),nullable=False)
  109. simple_desc = db.Column(db.String(20),default='')
  110. project_id = db.Column(db.INT,db.ForeignKey("project_info.id"))
  111. @property
  112. def case_records(self):
  113. # 获取module 可用的测试用例
  114. return self.test_case.filter_by().all()
  115. # 测试用例和接口也是多对一的关系
  116. class CaseInfo(Base):
  117. # 测试用例表
  118. type = db.Column(db.SmallInteger,default=1)
  119. name = db.Column(db.String(20),nullable=False)
  120. include = db.Column(db.String(512))
  121. request = db.Column(db.Text)
  122. module_id = db.Column(db.INT,db.ForeignKey('module_info.id'))
  123. # 全局csrf 校验
  124. # 在初始化的时候设置一 csrfprotect()
  125. # 在ajax中使用全局csrftoken验证的时候需要

web/templates_env/init

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

  1. from datetime import datetime
  2. def str_time(ts):
  3. return datetime.fromtimestamp(ts)