经常会用到 flask 框架,因此想做一个模板,方便以后直接进行开发使用。
大体是使用蓝图设计一个项目模板,让其配置文件、数据库文件等单独存放,提升项目可读性。
完整代码:https://github.com/HuTa0kj/flask-template
项目目录及结构 模板项目目录如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 run.py app |__init__.py 蓝图注册文件 |config.py 配置文件,如 SECRET_KEY、数据库账号密码 |database.py 数据库文件,放置数据库表对象 |extensions.py 拓展文件,导入拓展包(flask_sqlalchemy、flask_login),并实例化 |models.py 模型文件,放置如用户登录模型之类的非数据库模型 |tasks.py celery 异步任务 |static| 静态文件夹 |bootstrap| bootstrap 静态文件目录 |css |js |templates 模板文件夹,如项目较大,可以设置二级目录 |user user 的模板 |admin admin 的模板 |views 视图函数文件夹 |__init__.py 空文件夹,包占位 |user user 视图函数,可以根据对应功能创建,并在蓝图文件中注册 |admin admin 视图函数 |data| |mysql-volume MySQL 挂载目录 |redis-volume Redis 挂载目录
主要还是遵循了蓝图的设计模式,拆分了各个部分并存放到不同文件中。
项目代码 app.py 作为项目的启动文件,直接通过 create_app 启动,并加载 DevConfig 配置。app.py 本质上就是启动程序,因此不需要写入过多的内容,保持它专属的特性即可。
1 2 3 4 5 6 7 from app import create_app, configapp, celery = create_app(config.DevConfig) if __name__ == '__main__' : app.run(host='0.0.0.0' , port=5000 )
__init__.py _init _.py 文件负责导入对应的配置,完成蓝图的注册、插件模块的导入。
该文件创建了 create_app 用来创建 app 对象,并加载配置文件,如果没有则默认加载 BaseConfig。
使用 register_extensions 函数用来初始化拓展对象。需要从 extensions 中导入对应的实例化对象。
使用 register_blueprints 注册蓝图。需要从 views 下导入对应的蓝图,这里就导入了 user 和 admin 并进行注册。
load_user 为 flask_login 注册用户使用。
此外,经常会用到 celery 和 redis 构建异步消息队列,因此添加了初始化 celery 配置。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 from flask import Flaskfrom app.views.user.user import userfrom app.models import LoginUserfrom app.extensions import lm, db, celeryfrom app.config import BaseConfigdef create_app (config=BaseConfig ): app = Flask(__name__) app.config.from_object(config) register_extensions(app) register_blueprints(app) celery = make_celery(app) celery.set_default() return app, celery def register_extensions (app ): lm.init_app(app) lm.login_view = 'user.login' db.init_app(app) def make_celery (app ): celery.conf.update( broker_url=app.config["CELERY_BROKER_URL" ], result_backend=app.config["CELERY_RESULT_BACKEND" ] ) class ContextTask (celery.Task): """为 Celery 任务提供 Flask 应用上下文""" def __call__ (self, *args, **kwargs ): with app.app_context(): return self.run(*args, **kwargs) celery.Task = ContextTask return celery def register_blueprints (app ): app.register_blueprint(user) @lm.user_loader def load_user (user_id ): user = LoginUser(user_id) return user
config.py config.py 文件用于导入一些配置信息,例如数据库信息、开发模式或者生产模式的配置等。
Dev 模式为开发模式,开启了 debug,关闭了 csrf 保护。
Base 模式为基础设置,配置了数据库字段、秘钥以及 redis 的 broker 和 backend 地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class BaseConfig (object ): DEBUG = False SECRET_KEY = 'xxx' HOSTNAME = 'mysql' PORT = '3306' USERNAME = 'root' PASSWORD = '123456' DATABASE = 'flask_app' SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{USERNAME} :{PASSWORD} @{HOSTNAME} :{PORT} /{DATABASE} ?charset=utf8" REDIS_HOST = 'redis' CELERY_BROKER_URL = f'redis://{REDIS_HOST} :6379/1' , CELERY_RESULT_BACKEND = f'redis://{REDIS_HOST} :6379/2' class DevConfig (BaseConfig ): DEBUG = True ASSETS_DEBUG = True WTF_CSRF_ENABLED = False
database.py database.py 主要创建数据库模型,并配置数据表的对应字段属性,通过模型处理数据库。
创建了数据表模型,这里设置了 user 表,并添加了对应字段的信息,后续有新增的数据表也需要在此文件中添加。如果哪个视图函数需要对应的数据表操作,只需要导入对应的类就可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 from app.extensions import db""" 编写数据库连接 """ class UserTable (db.Model): __tablename__ = "user" id = db.Column(db.Integer, primary_key=True , autoincrement=True ) username = db.Column(db.String(100 ), nullable=False ) password = db.Column(db.String(100 ), nullable=False )
extensions.py 扩展导入文件,这里导入扩展 flask_login 和 flask_sqlalchemy,并实例化对象。用于管理扩展插件,但导入新拓展后需要在蓝图文件中初始化。
1 2 3 4 5 6 7 8 from celery import Celeryfrom flask_sqlalchemy import SQLAlchemyfrom flask_login import LoginManagerdb = SQLAlchemy() lm = LoginManager() celery = Celery()
models.py 放置非数据库的模型文件,这里放入 LoginUser 是用来在用户登录时进行注册的。
1 2 3 4 5 6 7 8 9 10 11 12 13 from flask_login import UserMixinclass LoginUser (UserMixin ): def __init__ (self, user_id ): self.id = user_id def get_id (self ): return str (self.id ) def is_authenticated (self ): return True
tasks.py 放置 celery 要执行的异步函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import timefrom datetime import datetimefrom app.extensions import celery@celery.task def log_access_time (user_id ): start_time = datetime.now() print (f"用户 {user_id} 访问时间: {start_time} " ) time.sleep(30 ) end_time = datetime.now() print (f"用户 {user_id} 结束时间: {end_time} " ) return start_time, end_time
views/user/user.py user 文件,通过 Blueprint 引入,并在蓝图文件注册,对应着 user 的一些功能。
需要数据库操作,则需要从 database 中引入模型,如果需要其他模型操作,需要从 models 下引入。
其中异步任务依赖 app.tasks 里面的异步任务函数,使用 delay 方法异步执行,并可通过 task_status/ 查询状态和结果。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 from flask import Blueprint, request, redirect, render_template, jsonifyfrom flask_login import login_required, login_user, logout_user, current_userfrom celery.result import AsyncResultfrom app.tasks import log_access_timefrom app.database import UserTablefrom app.models import LoginUseruser = Blueprint('user' , __name__) @user.route('/login/' , methods=['GET' , 'POST' ] ) def login (): if request.method == 'POST' : username = request.form.get('username' ) password = request.form.get('password' ) users = UserTable.query.filter_by(username=username).first() if users: if users.password == password: print (users.username) print (users.password) login_user(LoginUser(users.id )) task = log_access_time.delay(current_user.id ) return jsonify({ 'task_id' : task.id , 'status' : 'Login successful' }) else : return jsonify({'status' : 'Invalid password' }), 400 else : return jsonify({'status' : 'User not found' }), 404 return render_template('/user/login.html' ) @user.route('/task_status/<task_id>' , methods=['GET' ] ) @login_required def get_task_status (task_id ): task = AsyncResult(task_id) if task.state == 'PENDING' : response = { 'state' : task.state, 'status' : 'Pending...' , } elif task.state == 'SUCCESS' : response = { 'state' : task.state, 'result' : task.result, } else : response = { 'state' : task.state, 'status' : str (task.info), } return jsonify(response) @user.route('/dashboard/' ) @login_required def dashboard (): user_id = current_user.id user = UserTable.query.get(user_id) return f"欢迎 {user.username} 到你的仪表盘!" @user.route('/login_out/' ) @login_required def login_out (): logout_user() response = redirect('/' ) return response