feat: expose gradebook API and CSV export

This commit is contained in:
2026-05-02 07:56:19 -06:00
parent b10e030298
commit 57df250b55
2 changed files with 96 additions and 1 deletions

View File

@@ -1,6 +1,9 @@
import csv
import threading
from typing import List, Optional
from typing import List, Literal, Optional
from urllib.parse import quote
from uuid import UUID
from django.http import HttpResponse
from ninja import Router, Query
from ninja.errors import HttpError
from ninja.pagination import paginate
@@ -30,6 +33,7 @@ from .schemas import (
FlagIn,
FlagStats,
AwardOut,
GradebookOut,
PromptRoundOut,
ShowcaseDetailOut,
ShowcaseItemOut,
@@ -46,6 +50,7 @@ from .schemas import (
from .models import Award, ItemOrdering, Rating, Submission, SubmissionAward
from .gradebook import GradebookFilters, build_gradebook, gradebook_csv_rows
from task.models import Task
from account.models import RoleChoices, User
@@ -471,6 +476,54 @@ def get_task_stats(request, task_id: int, classname: Optional[str] = None):
)
@router.get("/gradebook/", response=GradebookOut)
@admin_required
def get_gradebook(
request,
classname: str = "",
task_type: Optional[Literal["tutorial", "challenge"]] = None,
username: Optional[str] = None,
include_all_tasks: bool = False,
):
return build_gradebook(
GradebookFilters(
classname=classname,
task_type=task_type,
username=username,
include_all_tasks=include_all_tasks,
)
)
@router.get("/gradebook/export/")
@admin_required
def export_gradebook(
request,
classname: str = "",
task_type: Optional[Literal["tutorial", "challenge"]] = None,
username: Optional[str] = None,
include_all_tasks: bool = False,
):
gradebook = build_gradebook(
GradebookFilters(
classname=classname,
task_type=task_type,
username=username,
include_all_tasks=include_all_tasks,
)
)
response = HttpResponse(content_type="text/csv; charset=utf-8")
filename = f"gradebook-{gradebook['classname']}.csv"
response["Content-Disposition"] = (
f"attachment; filename*=UTF-8''{quote(filename)}"
)
response.write("\ufeff")
writer = csv.writer(response)
for row in gradebook_csv_rows(gradebook):
writer.writerow(row)
return response
@router.get("/showcase/", response=List[AwardOut])
@login_required
def list_showcase(request):

View File

@@ -160,6 +160,48 @@ class TaskStatsOut(Schema):
top_viewed: list[TopViewedItem]
class GradebookTask(Schema):
id: int
display: int
title: str
task_type: Literal["tutorial", "challenge"]
submitted_count: int
coverage: float
included: bool
class GradebookCell(Schema):
score: float
submitted: bool
submission_id: Optional[UUID] = None
class GradebookRow(Schema):
user_id: int
username: str
classname: str
rank: int
grade: Literal["A", "B", "C", "D", "E"]
scores: dict[int, GradebookCell]
tutorial_total: float
challenge_total: float
total_score: float
average_score: Optional[float]
submitted_task_count: int
missing_task_count: int
class GradebookOut(Schema):
classname: str
classes: list[str]
task_count: int
included_task_count: int
student_count: int
coverage_threshold_count: int
tasks: list[GradebookTask]
rows: list[GradebookRow]
class ShowcaseItemOut(Schema):
submission_id: UUID
username: str