feat: review open answers in the admin and pick a grader per quiz

- Do oceny admin screen (golem15.quizzes.reviews) over the open answers,
  with a read-only context partial, score and comment virtual fields saved
  through SetScore, confirm and regrade record actions, bulk confirm
- open question type, rubric and max_length in the question form; grader
  dropdown on the quiz form from the grader registry
- Review model with quiz, status and student filters
This commit is contained in:
Jakub Zych
2026-11-09 18:31:19 +01:00
parent 1ce1ebee16
commit 2ad4e8b1ec
20 changed files with 1244 additions and 9 deletions

View File

@@ -0,0 +1,48 @@
<div class="quizzes-review">
{{with .Data}}
<dl>
<dt>{{trans "golem15.quizzes::lang.reviews.quiz"}}</dt>
<dd>{{.QuizTitle}}</dd>
<dt>{{trans "golem15.quizzes::lang.reviews.student"}}</dt>
<dd>{{if .StudentName}}{{.StudentName}} <span class="help-block">{{.StudentEmail}}</span>{{else}}{{.StudentEmail}}{{end}}</dd>
<dt>{{trans "golem15.quizzes::lang.reviews.question"}} ({{.QuestionPoints}} {{trans "golem15.quizzes::lang.reviews.points_short"}})</dt>
<dd><p class="whitespace-pre-wrap">{{.QuestionText}}</p></dd>
{{if .ModelAnswer}}
<dt>{{trans "golem15.quizzes::lang.reviews.model_answer"}}</dt>
<dd><p class="whitespace-pre-wrap">{{.ModelAnswer}}</p></dd>
{{end}}
{{if .Rubric}}
<dt>{{trans "golem15.quizzes::lang.reviews.rubric"}}</dt>
<dd><p class="whitespace-pre-wrap">{{.Rubric}}</p></dd>
{{end}}
<dt>{{trans "golem15.quizzes::lang.reviews.text"}} <span class="help-block">{{trans "golem15.quizzes::lang.reviews.max_length"}}: {{.MaxLength}}</span></dt>
<dd><blockquote class="whitespace-pre-wrap">{{.Text}}</blockquote></dd>
<dt>{{trans "golem15.quizzes::lang.reviews.status"}}</dt>
<dd><strong>{{trans .StatusLabel}}</strong></dd>
{{if .Grader}}
<dt>{{trans "golem15.quizzes::lang.reviews.grader"}}</dt>
<dd>{{trans .GraderLabel}} <span class="help-block">{{trans "golem15.quizzes::lang.reviews.grading_attempts"}}: {{.GradingAttempts}}{{if .GradedAt}} · {{trans "golem15.quizzes::lang.reviews.graded_at"}}: {{.GradedAt}}{{end}}</span></dd>
{{end}}
{{if .HasGraderPoints}}
<dt>{{trans "golem15.quizzes::lang.reviews.grader_points"}}</dt>
<dd>{{.GraderPoints}}</dd>
{{end}}
{{if .GraderFeedback}}
<dt>{{trans "golem15.quizzes::lang.reviews.grader_feedback"}}</dt>
<dd><p class="whitespace-pre-wrap">{{.GraderFeedback}}</p></dd>
{{end}}
{{if .GradingError}}
<dt>{{trans "golem15.quizzes::lang.reviews.grading_error"}}</dt>
<dd><p class="whitespace-pre-wrap">{{.GradingError}}</p></dd>
{{end}}
{{if .Reviewed}}
<dt>{{trans "golem15.quizzes::lang.reviews.points"}}</dt>
<dd><strong>{{.Points}}</strong> / {{.QuestionPoints}}{{if .ReviewedAt}} <span class="help-block">{{trans "golem15.quizzes::lang.reviews.reviewed_at"}}: {{.ReviewedAt}}</span>{{end}}</dd>
{{end}}
{{if .ReviewComment}}
<dt>{{trans "golem15.quizzes::lang.reviews.review_comment"}}</dt>
<dd><p class="whitespace-pre-wrap">{{.ReviewComment}}</p></dd>
{{end}}
</dl>
{{end}}
</div>

View File

@@ -0,0 +1,16 @@
scopes:
quiz:
label: golem15.quizzes::lang.reviews.quiz
modelClass: Quiz
nameFrom: title
scope: filterByQuiz
status:
label: golem15.quizzes::lang.reviews.status
modelClass: Review
nameFrom: grading_status
scope: filterByStatus
student:
label: golem15.quizzes::lang.reviews.student
modelClass: Review
nameFrom: grading_status
scope: filterByStudent

View File

@@ -0,0 +1,15 @@
name: golem15.quizzes::lang.reviews.answer
form: ~/plugins/golem15/quizzes/models/reviews/fields.yaml
modelClass: Review
defaultRedirect: golem15/quizzes/reviews
# Open answers come from students: there is no create. Saving the form
# stores the owner's score and comment, then returns to the preview.
update:
redirect: golem15/quizzes/reviews/preview/:id
redirectClose: golem15/quizzes/reviews
# The preview screen shows the answer and offers the record actions.
preview: {}
recordActions: [confirm, regrade]

View File

@@ -0,0 +1,16 @@
list: ~/plugins/golem15/quizzes/models/reviews/columns.yaml
modelClass: Review
title: golem15.quizzes::lang.reviews.title
recordUrl: golem15/quizzes/reviews/preview/:id
recordsPerPage: 50
perPageOptions: [20, 50, 100]
showCheckboxes: true
defaultSort:
column: updated_at
direction: asc
filter: config_filter.yaml
bulkActions: [confirm]
toolbar:
buttons: []
search:
prompt: backend::lang.list.search_prompt