虚拟环境
使用pipenv或者virtualenv 新建一个虚拟环境用来flask项目的开发,参照前面的文档,不展开。
版本控制
项目目录下git init
新建.gitignore文件
.idea/ # vscodeide会产生的文件__pycache__/ # pycharm运行的时候的缓存log/ #运行错误信息case_log/ # casereport/ # 测试报告secret* # 敏感配置文件
仓库架构
app包app包结构-- init.py详细看app目录下 init方法 ,-- config.py/config包-- static/-- templates/-- main/-- views 包 # 如果视图数量比较少的话也可以用views.py进行处理doc包--主要进行项目相关文档的存放readme.md 文件-- 项目介绍信息app.py 文件-- 项目本地开发,调试的时候使用的执行入口
app/init.py文件
init文件下为当该模块被导入时自动调用执行的python代码块,利用这一特性可以对app核心对象进行一些配置,下看代码
app/__init__.py'''初始化app 核心对象配置相关的一些插件'''from flask import Flaskfrom app.config import config, DevelopConfigapp = 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 = Falseclass 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.pydef create_app(): # create_app 返回的app没有传入到app.py中, 成为导致配置属性未生效的原因# 初始化 dbapp.config.from_object(config) # 从一个对象里面导入# 配置文件的加载需要放在前面db.init_app(app)# 初始化配置文件# migrate initmigrate.init_app(app,db)# 自定义的错误处理机制# 模板过滤器# ------7.11----------# 蓝图定义之后需要注册 蓝图只需要使用一次,则即用即导入,避免出现循环导入from app.main import mainapp.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 SQLAlchemyimport timedb = SQLAlchemy()# 还没有对app进行绑定,可以延时进行绑定,先进行实体类的编写# 这里是将每个模型类都会创建的字段进行单独封装,然后之后通过继承提高代码复用# 但是这个Base并不需要建表,使用__abstract__=True# 加上之后就不会在进行创建表了class Base(db.Model):__abstract__ = Truecreated_at = db.Column(db.Integer,default=int(time.time()))# 数据生成时间,数据修改时间updated_at = db.Column(db.Integer,default=int(time.time()),onupdate=int(time.time()))# statusstatus = 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 = FalseSQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:123456@localhost:3306/demo'class DevelopConfig(BaseConfig):DEBUG = True
绑定app,初始化建表
from flask import Flaskfrom app.config import config, DevelopConfigfrom app.main.models import dbfrom flask_migrate import Migrateapp = Flask(__name__)migrate = Migrate()# 初始化一些项目环境所用的配置# config = DevelopConfig # 通过导包来引入配置属性,但是当配置多起来的时候不便于管理def create_app(): # create_app 返回的app没有传入到app.py中, 成为导致配置属性未生效的原因# 初始化 dbapp.config.from_object(config) # 从一个对象里面导入# 配置文件的加载需要放在前面db.init_app(app)# 初始化配置文件# migrate initmigrate.init_app(app,db)# 自定义的错误处理机制# 模板过滤器# ------7.11----------# 蓝图定义之后需要注册 蓝图只需要使用一次,则即用即导入,避免出现循环导入from app.main import mainapp.register_blueprint(main)return app
目录分包

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_forfrom 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 Flaskfrom flask_sqlalchemy import SQLAlchemyfrom flask_wtf import CSRFProtectfrom app.config import config, DevelopConfigfrom flask_migrate import Migratefrom app.web.models import db# 初始化一些项目环境所用的配置# config = DevelopConfig # 通过导包来引入配置属性,但是当配置多起来的时候不便于管理from app.web.templates_env import str_timecsrf = CSRFProtect()def create_app(): # create_app 返回的app没有传入到app.py中, 成为导致配置属性未生效的原因app = Flask(__name__)app.config.from_object(config) # 从一个对象里面导入# 初始化 db# 配置文件的加载需要放在前面db.init_app(app)# 初始化配置文件# migrate initmigrate = Migrate(app, db)# migrate.init_app(app, db)# 自定义的错误处理机制# 模板过滤器# ------7.11----------# 蓝图定义之后需要注册 蓝图只需要使用一次,则即用即导入,避免出现循环导入from app.web import webapp.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 Blueprintweb = Blueprint("web", __name__)# 视图路由 需要导入,,不然访问不到from .views import index, cases, modules, projects,suites# 需要挂在到各个不同的视图函数,即路由project# 蓝图可以共享app的静态资源与模板
web/models/init.py
from flask_sqlalchemy import SQLAlchemy, BaseQueryfrom flask import current_appimport time# 还没有对app进行绑定,可以延时进行绑定,先进行实体类的编写# 这里是将每个模型类都会创建的字段进行单独封装,然后之后通过继承提高代码复用# 但是这个Base并不需要建表,使用__abstract__=True# 加上之后就不会在进行创建表了from sqlalchemy import descfrom app.errors.DataBbaseException import DataBaseException, UpdateDataException, DeleteDataExceptionclass MyBaseQuery(BaseQuery):def filter_by(self, **kwargs):kwargs.setdefault('status',1)# 设置默认值,会去查找status有没有值,没有默认设置成1return super(MyBaseQuery, self).filter_by(**kwargs)db = SQLAlchemy(query_class=MyBaseQuery) # 定义之后仍然需要在db对象初始化的时候进行指定class Base(db.Model):__abstract__ = Trueid = 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()))# statusstatus = db.Column(db.SmallInteger, default=1)# 思考都有哪些共有的方法需要写在base下被各个实体类继承# 查询所有属性# 分页@classmethoddef all(cls):return cls.query.filter_by().order_by(desc(cls.updated_at)).all()@classmethoddef 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)# 在这里设置一个关系声明,关联到moudlesmodules = db.relationship("ModuleInfo",backref= 'project',lazy='dynamic')# dynamic 返回一个query对象,还需要调用filter_by().all()方法# select 直接返回查询到的数据# 删除状态DELETE_STATUS = 0@classmethoddef 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 selfexcept 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_STATUStry:db.session.add(self)db.session.commit()return selfexcept 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"))@propertydef 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 datetimedef str_time(ts):return datetime.fromtimestamp(ts)
