180 lines
5.7 KiB
Python
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())
|