from datetime import datetime from sqlalchemy import select, func from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from app.db.models import Assignment, AssignmentSubmission, User from app.schemas.assignment import AssignmentCreate, AssignmentUpdate, SubmissionCreate, SubmissionGrade from app.services.notification_service import create_notifications_for_class async def create_assignment( db: AsyncSession, class_id: int, creator_id: int, data: AssignmentCreate ) -> Assignment: assignment = Assignment( class_id=class_id, creator_id=creator_id, title=data.title, description=data.description, deadline=data.deadline, ) db.add(assignment) await db.commit() await db.refresh(assignment) await create_notifications_for_class( db, class_id, "assignment", f"新作业: {data.title}", content=data.description, related_id=assignment.id, email_subject=f"HKU ICB - 新作业: {data.title}", email_body=f"
{data.description or data.title}
", email_action_path="/assignments", ) return assignment async def update_assignment( db: AsyncSession, assignment: Assignment, data: AssignmentUpdate ) -> Assignment: for field, value in data.model_dump(exclude_unset=True).items(): setattr(assignment, field, value) await db.commit() await db.refresh(assignment) return assignment async def delete_assignment(db: AsyncSession, assignment: Assignment): await db.delete(assignment) await db.commit() async def get_assignment_by_id(db: AsyncSession, assignment_id: int) -> Assignment | None: result = await db.execute( select(Assignment) .options( selectinload(Assignment.creator), selectinload(Assignment.submissions).selectinload(AssignmentSubmission.student), ) .where(Assignment.id == assignment_id) ) return result.scalar_one_or_none() async def list_assignments( db: AsyncSession, class_id: int, page: int = 1, page_size: int = 20 ) -> tuple[list[Assignment], int]: total_result = await db.execute( select(func.count(Assignment.id)).where(Assignment.class_id == class_id) ) total = total_result.scalar() or 0 result = await db.execute( select(Assignment) .options( selectinload(Assignment.creator), selectinload(Assignment.submissions), ) .where(Assignment.class_id == class_id) .order_by(Assignment.created_at.desc()) .offset((page - 1) * page_size) .limit(page_size) ) return list(result.scalars().all()), total async def add_attachments(db: AsyncSession, assignment: Assignment, urls: list[str]): existing = assignment.get_attachment_urls_list() existing.extend(urls) assignment.set_attachment_urls_list(existing) await db.commit() await db.refresh(assignment) async def create_submission( db: AsyncSession, assignment_id: int, student_id: int, notes: str | None, file_url: str | None = None, file_name: str | None = None, file_type: str | None = None, file_size: int | None = None, ) -> AssignmentSubmission: # Check assignment exists and is open a_result = await db.execute(select(Assignment).where(Assignment.id == assignment_id)) assignment = a_result.scalar_one_or_none() if assignment is None: raise ValueError("作业不存在") if assignment.status != "open": raise ValueError("作业已关闭提交") if assignment.deadline and datetime.now() > assignment.deadline: raise ValueError("作业已过截止日期") # Check no existing submission existing = await db.execute( select(AssignmentSubmission).where( AssignmentSubmission.assignment_id == assignment_id, AssignmentSubmission.student_id == student_id, ) ) if existing.scalar_one_or_none(): raise ValueError("你已经提交过作业了") submission = AssignmentSubmission( assignment_id=assignment_id, student_id=student_id, notes=notes, file_url=file_url, file_name=file_name, file_type=file_type, file_size=file_size, ) db.add(submission) await db.commit() await db.refresh(submission) # Reload with student relationship result = await db.execute( select(AssignmentSubmission) .options(selectinload(AssignmentSubmission.student)) .where(AssignmentSubmission.id == submission.id) ) return result.scalar_one() async def get_submission_by_student( db: AsyncSession, assignment_id: int, student_id: int ) -> AssignmentSubmission | None: result = await db.execute( select(AssignmentSubmission) .options(selectinload(AssignmentSubmission.student)) .where( AssignmentSubmission.assignment_id == assignment_id, AssignmentSubmission.student_id == student_id, ) ) return result.scalar_one_or_none() async def grade_submission( db: AsyncSession, submission: AssignmentSubmission, data: SubmissionGrade ) -> AssignmentSubmission: submission.grade = data.grade submission.feedback = data.feedback submission.graded_at = datetime.now() await db.commit() await db.refresh(submission) return submission async def list_submissions(db: AsyncSession, assignment_id: int) -> list[AssignmentSubmission]: result = await db.execute( select(AssignmentSubmission) .options(selectinload(AssignmentSubmission.student)) .where(AssignmentSubmission.assignment_id == assignment_id) .order_by(AssignmentSubmission.created_at.desc()) ) return list(result.scalars().all())