Files
012-kaopeilian/backend/app/models/course.py
yuliang_guo 41a2f7944a
All checks were successful
continuous-integration/drone/push Build is passing
fix: 修复flake8 lint检查错误
- 删除废弃的 admin_positions_backup.py 备份文件
- 修复 courses.py 缺失的 select 导入
- 修复 coze_gateway.py 异常变量作用域问题
- 修复 scheduler_service.py 无用的 global 声明
- 添加 TYPE_CHECKING 导入解决模型前向引用警告
2026-01-31 17:43:39 +08:00

292 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
课程相关数据库模型
"""
from enum import Enum
from typing import List, Optional, TYPE_CHECKING
from datetime import datetime
if TYPE_CHECKING:
from app.models.growth_path import GrowthPathNode
from sqlalchemy import (
String,
Text,
Integer,
Boolean,
ForeignKey,
Enum as SQLEnum,
Float,
JSON,
DateTime,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.base import BaseModel, SoftDeleteMixin, AuditMixin
class CourseStatus(str, Enum):
"""课程状态枚举"""
DRAFT = "draft" # 草稿
PUBLISHED = "published" # 已发布
ARCHIVED = "archived" # 已归档
class CourseCategory(str, Enum):
"""课程分类枚举"""
TECHNOLOGY = "technology" # 技术
MANAGEMENT = "management" # 管理
BUSINESS = "business" # 业务
GENERAL = "general" # 通用
class Course(BaseModel, SoftDeleteMixin, AuditMixin):
"""
课程表
"""
__tablename__ = "courses"
# 基本信息
name: Mapped[str] = mapped_column(String(200), nullable=False, comment="课程名称")
description: Mapped[Optional[str]] = mapped_column(
Text, nullable=True, comment="课程描述"
)
category: Mapped[CourseCategory] = mapped_column(
SQLEnum(
CourseCategory,
values_callable=lambda enum_cls: [e.value for e in enum_cls],
validate_strings=True,
),
default=CourseCategory.GENERAL,
nullable=False,
comment="课程分类",
)
status: Mapped[CourseStatus] = mapped_column(
SQLEnum(
CourseStatus,
values_callable=lambda enum_cls: [e.value for e in enum_cls],
validate_strings=True,
),
default=CourseStatus.DRAFT,
nullable=False,
comment="课程状态",
)
# 课程详情
cover_image: Mapped[Optional[str]] = mapped_column(
String(500), nullable=True, comment="封面图片URL"
)
duration_hours: Mapped[Optional[float]] = mapped_column(
Float, nullable=True, comment="课程时长(小时)"
)
difficulty_level: Mapped[Optional[int]] = mapped_column(
Integer, nullable=True, comment="难度等级(1-5)"
)
tags: Mapped[Optional[List[str]]] = mapped_column(
JSON, nullable=True, comment="标签列表"
)
# 发布信息
published_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), nullable=True, comment="发布时间"
)
publisher_id: Mapped[Optional[int]] = mapped_column(
Integer, nullable=True, comment="发布人ID"
)
# 播课信息
# 播课功能Coze工作流直接写数据库
broadcast_audio_url: Mapped[Optional[str]] = mapped_column(
String(500), nullable=True, comment="播课音频URL"
)
broadcast_generated_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), nullable=True, comment="播课生成时间"
)
# 排序和权重
sort_order: Mapped[int] = mapped_column(
Integer, default=0, nullable=False, comment="排序顺序"
)
is_featured: Mapped[bool] = mapped_column(
Boolean, default=False, nullable=False, comment="是否推荐"
)
# 统计信息
student_count: Mapped[int] = mapped_column(
Integer, default=0, nullable=False, comment="学习人数"
)
is_new: Mapped[bool] = mapped_column(
Boolean, default=True, nullable=False, comment="是否新课程"
)
# 资料下载设置
allow_download: Mapped[bool] = mapped_column(
Boolean, default=False, nullable=False, comment="是否允许下载资料"
)
# 关联关系
materials: Mapped[List["CourseMaterial"]] = relationship(
"CourseMaterial", back_populates="course"
)
knowledge_points: Mapped[List["KnowledgePoint"]] = relationship(
"KnowledgePoint", back_populates="course"
)
# 岗位分配关系(通过关联表)
position_assignments = relationship("PositionCourse", back_populates="course", cascade="all, delete-orphan")
exams = relationship("Exam", back_populates="course")
questions = relationship("Question", back_populates="course")
class CourseMaterial(BaseModel, SoftDeleteMixin, AuditMixin):
"""
课程资料表
"""
__tablename__ = "course_materials"
course_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("courses.id", ondelete="CASCADE"),
nullable=False,
comment="课程ID",
)
name: Mapped[str] = mapped_column(String(200), nullable=False, comment="资料名称")
description: Mapped[Optional[str]] = mapped_column(
Text, nullable=True, comment="资料描述"
)
file_url: Mapped[str] = mapped_column(String(500), nullable=False, comment="文件URL")
file_type: Mapped[str] = mapped_column(String(50), nullable=False, comment="文件类型")
file_size: Mapped[int] = mapped_column(Integer, nullable=False, comment="文件大小(字节)")
# 排序
sort_order: Mapped[int] = mapped_column(
Integer, default=0, nullable=False, comment="排序顺序"
)
# 关联关系
course: Mapped["Course"] = relationship("Course", back_populates="materials")
# 关联的知识点(直接关联)
knowledge_points: Mapped[List["KnowledgePoint"]] = relationship(
"KnowledgePoint", back_populates="material"
)
class KnowledgePoint(BaseModel, SoftDeleteMixin, AuditMixin):
"""
知识点表
"""
__tablename__ = "knowledge_points"
course_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("courses.id", ondelete="CASCADE"),
nullable=False,
comment="课程ID",
)
material_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("course_materials.id", ondelete="CASCADE"),
nullable=False,
comment="关联资料ID",
)
name: Mapped[str] = mapped_column(String(200), nullable=False, comment="知识点名称")
description: Mapped[Optional[str]] = mapped_column(
Text, nullable=True, comment="知识点描述"
)
type: Mapped[str] = mapped_column(
String(50), default="概念定义", nullable=False, comment="知识点类型"
)
source: Mapped[int] = mapped_column(
Integer, default=0, nullable=False, comment="来源0=手动1=AI分析"
)
topic_relation: Mapped[Optional[str]] = mapped_column(
String(200), nullable=True, comment="与主题的关系描述"
)
# 关联关系
course: Mapped["Course"] = relationship("Course", back_populates="knowledge_points")
material: Mapped["CourseMaterial"] = relationship("CourseMaterial")
class GrowthPath(BaseModel, SoftDeleteMixin):
"""
成长路径表
"""
__tablename__ = "growth_paths"
name: Mapped[str] = mapped_column(String(200), nullable=False, comment="路径名称")
description: Mapped[Optional[str]] = mapped_column(
Text, nullable=True, comment="路径描述"
)
target_role: Mapped[Optional[str]] = mapped_column(
String(100), nullable=True, comment="目标角色"
)
# 岗位关联
position_id: Mapped[Optional[int]] = mapped_column(
Integer, nullable=True, comment="关联岗位ID兼容旧版优先使用position_ids"
)
position_ids: Mapped[Optional[List[int]]] = mapped_column(
JSON, nullable=True, comment="关联岗位ID列表支持多选"
)
# 路径配置(保留用于兼容,新版使用 nodes 关联表)
courses: Mapped[Optional[List[dict]]] = mapped_column(
JSON, nullable=True, comment="课程列表[{course_id, order, is_required}]已废弃使用nodes"
)
# 阶段配置
stages: Mapped[Optional[List[dict]]] = mapped_column(
JSON, nullable=True, comment="阶段配置[{name, description, order}]"
)
# 预计时长
estimated_duration_days: Mapped[Optional[int]] = mapped_column(
Integer, nullable=True, comment="预计完成天数"
)
# 状态
is_active: Mapped[bool] = mapped_column(
Boolean, default=True, nullable=False, comment="是否启用"
)
sort_order: Mapped[int] = mapped_column(
Integer, default=0, nullable=False, comment="排序顺序"
)
# 关联关系
nodes: Mapped[List["GrowthPathNode"]] = relationship(
"GrowthPathNode", back_populates="growth_path", cascade="all, delete-orphan"
)
class MaterialKnowledgePoint(BaseModel, SoftDeleteMixin):
"""
资料知识点关联表
"""
__tablename__ = "material_knowledge_points"
material_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("course_materials.id", ondelete="CASCADE"),
nullable=False,
comment="资料ID",
)
knowledge_point_id: Mapped[int] = mapped_column(
Integer,
ForeignKey("knowledge_points.id", ondelete="CASCADE"),
nullable=False,
comment="知识点ID",
)
sort_order: Mapped[int] = mapped_column(
Integer, default=0, nullable=False, comment="排序顺序"
)
is_ai_generated: Mapped[bool] = mapped_column(
Boolean, default=False, nullable=False, comment="是否AI生成"
)