from flask_sqlalchemy import SQLAlchemy from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash from datetime import datetime db = SQLAlchemy() class User(UserMixin, db.Model): """用户模型""" __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) password_hash = db.Column(db.String(255), nullable=False) is_admin = db.Column(db.Boolean, default=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) def set_password(self, password): self.password_hash = generate_password_hash(password) def check_password(self, password): return check_password_hash(self.password_hash, password) def to_dict(self): return { 'id': self.id, 'username': self.username, 'email': self.email, 'is_admin': self.is_admin, 'created_at': self.created_at.isoformat() } class Article(db.Model): """文章/新闻模型""" __tablename__ = 'articles' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200), nullable=False) content = db.Column(db.Text, nullable=False) summary = db.Column(db.Text) category = db.Column(db.String(50), default='news') # news, publication, etc. is_protected = db.Column(db.Boolean, default=False) # 是否需要登录查看 author_id = db.Column(db.Integer, db.ForeignKey('users.id')) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) published = db.Column(db.Boolean, default=True) author = db.relationship('User', backref='articles') def to_dict(self): return { 'id': self.id, 'title': self.title, 'content': self.content, 'summary': self.summary, 'category': self.category, 'is_protected': self.is_protected, 'author_id': self.author_id, 'created_at': self.created_at.isoformat(), 'updated_at': self.updated_at.isoformat(), 'published': self.published } class Resource(db.Model): """资源文件模型""" __tablename__ = 'resources' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(200), nullable=False) description = db.Column(db.Text) file_path = db.Column(db.String(500), nullable=False) file_size = db.Column(db.Integer) # 文件大小(字节) file_type = db.Column(db.String(50)) # 文件类型 category = db.Column(db.String(50)) # software, dataset, document, etc. is_protected = db.Column(db.Boolean, default=False) # 是否需要登录下载 download_count = 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) def to_dict(self): return { 'id': self.id, 'name': self.name, 'description': self.description, 'file_path': self.file_path, 'file_size': self.file_size, 'file_type': self.file_type, 'category': self.category, 'is_protected': self.is_protected, 'download_count': self.download_count, 'created_at': self.created_at.isoformat(), 'updated_at': self.updated_at.isoformat() } class TeamMember(db.Model): """团队成员模型""" __tablename__ = 'team_members' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) title = db.Column(db.String(200)) title_en = db.Column(db.String(200)) bio = db.Column(db.Text) bio_en = db.Column(db.Text) role = db.Column(db.String(50)) # professor, student, alumni email = db.Column(db.String(120)) website = db.Column(db.String(500)) avatar_path = db.Column(db.String(500)) order = 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) def to_dict(self): return { 'id': self.id, 'name': self.name, 'title': self.title, 'title_en': self.title_en, 'bio': self.bio, 'bio_en': self.bio_en, 'role': self.role, 'email': self.email, 'website': self.website, 'avatar_path': self.avatar_path, 'order': self.order, 'created_at': self.created_at.isoformat(), 'updated_at': self.updated_at.isoformat() } class Meeting(db.Model): """组会模型""" __tablename__ = 'meetings' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200), nullable=False) description = db.Column(db.Text) date = db.Column(db.Date, nullable=False) time = db.Column(db.Time, nullable=False) location = db.Column(db.String(200)) organizer_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) status = db.Column(db.String(50), default='planned') # planned, ongoing, completed, cancelled meeting_type = db.Column(db.String(50), default='weekly') # weekly, monthly, special meeting_notes = db.Column(db.Text) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) organizer = db.relationship('User', backref='organized_meetings') registrations = db.relationship('MeetingRegistration', backref='meeting', cascade='all, delete-orphan') files = db.relationship('MeetingFile', backref='meeting', cascade='all, delete-orphan') def to_dict(self): return { 'id': self.id, 'title': self.title, 'description': self.description, 'date': self.date.isoformat() if self.date else None, 'time': self.time.strftime('%H:%M') if self.time else None, 'location': self.location, 'organizer_id': self.organizer_id, 'organizer_name': self.organizer.username if self.organizer else None, 'status': self.status, 'meeting_type': self.meeting_type, 'meeting_notes': self.meeting_notes, 'created_at': self.created_at.isoformat(), 'updated_at': self.updated_at.isoformat(), 'attendee_count': len(self.registrations) if self.registrations else 0 } class MeetingRegistration(db.Model): """组会报名模型""" __tablename__ = 'meeting_registrations' id = db.Column(db.Integer, primary_key=True) meeting_id = db.Column(db.Integer, db.ForeignKey('meetings.id'), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) registered_at = db.Column(db.DateTime, default=datetime.utcnow) status = db.Column(db.String(50), default='registered') # registered, checked_in, absent user = db.relationship('User', backref='meeting_registrations') def to_dict(self): return { 'id': self.id, 'meeting_id': self.meeting_id, 'user_id': self.user_id, 'username': self.user.username if self.user else None, 'registered_at': self.registered_at.isoformat(), 'status': self.status } class MeetingFile(db.Model): """组会文件模型""" __tablename__ = 'meeting_files' id = db.Column(db.Integer, primary_key=True) meeting_id = db.Column(db.Integer, db.ForeignKey('meetings.id'), nullable=False) name = db.Column(db.String(200), nullable=False) file_path = db.Column(db.String(500), nullable=False) file_size = db.Column(db.Integer) file_type = db.Column(db.String(50)) uploader_id = db.Column(db.Integer, db.ForeignKey('users.id')) uploaded_at = db.Column(db.DateTime, default=datetime.utcnow) uploader = db.relationship('User', backref='uploaded_meeting_files') def to_dict(self): return { 'id': self.id, 'meeting_id': self.meeting_id, 'name': self.name, 'file_path': self.file_path, 'file_size': self.file_size, 'file_type': self.file_type, 'uploader_id': self.uploader_id, 'uploaded_at': self.uploaded_at.isoformat() } class ResearchProject(db.Model): """研究项目模型""" __tablename__ = 'research_projects' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200), nullable=False) description = db.Column(db.Text) start_date = db.Column(db.Date) end_date = db.Column(db.Date) owner_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) status = db.Column(db.String(50), default='active') # active, completed, paused, cancelled progress = db.Column(db.Integer, default=0) # 0-100 tags = db.Column(db.String(500)) # 逗号分隔的标签 created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) owner = db.relationship('User', backref='research_projects') notes = db.relationship('ResearchNote', backref='project', cascade='all, delete-orphan') tasks = db.relationship('ResearchTask', backref='project', cascade='all, delete-orphan') files = db.relationship('ResearchFile', backref='project', cascade='all, delete-orphan') def to_dict(self): return { 'id': self.id, 'title': self.title, 'description': self.description, 'start_date': self.start_date.isoformat() if self.start_date else None, 'end_date': self.end_date.isoformat() if self.end_date else None, 'owner_id': self.owner_id, 'owner_name': self.owner.username if self.owner else None, 'status': self.status, 'progress': self.progress, 'tags': self.tags.split(',') if self.tags else [], 'created_at': self.created_at.isoformat(), 'updated_at': self.updated_at.isoformat() } class ResearchNote(db.Model): """研究笔记模型""" __tablename__ = 'research_notes' id = db.Column(db.Integer, primary_key=True) project_id = db.Column(db.Integer, db.ForeignKey('research_projects.id'), nullable=False) title = db.Column(db.String(200), nullable=False) content = db.Column(db.Text, nullable=False) author_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) note_type = db.Column(db.String(50), default='note') # note, experiment, literature, idea created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) author = db.relationship('User', backref='research_notes') def to_dict(self): return { 'id': self.id, 'project_id': self.project_id, 'title': self.title, 'content': self.content, 'author_id': self.author_id, 'author_name': self.author.username if self.author else None, 'note_type': self.note_type, 'created_at': self.created_at.isoformat(), 'updated_at': self.updated_at.isoformat() } class ResearchTask(db.Model): """研究任务模型""" __tablename__ = 'research_tasks' id = db.Column(db.Integer, primary_key=True) project_id = db.Column(db.Integer, db.ForeignKey('research_projects.id'), nullable=False) title = db.Column(db.String(200), nullable=False) description = db.Column(db.Text) assignee_id = db.Column(db.Integer, db.ForeignKey('users.id')) due_date = db.Column(db.Date) status = db.Column(db.String(50), default='todo') # todo, in_progress, completed, cancelled priority = db.Column(db.String(50), default='medium') # high, medium, low created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) assignee = db.relationship('User', backref='assigned_tasks') def to_dict(self): return { 'id': self.id, 'project_id': self.project_id, 'title': self.title, 'description': self.description, 'assignee_id': self.assignee_id, 'assignee_name': self.assignee.username if self.assignee else None, 'due_date': self.due_date.isoformat() if self.due_date else None, 'status': self.status, 'priority': self.priority, 'created_at': self.created_at.isoformat(), 'updated_at': self.updated_at.isoformat() } class ResearchFile(db.Model): """研究文件模型""" __tablename__ = 'research_files' id = db.Column(db.Integer, primary_key=True) project_id = db.Column(db.Integer, db.ForeignKey('research_projects.id'), nullable=False) name = db.Column(db.String(200), nullable=False) file_path = db.Column(db.String(500), nullable=False) file_size = db.Column(db.Integer) file_type = db.Column(db.String(50)) uploader_id = db.Column(db.Integer, db.ForeignKey('users.id')) uploaded_at = db.Column(db.DateTime, default=datetime.utcnow) uploader = db.relationship('User', backref='uploaded_research_files') def to_dict(self): return { 'id': self.id, 'project_id': self.project_id, 'name': self.name, 'file_path': self.file_path, 'file_size': self.file_size, 'file_type': self.file_type, 'uploader_id': self.uploader_id, 'uploaded_at': self.uploaded_at.isoformat() }