mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-18 20:09:25 +00:00
fix: misc assignments bugs
This commit is contained in:
parent
d244ff2076
commit
a6b5db29ca
6 changed files with 121 additions and 37 deletions
|
|
@ -162,7 +162,7 @@ class AssignmentTask(AssignmentTaskBase, table=True):
|
|||
|
||||
class AssignmentTaskSubmissionBase(SQLModel):
|
||||
"""Represents the common fields for an assignment task submission."""
|
||||
|
||||
assignment_task_submission_uuid: str
|
||||
task_submission: Dict = Field(default={}, sa_column=Column(JSON))
|
||||
grade: int = 0 # Value is always between 0-100
|
||||
task_submission_grade_feedback: str
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ from src.security.rbac.rbac import (
|
|||
authorization_verify_based_on_roles_and_authorship,
|
||||
authorization_verify_if_element_is_public,
|
||||
authorization_verify_if_user_is_anon,
|
||||
authorization_verify_based_on_roles,
|
||||
)
|
||||
from src.services.courses.activities.uploads.sub_file import upload_submission_file
|
||||
from src.services.courses.activities.uploads.tasks_ref_files import (
|
||||
|
|
@ -565,10 +566,17 @@ async def put_assignment_task_submission_file(
|
|||
org_statement = select(Organization).where(Organization.id == course.org_id)
|
||||
org = db_session.exec(org_statement).first()
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, course.course_uuid, current_user, "update", db_session)
|
||||
# RBAC check - only need read permission to submit files
|
||||
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
||||
|
||||
# Upload reference file
|
||||
# Check if user is enrolled in the course
|
||||
if not await authorization_verify_based_on_roles(request, current_user.id, "read", course.course_uuid, db_session):
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="You must be enrolled in this course to submit files"
|
||||
)
|
||||
|
||||
# Upload submission file
|
||||
if sub_file and sub_file.filename and activity and org:
|
||||
name_in_disk = f"{assignment_task_uuid}_sub_{current_user.email}_{uuid4()}.{sub_file.filename.split('.')[-1]}"
|
||||
await upload_submission_file(
|
||||
|
|
@ -699,7 +707,7 @@ async def handle_assignment_task_submission(
|
|||
current_user: PublicUser | AnonymousUser,
|
||||
db_session: Session,
|
||||
):
|
||||
# TODO: Improve terrible implementation of this function
|
||||
assignment_task_submission_uuid = assignment_task_submission_object.assignment_task_submission_uuid
|
||||
# Check if assignment task exists
|
||||
statement = select(AssignmentTask).where(
|
||||
AssignmentTask.assignment_task_uuid == assignment_task_uuid
|
||||
|
|
@ -722,15 +730,59 @@ async def handle_assignment_task_submission(
|
|||
detail="Assignment not found",
|
||||
)
|
||||
|
||||
# Check if user already submitted the assignment
|
||||
statement = select(AssignmentTaskSubmission).where(
|
||||
AssignmentTaskSubmission.assignment_task_id == assignment_task.id,
|
||||
AssignmentTaskSubmission.user_id == current_user.id,
|
||||
)
|
||||
assignment_task_submission = db_session.exec(statement).first()
|
||||
# Check if course exists
|
||||
statement = select(Course).where(Course.id == assignment.course_id)
|
||||
course = db_session.exec(statement).first()
|
||||
|
||||
# Update Task submission if it exists
|
||||
if not course:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Course not found",
|
||||
)
|
||||
|
||||
# Check if user has instructor/admin permissions
|
||||
is_instructor = await authorization_verify_based_on_roles(request, current_user.id, "update", course.course_uuid, db_session)
|
||||
|
||||
# For regular users, ensure they can only submit their own work
|
||||
if not is_instructor:
|
||||
# Check if user is enrolled in the course
|
||||
if not await authorization_verify_based_on_roles(request, current_user.id, "read", course.course_uuid, db_session):
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="You must be enrolled in this course to submit assignments"
|
||||
)
|
||||
|
||||
# Regular users cannot update grades - only check if actual values are being set
|
||||
if (assignment_task_submission_object.grade is not None and assignment_task_submission_object.grade != 0) or \
|
||||
(assignment_task_submission_object.task_submission_grade_feedback is not None and assignment_task_submission_object.task_submission_grade_feedback != ""):
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="You do not have permission to update grades"
|
||||
)
|
||||
|
||||
# Only need read permission for submissions
|
||||
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
||||
else:
|
||||
# Instructors/admins need update permission to grade
|
||||
await rbac_check(request, course.course_uuid, current_user, "update", db_session)
|
||||
|
||||
# Try to find existing submission if UUID is provided
|
||||
assignment_task_submission = None
|
||||
if assignment_task_submission_uuid:
|
||||
statement = select(AssignmentTaskSubmission).where(
|
||||
AssignmentTaskSubmission.assignment_task_submission_uuid == assignment_task_submission_uuid
|
||||
)
|
||||
assignment_task_submission = db_session.exec(statement).first()
|
||||
|
||||
# If submission exists, update it
|
||||
if assignment_task_submission:
|
||||
# For regular users, ensure they can only update their own submissions
|
||||
if not is_instructor and assignment_task_submission.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="You can only update your own submissions"
|
||||
)
|
||||
|
||||
# Update only the fields that were passed in
|
||||
for var, value in vars(assignment_task_submission_object).items():
|
||||
if value is not None:
|
||||
|
|
@ -742,9 +794,6 @@ async def handle_assignment_task_submission(
|
|||
db_session.commit()
|
||||
db_session.refresh(assignment_task_submission)
|
||||
|
||||
# return assignment task submission read
|
||||
return AssignmentTaskSubmissionRead.model_validate(assignment_task_submission)
|
||||
|
||||
else:
|
||||
# Create new Task submission
|
||||
current_time = str(datetime.now())
|
||||
|
|
@ -753,10 +802,10 @@ async def handle_assignment_task_submission(
|
|||
model_data = assignment_task_submission_object.model_dump()
|
||||
|
||||
assignment_task_submission = AssignmentTaskSubmission(
|
||||
assignment_task_submission_uuid=f"assignmenttasksubmission_{uuid4()}",
|
||||
assignment_task_submission_uuid=assignment_task_submission_uuid or f"assignmenttasksubmission_{uuid4()}",
|
||||
task_submission=model_data["task_submission"],
|
||||
grade=model_data["grade"],
|
||||
task_submission_grade_feedback=model_data["task_submission_grade_feedback"],
|
||||
grade=0, # Always start with 0 for new submissions
|
||||
task_submission_grade_feedback="", # Start with empty feedback
|
||||
assignment_task_id=int(assignment_task.id), # type: ignore
|
||||
assignment_type=assignment_task.assignment_type,
|
||||
activity_id=assignment.activity_id,
|
||||
|
|
@ -770,9 +819,10 @@ async def handle_assignment_task_submission(
|
|||
# Insert Assignment Task Submission in DB
|
||||
db_session.add(assignment_task_submission)
|
||||
db_session.commit()
|
||||
db_session.refresh(assignment_task_submission)
|
||||
|
||||
# return assignment task submission read
|
||||
return AssignmentTaskSubmissionRead.model_validate(assignment_task_submission)
|
||||
# return assignment task submission read
|
||||
return AssignmentTaskSubmissionRead.model_validate(assignment_task_submission)
|
||||
|
||||
|
||||
async def read_user_assignment_task_submissions(
|
||||
|
|
@ -1096,7 +1146,7 @@ async def create_assignment_submission(
|
|||
)
|
||||
|
||||
# RBAC check
|
||||
await rbac_check(request, course.course_uuid, current_user, "update", db_session)
|
||||
await rbac_check(request, course.course_uuid, current_user, "read", db_session)
|
||||
|
||||
# Create Assignment User Submission
|
||||
assignment_user_submission = AssignmentUserSubmission(
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import toast from 'react-hot-toast';
|
|||
|
||||
type FileSchema = {
|
||||
fileUUID: string;
|
||||
assignment_task_submission_uuid?: string;
|
||||
};
|
||||
|
||||
type TaskFileObjectProps = {
|
||||
|
|
@ -64,13 +65,13 @@ export default function TaskFileObject({ view, user_id, assignmentTaskUUID }: Ta
|
|||
// wait for 1 second to show loading animation
|
||||
await new Promise((r) => setTimeout(r, 1500))
|
||||
if (res.success === false) {
|
||||
|
||||
setError(res.data.detail)
|
||||
setIsLoading(false)
|
||||
} else {
|
||||
assignmentTaskStateHook({ type: 'reload' })
|
||||
setUserSubmissions({
|
||||
fileUUID: res.data.file_uuid,
|
||||
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||
})
|
||||
setIsLoading(false)
|
||||
setError('')
|
||||
|
|
@ -86,8 +87,14 @@ export default function TaskFileObject({ view, user_id, assignmentTaskUUID }: Ta
|
|||
if (assignmentTaskUUID) {
|
||||
const res = await getAssignmentTaskSubmissionsMe(assignmentTaskUUID, assignment.assignment_object.assignment_uuid, access_token);
|
||||
if (res.success) {
|
||||
setUserSubmissions(res.data.task_submission);
|
||||
setInitialUserSubmissions(res.data.task_submission);
|
||||
setUserSubmissions({
|
||||
...res.data.task_submission,
|
||||
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||
});
|
||||
setInitialUserSubmissions({
|
||||
...res.data.task_submission,
|
||||
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -101,6 +108,7 @@ export default function TaskFileObject({ view, user_id, assignmentTaskUUID }: Ta
|
|||
|
||||
// Save the quiz to the server
|
||||
const values = {
|
||||
assignment_task_submission_uuid: userSubmissions.assignment_task_submission_uuid,
|
||||
task_submission: userSubmissions,
|
||||
grade: 0,
|
||||
task_submission_grade_feedback: '',
|
||||
|
|
@ -156,9 +164,15 @@ export default function TaskFileObject({ view, user_id, assignmentTaskUUID }: Ta
|
|||
if (assignmentTaskUUID && user_id) {
|
||||
const res = await getAssignmentTaskSubmissionsUser(assignmentTaskUUID, user_id, assignment.assignment_object.assignment_uuid, access_token);
|
||||
if (res.success) {
|
||||
setUserSubmissions(res.data.task_submission);
|
||||
setUserSubmissions({
|
||||
...res.data.task_submission,
|
||||
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||
});
|
||||
setUserSubmissionObject(res.data);
|
||||
setInitialUserSubmissions(res.data.task_submission);
|
||||
setInitialUserSubmissions({
|
||||
...res.data.task_submission,
|
||||
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -173,6 +187,7 @@ export default function TaskFileObject({ view, user_id, assignmentTaskUUID }: Ta
|
|||
|
||||
// Save the grade to the server
|
||||
const values = {
|
||||
assignment_task_submission_uuid: userSubmissions.assignment_task_submission_uuid,
|
||||
task_submission: userSubmissions,
|
||||
grade: grade,
|
||||
task_submission_grade_feedback: 'Graded by teacher : @' + session.data.user.username,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ type QuizSubmitSchema = {
|
|||
optionUUID: string;
|
||||
answer: boolean
|
||||
}[];
|
||||
assignment_task_submission_uuid?: string;
|
||||
};
|
||||
|
||||
type TaskQuizObjectProps = {
|
||||
|
|
@ -175,8 +176,14 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
|
|||
if (assignmentTaskUUID) {
|
||||
const res = await getAssignmentTaskSubmissionsMe(assignmentTaskUUID, assignment.assignment_object.assignment_uuid, access_token);
|
||||
if (res.success) {
|
||||
setUserSubmissions(res.data.task_submission);
|
||||
setInitialUserSubmissions(res.data.task_submission);
|
||||
setUserSubmissions({
|
||||
...res.data.task_submission,
|
||||
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||
});
|
||||
setInitialUserSubmissions({
|
||||
...res.data.task_submission,
|
||||
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -242,9 +249,15 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
|
|||
if (assignmentTaskUUID && user_id) {
|
||||
const res = await getAssignmentTaskSubmissionsUser(assignmentTaskUUID, user_id, assignment.assignment_object.assignment_uuid, access_token);
|
||||
if (res.success) {
|
||||
setUserSubmissions(res.data.task_submission);
|
||||
setUserSubmissions({
|
||||
...res.data.task_submission,
|
||||
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||
});
|
||||
setUserSubmissionObject(res.data);
|
||||
setInitialUserSubmissions(res.data.task_submission);
|
||||
setInitialUserSubmissions({
|
||||
...res.data.task_submission,
|
||||
assignment_task_submission_uuid: res.data.assignment_task_submission_uuid
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -271,6 +284,7 @@ function TaskQuizObject({ view, assignmentTaskUUID, user_id }: TaskQuizObjectPro
|
|||
|
||||
// Save the grade to the server
|
||||
const values = {
|
||||
assignment_task_submission_uuid: userSubmissions.assignment_task_submission_uuid,
|
||||
task_submission: userSubmissions,
|
||||
grade: finalGrade,
|
||||
task_submission_grade_feedback: 'Auto graded by system',
|
||||
|
|
|
|||
|
|
@ -41,21 +41,27 @@ function AssignmentSubmissionsSubPage({ assignment_uuid }: { assignment_uuid: st
|
|||
<X size={18} />
|
||||
<h3>Late</h3>
|
||||
</div>
|
||||
{renderSubmissions('LATE')}
|
||||
<div className='flex flex-col gap-4'>
|
||||
{renderSubmissions('LATE')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1'>
|
||||
<div className='flex w-fit mx-auto px-3.5 py-1 bg-amber-600/80 space-x-2 my-5 items-center text-sm font-bold text-white rounded-full'>
|
||||
<SendHorizonal size={18} />
|
||||
<h3>Submitted</h3>
|
||||
</div>
|
||||
{renderSubmissions('SUBMITTED')}
|
||||
<div className='flex flex-col gap-4'>
|
||||
{renderSubmissions('SUBMITTED')}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex-1'>
|
||||
<div className='flex w-fit mx-auto px-3.5 py-1 bg-emerald-600/80 space-x-2 my-5 items-center text-sm font-bold text-white rounded-full'>
|
||||
<UserCheck size={18} />
|
||||
<h3>Graded</h3>
|
||||
</div>
|
||||
{renderSubmissions('GRADED')}
|
||||
<div className='flex flex-col gap-4'>
|
||||
{renderSubmissions('GRADED')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ function EvaluateAssignment({ user_id }: any) {
|
|||
const assignments = useAssignments() as any;
|
||||
const session = useLHSession() as any;
|
||||
const org = useOrg() as any;
|
||||
const router = useRouter();
|
||||
|
||||
async function gradeAssignment() {
|
||||
const res = await putFinalGrade(user_id, assignments?.assignment_object.assignment_uuid, session.data?.tokens?.access_token);
|
||||
|
|
@ -92,17 +91,17 @@ function EvaluateAssignment({ user_id }: any) {
|
|||
)
|
||||
})}
|
||||
<div className='flex space-x-4 font-semibold items-center justify-between'>
|
||||
<button onClick={rejectAssignment} className='flex space-x-2 px-4 py-2 text-sm bg-rose-600/80 text-white rounded-lg nice-shadow items-center'>
|
||||
<button onClick={rejectAssignment} className='flex space-x-2 px-4 py-2 text-sm bg-rose-600/80 text-white rounded-lg nice-shadow items-center cursor-pointer'>
|
||||
<X size={18} />
|
||||
<span>Reject Assignment</span>
|
||||
</button>
|
||||
<div className='flex space-x-3 items-center'>
|
||||
<button onClick={gradeAssignment} className='flex space-x-2 px-4 py-2 text-sm bg-violet-600/80 text-white rounded-lg nice-shadow items-center'>
|
||||
<button onClick={gradeAssignment} className='flex space-x-2 px-4 py-2 text-sm bg-violet-600/80 text-white rounded-lg nice-shadow items-center cursor-pointer'>
|
||||
<BookOpenCheck size={18} />
|
||||
<span>Set final grade</span>
|
||||
</button>
|
||||
<MoveRight className='text-gray-400' size={18} />
|
||||
<button onClick={markActivityAsDone} className='flex space-x-2 px-4 py-2 text-sm bg-teal-600/80 text-white rounded-lg nice-shadow items-center'>
|
||||
<button onClick={markActivityAsDone} className='flex space-x-2 px-4 py-2 text-sm bg-teal-600/80 text-white rounded-lg nice-shadow items-center cursor-pointer'>
|
||||
<Check size={18} />
|
||||
<span>Mark Activity as Done for User</span>
|
||||
</button>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue