feat: expose gradebook API and CSV export
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
|
import csv
|
||||||
import threading
|
import threading
|
||||||
from typing import List, Optional
|
from typing import List, Literal, Optional
|
||||||
|
from urllib.parse import quote
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
from django.http import HttpResponse
|
||||||
from ninja import Router, Query
|
from ninja import Router, Query
|
||||||
from ninja.errors import HttpError
|
from ninja.errors import HttpError
|
||||||
from ninja.pagination import paginate
|
from ninja.pagination import paginate
|
||||||
@@ -30,6 +33,7 @@ from .schemas import (
|
|||||||
FlagIn,
|
FlagIn,
|
||||||
FlagStats,
|
FlagStats,
|
||||||
AwardOut,
|
AwardOut,
|
||||||
|
GradebookOut,
|
||||||
PromptRoundOut,
|
PromptRoundOut,
|
||||||
ShowcaseDetailOut,
|
ShowcaseDetailOut,
|
||||||
ShowcaseItemOut,
|
ShowcaseItemOut,
|
||||||
@@ -46,6 +50,7 @@ from .schemas import (
|
|||||||
|
|
||||||
|
|
||||||
from .models import Award, ItemOrdering, Rating, Submission, SubmissionAward
|
from .models import Award, ItemOrdering, Rating, Submission, SubmissionAward
|
||||||
|
from .gradebook import GradebookFilters, build_gradebook, gradebook_csv_rows
|
||||||
from task.models import Task
|
from task.models import Task
|
||||||
from account.models import RoleChoices, User
|
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])
|
@router.get("/showcase/", response=List[AwardOut])
|
||||||
@login_required
|
@login_required
|
||||||
def list_showcase(request):
|
def list_showcase(request):
|
||||||
|
|||||||
@@ -160,6 +160,48 @@ class TaskStatsOut(Schema):
|
|||||||
top_viewed: list[TopViewedItem]
|
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):
|
class ShowcaseItemOut(Schema):
|
||||||
submission_id: UUID
|
submission_id: UUID
|
||||||
username: str
|
username: str
|
||||||
|
|||||||
Reference in New Issue
Block a user