157 lines
5.6 KiB
Python
157 lines
5.6 KiB
Python
from flask import Blueprint, request, jsonify, render_template, session
|
|
from app.models import db, Goal, StudentGoal, GoalRelation, StudentGoalEvaluation
|
|
from app.routes.auth import login_required_json, admin_required, login_required
|
|
from app.routes import main_bp
|
|
|
|
goals_bp = Blueprint("goals", __name__)
|
|
|
|
# 目标管理页面路由
|
|
@main_bp.route("/goals")
|
|
@login_required
|
|
def goals_page():
|
|
"""目标管理页面"""
|
|
return render_template("goals.html", active_nav="goals")
|
|
|
|
@goals_bp.route("/api/goals", methods=["GET"])
|
|
@login_required_json
|
|
def get_goals():
|
|
goals = Goal.query.order_by(Goal.created_at.desc()).all()
|
|
return jsonify([g.to_dict() for g in goals])
|
|
|
|
@goals_bp.route("/api/goals", methods=["POST"])
|
|
@login_required_json
|
|
def create_goal():
|
|
data = request.get_json()
|
|
goal = Goal(
|
|
name=data["name"],
|
|
content=data.get("content", ""),
|
|
level=data.get("level", "入门"),
|
|
category=data.get("category", "综合")
|
|
)
|
|
db.session.add(goal)
|
|
db.session.commit()
|
|
return jsonify(goal.to_dict()), 201
|
|
|
|
@goals_bp.route("/api/goals/<int:goal_id>", methods=["GET"])
|
|
@login_required_json
|
|
def get_goal(goal_id):
|
|
goal = Goal.query.get_or_404(goal_id)
|
|
return jsonify(goal.to_dict())
|
|
|
|
@goals_bp.route("/api/goals/<int:goal_id>", methods=["PUT"])
|
|
@login_required_json
|
|
def update_goal(goal_id):
|
|
goal = Goal.query.get_or_404(goal_id)
|
|
data = request.get_json()
|
|
if "name" in data:
|
|
goal.name = data["name"]
|
|
if "content" in data:
|
|
goal.content = data["content"]
|
|
if "level" in data:
|
|
goal.level = data["level"]
|
|
if "category" in data:
|
|
goal.category = data["category"]
|
|
db.session.commit()
|
|
return jsonify(goal.to_dict())
|
|
|
|
@goals_bp.route("/api/goals/<int:goal_id>", methods=["DELETE"])
|
|
@login_required_json
|
|
def delete_goal(goal_id):
|
|
"""删除目标模板(仅管理员,需检查依赖)"""
|
|
from app.models import User
|
|
user = User.query.get(session.get("user_id"))
|
|
if not user or user.role != "admin":
|
|
return jsonify({"error": "权限不足,仅管理员可操作"}), 403
|
|
|
|
goal = Goal.query.get_or_404(goal_id)
|
|
|
|
# 检查依赖关系
|
|
deps = []
|
|
|
|
# 1. 检查是否有学员分配了此目标
|
|
student_goal_count = StudentGoal.query.filter_by(goal_id=goal_id).count()
|
|
if student_goal_count > 0:
|
|
deps.append(f"已被 {student_goal_count} 名学员分配")
|
|
|
|
# 2. 检查是否有目标关联(作为父目标或子目标)
|
|
as_parent = GoalRelation.query.filter_by(parent_goal_id=goal_id).count()
|
|
as_child = GoalRelation.query.filter_by(child_goal_id=goal_id).count()
|
|
if as_parent > 0:
|
|
deps.append(f"有 {as_parent} 个子目标关联")
|
|
if as_child > 0:
|
|
deps.append(f"作为子目标被 {as_child} 个目标引用")
|
|
|
|
if deps:
|
|
return jsonify({"error": f"无法删除:{', '.join(deps)}"}), 400
|
|
|
|
db.session.delete(goal)
|
|
db.session.commit()
|
|
return jsonify({"message": "删除成功"})
|
|
|
|
# ===== 以下是 Task 3 的循环检测和关联 API =====
|
|
|
|
def _has_cycle(parent_id, child_id):
|
|
"""检测添加 child_id 作为 parent_id 的子目标是否会形成循环"""
|
|
visited = set()
|
|
stack = [child_id]
|
|
while stack:
|
|
current = stack.pop()
|
|
if current == parent_id:
|
|
return True
|
|
if current in visited:
|
|
continue
|
|
visited.add(current)
|
|
from app.models import GoalRelation
|
|
for rel in GoalRelation.query.filter_by(parent_goal_id=current).all():
|
|
stack.append(rel.child_goal_id)
|
|
return False
|
|
|
|
@goals_bp.route("/api/goals/<int:goal_id>/children", methods=["GET"])
|
|
@login_required_json
|
|
def get_goal_children(goal_id):
|
|
from app.models import GoalRelation
|
|
relations = GoalRelation.query.filter_by(parent_goal_id=goal_id).all()
|
|
child_ids = [r.child_goal_id for r in relations]
|
|
children = Goal.query.filter(Goal.id.in_(child_ids)).all() if child_ids else []
|
|
return jsonify([c.to_dict() for c in children])
|
|
|
|
@goals_bp.route("/api/goals/<int:goal_id>/parents", methods=["GET"])
|
|
@login_required_json
|
|
def get_goal_parents(goal_id):
|
|
from app.models import GoalRelation
|
|
relations = GoalRelation.query.filter_by(child_goal_id=goal_id).all()
|
|
parent_ids = [r.parent_goal_id for r in relations]
|
|
parents = Goal.query.filter(Goal.id.in_(parent_ids)).all() if parent_ids else []
|
|
return jsonify([p.to_dict() for p in parents])
|
|
|
|
@goals_bp.route("/api/goals/<int:goal_id>/children", methods=["POST"])
|
|
@login_required_json
|
|
def add_goal_child(goal_id):
|
|
from app.models import GoalRelation
|
|
data = request.get_json()
|
|
child_id = data["child_goal_id"]
|
|
|
|
if goal_id == child_id:
|
|
return jsonify({"error": "不能将目标关联到自身"}), 400
|
|
|
|
if _has_cycle(goal_id, child_id):
|
|
return jsonify({"error": "添加此关联会形成循环引用"}), 400
|
|
|
|
existing = GoalRelation.query.filter_by(parent_goal_id=goal_id, child_goal_id=child_id).first()
|
|
if existing:
|
|
return jsonify({"error": "关联已存在"}), 400
|
|
|
|
relation = GoalRelation(parent_goal_id=goal_id, child_goal_id=child_id)
|
|
db.session.add(relation)
|
|
db.session.commit()
|
|
return jsonify({"message": "添加成功"})
|
|
|
|
@goals_bp.route("/api/goals/<int:goal_id>/children/<int:child_id>", methods=["DELETE"])
|
|
@login_required_json
|
|
def remove_goal_child(goal_id, child_id):
|
|
from app.models import GoalRelation
|
|
relation = GoalRelation.query.filter_by(parent_goal_id=goal_id, child_goal_id=child_id).first()
|
|
if relation:
|
|
db.session.delete(relation)
|
|
db.session.commit()
|
|
return jsonify({"message": "移除成功"}) |