Skip to content

前几章把代码都写在一个文件里,只适合 demo;真实项目要"保持代码简洁且易于扩展"。本章把 Flask 项目改造成模块化结构:配置文件分离、应用工厂函数、蓝图分模块、Flask-Login 做用户认证,并用手动命令注册管理员。

配置文件(config.py)

把零散的 app.config[...] 整合为配置类,用类继承建立多套环境配置:

python
import os
basedir = os.path.abspath(os.path.dirname(__file__))

class Config:
    SECRET_KEY = 'Chapter6'
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    @staticmethod
    def init_app(app):   # 初始化时执行自定义操作(默认空)
        pass

class DevelopmentConfig(Config):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')

class TestingConfig(Config):
    TESTING = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'data-test.sqlite')

class ProductionConfig(Config):
    SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'data.sqlite')

configs = dict(development=DevelopmentConfig, testing=TestingConfig, production=ProductionConfig)

加载配置按环境变量 FLASK_ENV(命令行 set FLASK_ENV=development;PyCharm 默认 development,命令行默认 production):

python
from flask.helpers import get_env
from config import configs

env = get_env()
config = configs.get(env)
app.config.from_object(config)
config.init_app(app)

项目结构(模块化)

项目根/
├── manage.py          ← 应用管理入口(Manager)
├── config.py          ← 配置文件
└── app/               ← Python 包(templates/ static/ 移入其中)
    ├── __init__.py    ← 应用"工厂"函数 create_app(env)
    ├── database.py    ← BaseModel 基类 + DatabaseHelper
    ├── commands.py    ← 管理指令(如 create_admin)
    ├── user/          ← 用户模块(蓝图)
    │   ├── __init__.py  models.py  views.py  forms.py  errors.py  common.py
    └── admin/         ← 后台管理模块(含子模块)
        ├── __init__.py  views.py  common.py
        └── user/      ← 子模块:用户管理

应用"工厂"函数(app/init.py)

把所有初始化(配置、db、Migrate、Bootstrap、蓝图注册)封装成一个函数,不同环境调用即得不同应用:

python
bootstrap = Bootstrap()          # 扩展对象在函数外创建,供其他文件引用
db = SQLAlchemy()
dh = DatabaseHelper(db)

def create_app(env):
    app = Flask(__name__)
    config = configs.get(env)
    app.config.from_object(config)
    config.init_app(app)

    db.init_app(app)
    migrate = Migrate(app, db)
    migrate.init_app(app, render_as_batch=True)
    bootstrap.init_app(app)

    from .user import user
    app.register_blueprint(user, url_prefix='/user')      # 注册蓝图
    from .admin import admin, admin_user
    app.register_blueprint(admin, url_prefix='/admin')
    app.register_blueprint(admin_user, url_prefix='/admin/user')

    for cls in dh.get_all_model_classes():                 # 模型注册到模板全局
        app.add_template_global(cls)
    return app

register_blueprint(蓝图, url_prefix='/user') 给模块加 URL 前缀,如 /user/login

管理入口(manage.py)

python
from flask_script import Manager, Shell
from app import create_app, db, dh

env = get_env()
app = create_app(env)
manager = Manager(app)

def make_shell_context():
    context = dict(db=db, dh=dh)
    context.update((cls.__name__, cls) for cls in dh.get_all_model_classes())
    return context

manager.add_command("shell", Shell(make_context=make_shell_context))
manager.add_command('db', MigrateCommand)

if __name__ == '__main__':
    manager.run()

蓝图(Blueprint):模块化开发

蓝图把一组视图函数组合成一个模块,注册到应用后即可按模块组织路由。

python
# user/__init__.py —— 蓝图初始化
from flask import Blueprint
user = Blueprint('user', __name__)
from . import models, views, errors   # 引入即注册:模型被 db 捕捉、视图被蓝图捕捉
python
# user/views.py —— 视图用蓝图路由装饰器
@user.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    if request.method == 'POST' and form.validate_on_submit():
        item = UserModel(username=form.username.data, password=form.password.data,
                         info=UserInfoModel(phone=form.phone.data, email=form.email.data))
        try:
            dh.session.add(item); dh.session.commit()
            flash('注册成功', 'success'); return redirect('login')
        except Exception as e:
            flash('注册失败 - %s' % e, 'danger'); return abort(500)
    return render_template('user/register.html', form=form)

子模块:后台管理拆成"用户管理"子模块——admin/__init__.pyfrom .user import user as admin_user,再注册 url_prefix='/admin/user'

Flask-Login:用户认证

解决"登录后访问其他页面却未登录"的问题——基于 Session 实现用户认证与会话管理:

bash
pip install flask-login==0.4.1
python
# app/__init__.py 初始化
from flask_login import LoginManager
login_manager = LoginManager()

def create_app(env):
    # ...
    login_manager.login_view = 'user.login'            # 未登录跳转的视图
    login_manager.login_message_category = 'danger'
    login_manager.login_message = '请登录后再进行操作'
    login_manager.init_app(app)
python
# user/models.py —— 模型继承 UserMixin
class UserModel(UserMixin, db.Model, BaseModel):
    pass

# user/common.py —— 用户加载器
@login_manager.user_loader
def _load_user(id: int):
    return UserModel.query.get(id)

# 视图登录:用 login_user(item) 完成认证(替代手动会话)
from flask_login import login_user
login_user(item)

要求认证访问

@login_required 装饰器保护视图,未登录自动重定向到登录页;模板中用全局对象 current_user 判断登录态:

python
from flask_login import logout_user, login_required

@user.route('/logout')
@login_required
def logout():
    if logout_user():
        flash('注销成功', 'success'); return redirect('/user/login')
html
{% if current_user.is_anonymous %} 游客菜单:登录/注册 {% else %} {{ current_user.username }} 用户信息/注销 {% endif %}

管理员认证访问

包装 login_required,加管理员权限检查:

python
from functools import wraps
from flask_login import login_required

def admin_required(func):
    @wraps(func)
    def decorated_view(*args, **kwargs):
        if not get_current_user().is_admin:
            flash('拒绝访问', 'danger'); return abort(403)
        return func(*args, **kwargs)
    return login_required(decorated_view)

所有后台视图加 @admin_required 即可。

管理员注册(管理命令)

用 Flask-Script 命令代替手动改数据库:

python
# app/commands.py
def register_manage_commands(manager: Manager):
    @manager.command
    def create_admin():
        username = input('Input Username:')
        password = input('Input Password:')
        item = UserModel(username=username, password=password, is_admin=True,
                         info=UserInfoModel(introduce='Create By Manager.'))
        dh.session.add(item); dh.session.commit()
bash
python manage.py create_admin   # 命令行创建管理员

实践建议

  1. 项目一开始就用工厂函数 + 蓝图组织,别等代码膨胀再重构
  2. 配置按环境分离:开发/测试/生产用不同 FLASK_ENV 与数据库文件
  3. 需要登录的视图一律 @login_required,别手动判 current_user;管理员视图用 @admin_required
  4. 扩展对象(db/bootstrap/login_manager)在函数外创建,init_app 在工厂内调用——这是 Flask 扩展的标准用法
  5. 内置管理操作(建管理员、清缓存)用 @manager.command 注册,减少手工操作

一句话总结

项目一大就要拆:配置按环境用类继承分套、create_app(env) 工厂函数装配、蓝图按模块注册并加 url_prefix;扩展对象在函数外创建、init_app 在工厂内调用,需要登录的视图统一 @login_required

相关概念