hku-class/backend/app/services/assignment_service.py
2026-04-12 18:15:38 +08:00

180 lines
5.7 KiB
Python

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"<p>{data.description or data.title}</p>",
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())