# models.py from flask_sqlalchemy import SQLAlchemy from datetime import datetime import json db = SQLAlchemy() class User(db.Model): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(80), nullable=False) email = db.Column(db.String(120), unique=True, nullable=True) phone = db.Column(db.String(20), unique=True, nullable=True) password = db.Column(db.String(128), nullable=False) role = db.Column(db.String(20), default='student') # student, teacher, admin created_at = db.Column(db.DateTime, default=datetime.utcnow) avatar = db.Column(db.String(200), nullable=True) exams_created = db.relationship('Exam', backref='creator', lazy=True) submissions = db.relationship('Submission', backref='user', lazy=True) drafts = db.relationship('Draft', backref='user', lazy=True) posts = db.relationship('Post', backref='author', lazy=True) replies = db.relationship('Reply', backref='author', lazy=True) reactions = db.relationship('Reaction', backref='user', lazy=True) bookmarks = db.relationship('Bookmark', backref='user', lazy=True) notifications = db.relationship('Notification', backref='user', lazy=True) contest_registrations = db.relationship('ContestRegistration', backref='user', lazy=True) teacher_applications = db.relationship('TeacherApplication', backref='user', lazy=True) # exam_bookmarks 关系将由 ExamBookmark 中的 backref 自动创建,此处不再定义 class Exam(db.Model): __tablename__ = 'exam' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200), nullable=False) subject = db.Column(db.String(50)) duration = db.Column(db.Integer, default=120) total_score = db.Column(db.Integer, default=100) status = db.Column(db.String(20), default='available') creator_id = db.Column(db.Integer, db.ForeignKey('user.id')) created_at = db.Column(db.DateTime, default=datetime.utcnow) _questions = db.Column(db.Text, default='[]') submissions = db.relationship('Submission', backref='exam', lazy=True, cascade='all, delete-orphan') drafts = db.relationship('Draft', backref='exam', lazy=True, cascade='all, delete-orphan') # bookmarked_by 关系将由 ExamBookmark 中的 backref 自动创建,此处不再定义 def set_questions(self, questions): self._questions = json.dumps(questions, ensure_ascii=False) def get_questions(self): return json.loads(self._questions) if self._questions else [] class Submission(db.Model): __tablename__ = 'submission' id = db.Column(db.Integer, primary_key=True) exam_id = db.Column(db.Integer, db.ForeignKey('exam.id'), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) _answers = db.Column(db.Text, default='{}') _question_scores = db.Column(db.Text, default='{}') score = db.Column(db.Integer, default=0) graded = db.Column(db.Boolean, default=False) graded_by = db.Column(db.String(80), default='') submitted_at = db.Column(db.DateTime, default=datetime.utcnow) def set_answers(self, answers): self._answers = json.dumps(answers, ensure_ascii=False) def get_answers(self): return json.loads(self._answers) if self._answers else {} def set_question_scores(self, scores): self._question_scores = json.dumps(scores, ensure_ascii=False) def get_question_scores(self): return json.loads(self._question_scores) if self._question_scores else {} @property def user_name(self): return self.user.name if self.user else '' class Draft(db.Model): __tablename__ = 'draft' id = db.Column(db.Integer, primary_key=True) exam_id = db.Column(db.Integer, db.ForeignKey('exam.id'), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) _answers = db.Column(db.Text, default='{}') updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) def set_answers(self, answers): self._answers = json.dumps(answers, ensure_ascii=False) def get_answers(self): return json.loads(self._answers) if self._answers else {} class Contest(db.Model): __tablename__ = 'contest' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(200), nullable=False) organizer = db.Column(db.String(100)) description = db.Column(db.Text) start_date = db.Column(db.String(20)) end_date = db.Column(db.String(20)) status = db.Column(db.String(20), default='upcoming') participants = db.Column(db.Integer, default=0) created_by = db.Column(db.String(50)) contact = db.Column(db.String(100)) _past_papers = db.Column(db.Text, default='[]') created_at = db.Column(db.DateTime, default=datetime.utcnow) registrations = db.relationship('ContestRegistration', backref='contest', lazy=True) posts = db.relationship('Post', backref='contest', lazy=True) def set_past_papers(self, papers): self._past_papers = json.dumps(papers, ensure_ascii=False) def get_past_papers(self): return json.loads(self._past_papers) if self._past_papers else [] class ContestRegistration(db.Model): __tablename__ = 'contest_registration' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) contest_id = db.Column(db.Integer, db.ForeignKey('contest.id'), nullable=False) registered_at = db.Column(db.DateTime, default=datetime.utcnow) class ContestMembership(db.Model): __tablename__ = 'contest_membership' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) contest_id = db.Column(db.Integer, db.ForeignKey('contest.id'), nullable=False) role = db.Column(db.String(20), default='member') joined_at = db.Column(db.DateTime, default=datetime.utcnow) class Post(db.Model): __tablename__ = 'post' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200), nullable=False) content = db.Column(db.Text, nullable=False) author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) contest_id = db.Column(db.Integer, db.ForeignKey('contest.id'), nullable=True) tag = db.Column(db.String(50), default='全部') is_official = db.Column(db.Boolean, default=False) pinned = db.Column(db.Boolean, default=False) likes = db.Column(db.Integer, default=0) replies_count = db.Column(db.Integer, default=0) views = db.Column(db.Integer, default=0) has_poll = db.Column(db.Boolean, default=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) replies = db.relationship('Reply', backref='post', lazy=True, cascade='all, delete-orphan') reactions = db.relationship('Reaction', backref='post', lazy=True, cascade='all, delete-orphan') bookmarks = db.relationship('Bookmark', backref='post', lazy=True, cascade='all, delete-orphan') poll = db.relationship('Poll', backref='post', uselist=False, cascade='all, delete-orphan') class Reply(db.Model): __tablename__ = 'reply' id = db.Column(db.Integer, primary_key=True) post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False) author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) content = db.Column(db.Text, nullable=False) reply_to = db.Column(db.String(80), nullable=True) likes = db.Column(db.Integer, default=0) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) reactions = db.relationship('Reaction', backref='reply', lazy=True, cascade='all, delete-orphan') class Poll(db.Model): __tablename__ = 'poll' id = db.Column(db.Integer, primary_key=True) post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False, unique=True) question = db.Column(db.String(200), nullable=False) multi = db.Column(db.Boolean, default=False) total_votes = db.Column(db.Integer, default=0) _options = db.Column(db.Text, default='[]') _voters = db.Column(db.Text, default='{}') def set_options(self, options): self._options = json.dumps(options, ensure_ascii=False) def get_options(self): return json.loads(self._options) if self._options else [] def set_voters(self, voters): self._voters = json.dumps(voters, ensure_ascii=False) def get_voters(self): return json.loads(self._voters) if self._voters else {} class Reaction(db.Model): __tablename__ = 'reaction' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=True) reply_id = db.Column(db.Integer, db.ForeignKey('reply.id'), nullable=True) reaction = db.Column(db.String(20), nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) class Bookmark(db.Model): __tablename__ = 'bookmark' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) class Notification(db.Model): __tablename__ = 'notification' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) type = db.Column(db.String(50)) content = db.Column(db.String(200)) from_user = db.Column(db.String(80)) post_id = db.Column(db.Integer, nullable=True) read = db.Column(db.Boolean, default=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) class EditHistory(db.Model): __tablename__ = 'edit_history' id = db.Column(db.Integer, primary_key=True) post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=True) reply_id = db.Column(db.Integer, db.ForeignKey('reply.id'), nullable=True) editor_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) old_content = db.Column(db.Text) new_content = db.Column(db.Text) edited_at = db.Column(db.DateTime, default=datetime.utcnow) class Report(db.Model): __tablename__ = 'report' id = db.Column(db.Integer, primary_key=True) type = db.Column(db.String(20)) target_id = db.Column(db.Integer, nullable=False) reporter_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) reason = db.Column(db.String(100)) detail = db.Column(db.Text) status = db.Column(db.String(20), default='pending') created_at = db.Column(db.DateTime, default=datetime.utcnow) class TeacherApplication(db.Model): __tablename__ = 'teacher_application' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) name = db.Column(db.String(80), nullable=False) email = db.Column(db.String(120), nullable=False) reason = db.Column(db.Text, nullable=False) status = db.Column(db.String(20), default='pending') created_at = db.Column(db.DateTime, default=datetime.utcnow) class Friend(db.Model): __tablename__ = 'friend' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) friend_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) status = db.Column(db.String(20), default='pending') created_at = db.Column(db.DateTime, default=datetime.utcnow) user = db.relationship('User', foreign_keys=[user_id], backref=db.backref('friends_initiated', lazy='dynamic')) friend = db.relationship('User', foreign_keys=[friend_id], backref=db.backref('friends_received', lazy='dynamic')) class ExamBookmark(db.Model): __tablename__ = 'exam_bookmark' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) exam_id = db.Column(db.Integer, db.ForeignKey('exam.id'), nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) # 使用 backref 自动在 User 和 Exam 上创建 'exam_bookmarks' 和 'bookmarked_by' 属性 user = db.relationship('User', backref=db.backref('exam_bookmarks', lazy='dynamic', cascade='all, delete-orphan')) exam = db.relationship('Exam', backref=db.backref('bookmarked_by', lazy='dynamic', cascade='all, delete-orphan')) __table_args__ = (db.UniqueConstraint('user_id', 'exam_id', name='unique_exam_bookmark'),)