185 lines
7.8 KiB
Python
185 lines
7.8 KiB
Python
# Flask 应用工厂
|
|
|
|
from flask import Flask
|
|
from app.models import db
|
|
import os
|
|
import pathlib
|
|
|
|
|
|
def create_app():
|
|
app = Flask(__name__)
|
|
|
|
# 使用 run.py 的目录作为项目根目录(绝对路径,不依赖工作目录)
|
|
import sys
|
|
if hasattr(sys, '_MEIPASS'):
|
|
BASE_DIR = pathlib.Path(sys._MEIPASS)
|
|
else:
|
|
BASE_DIR = pathlib.Path(__file__).resolve().parent.parent
|
|
|
|
# 存储到 app.config,各处引用,不再各自计算
|
|
app.config["BASE_DIR"] = BASE_DIR
|
|
app.config["SECRET_KEY"] = "piano-practice-plan-secret-key-2026"
|
|
app.config["DEBUG"] = True
|
|
app.config["SQLALCHEMY_DATABASE_URI"] = (
|
|
f"sqlite:///{BASE_DIR / 'data' / 'piano_plans.db'}"
|
|
)
|
|
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
|
|
|
app.config["PDF_OUTPUT_DIR"] = BASE_DIR / "output"
|
|
app.config["API_CONFIG_FILE"] = BASE_DIR / "config" / "api_config.json"
|
|
|
|
# 初始化数据库
|
|
db.init_app(app)
|
|
|
|
# 注册蓝图
|
|
from app.routes import main_bp
|
|
from app.routes.templates import templates_bp
|
|
from app.routes.goals import goals_bp
|
|
|
|
app.register_blueprint(main_bp)
|
|
app.register_blueprint(templates_bp)
|
|
app.register_blueprint(goals_bp)
|
|
from app.routes import student_goals
|
|
app.register_blueprint(student_goals.student_goals_bp)
|
|
|
|
# 创建数据库和目录
|
|
with app.app_context():
|
|
os.makedirs(os.path.join(BASE_DIR, "data"), exist_ok=True)
|
|
os.makedirs(app.config["PDF_OUTPUT_DIR"], exist_ok=True)
|
|
|
|
# 确保所有模型都被导入
|
|
from app.models import Student, Problem, StudentProblem, Template, PracticePlan
|
|
from app.models import Goal, GoalRelation, StudentGoal # 新增目标相关模型
|
|
|
|
db.create_all()
|
|
|
|
# 简单迁移:为已存在的数据库添加新字段(必须在init_default_templates之前)
|
|
try:
|
|
from sqlalchemy import text
|
|
|
|
# 检查practice_time字段是否存在
|
|
result = db.session.execute(text("PRAGMA table_info(students)"))
|
|
columns = [row[1] for row in result]
|
|
if "practice_time" not in columns:
|
|
db.session.execute(
|
|
text(
|
|
"ALTER TABLE students ADD COLUMN practice_time VARCHAR(20) DEFAULT '30-60分钟'"
|
|
)
|
|
)
|
|
db.session.commit()
|
|
if "wechat_nickname" not in columns:
|
|
db.session.execute(
|
|
text("ALTER TABLE students ADD COLUMN wechat_nickname VARCHAR(100)")
|
|
)
|
|
db.session.commit()
|
|
|
|
# 检查student_problems表的level字段
|
|
result2 = db.session.execute(text("PRAGMA table_info(student_problems)"))
|
|
columns2 = [row[1] for row in result2]
|
|
if "level" not in columns2:
|
|
db.session.execute(
|
|
text("ALTER TABLE student_problems ADD COLUMN level VARCHAR(20)")
|
|
)
|
|
db.session.commit()
|
|
|
|
# 检查users表是否存在
|
|
result3 = db.session.execute(
|
|
text(
|
|
"SELECT name FROM sqlite_master WHERE type='table' AND name='users'"
|
|
)
|
|
)
|
|
if not result3.fetchone():
|
|
db.session.execute(
|
|
text(
|
|
"""
|
|
CREATE TABLE users (
|
|
id INTEGER PRIMARY KEY,
|
|
username VARCHAR(50) UNIQUE NOT NULL,
|
|
password_hash VARCHAR(200) NOT NULL,
|
|
role VARCHAR(20) DEFAULT 'user',
|
|
created_at TIMESTAMP
|
|
)
|
|
"""
|
|
)
|
|
)
|
|
db.session.commit()
|
|
|
|
# 检查templates表是否有sort_order字段
|
|
result4 = db.session.execute(text("PRAGMA table_info(templates)"))
|
|
template_columns = [row[1] for row in result4]
|
|
if "sort_order" not in template_columns:
|
|
db.session.execute(text("ALTER TABLE templates ADD COLUMN sort_order INTEGER DEFAULT 0"))
|
|
db.session.commit()
|
|
|
|
# 检查practice_plans表是否有template_id字段
|
|
result5 = db.session.execute(text("PRAGMA table_info(practice_plans)"))
|
|
plan_columns = [row[1] for row in result5]
|
|
if "template_id" not in plan_columns:
|
|
db.session.execute(text("ALTER TABLE practice_plans ADD COLUMN template_id INTEGER REFERENCES templates(id)"))
|
|
db.session.commit()
|
|
|
|
# 检查practice_plans表是否有is_typical字段
|
|
result6 = db.session.execute(text("PRAGMA table_info(practice_plans)"))
|
|
plan_columns2 = [row[1] for row in result6]
|
|
if "is_typical" not in plan_columns2:
|
|
db.session.execute(text("ALTER TABLE practice_plans ADD COLUMN is_typical INTEGER DEFAULT 0"))
|
|
db.session.commit()
|
|
|
|
# 检查goals表是否有level字段
|
|
result7 = db.session.execute(text("PRAGMA table_info(goals)"))
|
|
goal_columns = [row[1] for row in result7]
|
|
if "level" not in goal_columns:
|
|
db.session.execute(text("ALTER TABLE goals ADD COLUMN level VARCHAR(20) DEFAULT '入门'"))
|
|
db.session.commit()
|
|
|
|
# 检查goals表是否有category字段
|
|
if "category" not in goal_columns:
|
|
db.session.execute(text("ALTER TABLE goals ADD COLUMN category VARCHAR(20) DEFAULT '综合'"))
|
|
db.session.commit()
|
|
|
|
# 迁移problems表分类:旧分类 -> 新分类
|
|
# 技术类/技术类(手型)/技术类(生理限制) -> 演奏能力
|
|
# 识谱类 -> 乐理相关
|
|
# 综合类 -> 综合类 (保持不变)
|
|
# 其他 -> 其他 (保持不变)
|
|
category_mapping = {
|
|
'技术类': '演奏能力',
|
|
'技术类(手型)': '演奏能力',
|
|
'技术类(生理限制)': '演奏能力',
|
|
'识谱类': '乐理相关',
|
|
}
|
|
for old_cat, new_cat in category_mapping.items():
|
|
db.session.execute(
|
|
text("UPDATE problems SET category = :new_cat WHERE category = :old_cat"),
|
|
{"new_cat": new_cat, "old_cat": old_cat}
|
|
)
|
|
db.session.commit()
|
|
|
|
# 检查student_goals表是否有新字段
|
|
result8 = db.session.execute(text("PRAGMA table_info(student_goals)"))
|
|
sg_columns = [row[1] for row in result8]
|
|
if "start_date" not in sg_columns:
|
|
db.session.execute(text("ALTER TABLE student_goals ADD COLUMN start_date TIMESTAMP"))
|
|
db.session.commit()
|
|
if "assessment_date" not in sg_columns:
|
|
db.session.execute(text("ALTER TABLE student_goals ADD COLUMN assessment_date TIMESTAMP"))
|
|
db.session.commit()
|
|
if "achievement_date" not in sg_columns:
|
|
db.session.execute(text("ALTER TABLE student_goals ADD COLUMN achievement_date TIMESTAMP"))
|
|
db.session.commit()
|
|
if "comment" not in sg_columns:
|
|
db.session.execute(text("ALTER TABLE student_goals ADD COLUMN comment TEXT"))
|
|
db.session.commit()
|
|
# 删除不再使用的字段
|
|
# deadline 和 completed_at 已被 start_date, assessment_date, achievement_date 取代
|
|
# status 字段现在由日期计算,不再存储
|
|
except Exception as e:
|
|
print(f"数据库迁移: {e}")
|
|
|
|
# 初始化默认模板(必须在迁移之后)
|
|
# 已禁用:如果需要默认模板,请手动创建
|
|
# from app.routes.templates import init_default_templates
|
|
# init_default_templates()
|
|
|
|
return app
|