VBV-731: Bewertungen bearbeiten
This commit is contained in:
parent
cd001a1269
commit
a2b3d63d6d
|
|
@ -2,13 +2,15 @@
|
|||
import EvaluationIntro from "@/components/assignment/evaluation/EvaluationIntro.vue";
|
||||
import EvaluationSummary from "@/components/assignment/evaluation/EvaluationSummary.vue";
|
||||
import EvaluationTask from "@/components/assignment/evaluation/EvaluationTask.vue";
|
||||
import { useCourseSessionDetailQuery } from "@/composables";
|
||||
import { useCourseSessionDetailQuery, useCurrentCourseSession } from "@/composables";
|
||||
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
|
||||
import type {
|
||||
Assignment,
|
||||
AssignmentCompletion,
|
||||
AssignmentEvaluationTask,
|
||||
CourseSessionUser,
|
||||
} from "@/types";
|
||||
import { useMutation } from "@urql/vue";
|
||||
import { useRouteQuery } from "@vueuse/router";
|
||||
import dayjs from "dayjs";
|
||||
import { findIndex } from "lodash";
|
||||
|
|
@ -21,10 +23,12 @@ const props = defineProps<{
|
|||
assignment: Assignment;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(["close"]);
|
||||
const emit = defineEmits(["close", "reopen"]);
|
||||
|
||||
log.debug("UserEvaluation setup");
|
||||
|
||||
const courseSession = useCurrentCourseSession();
|
||||
|
||||
// 0 = introduction, 1 - n = tasks, n+1 = submission
|
||||
const stepIndex = useRouteQuery("step", "0", { transform: Number, mode: "push" });
|
||||
|
||||
|
|
@ -58,6 +62,34 @@ function editTask(task: AssignmentEvaluationTask) {
|
|||
stepIndex.value = taskIndex + 1;
|
||||
}
|
||||
|
||||
function canReopen() {
|
||||
return (
|
||||
evaluationSubmitted.value &&
|
||||
(props.assignment.assignment_type === "CASEWORK" ||
|
||||
props.assignment.assignment_type === "PRAXIS_ASSIGNMENT")
|
||||
);
|
||||
}
|
||||
|
||||
const upsertAssignmentCompletionMutation = useMutation(
|
||||
UPSERT_ASSIGNMENT_COMPLETION_MUTATION
|
||||
);
|
||||
|
||||
async function reopen() {
|
||||
log.debug("reopen");
|
||||
|
||||
await upsertAssignmentCompletionMutation.executeMutation({
|
||||
assignmentId: props.assignment.id,
|
||||
courseSessionId: courseSession.value.id,
|
||||
assignmentUserId: props.assignmentUser.id,
|
||||
completionStatus: "EVALUATION_IN_PROGRESS",
|
||||
completionDataString: JSON.stringify({}),
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
id: props.assignmentCompletion?.id,
|
||||
});
|
||||
stepIndex.value = 1;
|
||||
}
|
||||
|
||||
const courseSessionDetailResult = useCourseSessionDetailQuery();
|
||||
|
||||
const assignmentDetail = computed(() => {
|
||||
|
|
@ -89,11 +121,11 @@ const taskExpertDataText = computed(() => {
|
|||
const text = computed(() => {
|
||||
if (props.assignment.assignment_type === "CASEWORK") {
|
||||
return {
|
||||
evaluationFinish: "a.Bewertung abschliessen",
|
||||
evaluationFinish: "a.Schliessen",
|
||||
};
|
||||
} else if (props.assignment.assignment_type === "PRAXIS_ASSIGNMENT") {
|
||||
return {
|
||||
evaluationFinish: "a.Feedback abschliessen",
|
||||
evaluationFinish: "a.Schliessen",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
|
|
@ -109,6 +141,16 @@ function nextButtonEnabled() {
|
|||
return true;
|
||||
}
|
||||
|
||||
function previousButtonVisible() {
|
||||
if (
|
||||
stepIndex.value > numTasks.value &&
|
||||
props.assignmentCompletion.completion_status === "EVALUATION_SUBMITTED"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function finishButtonEnabled() {
|
||||
return props.assignmentCompletion.completion_status === "EVALUATION_SUBMITTED";
|
||||
}
|
||||
|
|
@ -150,18 +192,19 @@ function finishButtonEnabled() {
|
|||
<nav v-if="stepIndex > 0" class="sticky bottom-0 border-t bg-gray-200 p-6">
|
||||
<div class="relative flex flex-row place-content-end">
|
||||
<button
|
||||
v-if="true"
|
||||
class="btn-secondary mr-2 flex items-center"
|
||||
v-if="previousButtonVisible()"
|
||||
class="btn-secondary flex items-center"
|
||||
data-cy="previous-step"
|
||||
@click="previousPage()"
|
||||
>
|
||||
<it-icon-arrow-left class="mr-2 h-6 w-6"></it-icon-arrow-left>
|
||||
{{ $t("general.backCapitalized") }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="stepIndex <= numTasks"
|
||||
:disabled="!nextButtonEnabled()"
|
||||
class="btn-secondary z-10 flex items-center"
|
||||
class="btn-secondary z-10 ml-2 flex items-center"
|
||||
data-cy="next-step"
|
||||
@click="nextPage()"
|
||||
>
|
||||
|
|
@ -170,15 +213,25 @@ function finishButtonEnabled() {
|
|||
</button>
|
||||
|
||||
<button
|
||||
v-if="stepIndex > numTasks"
|
||||
v-if="stepIndex > numTasks && canReopen()"
|
||||
class="btn-secondary z-10 ml-2"
|
||||
data-cy="btn-reopen"
|
||||
@click="reopen()"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
{{ $t("a.Bewertung bearbeiten") }}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="stepIndex > numTasks && finishButtonEnabled()"
|
||||
:disabled="!finishButtonEnabled()"
|
||||
class="btn-secondary z-10"
|
||||
data-cy="next-step"
|
||||
class="btn-primary z-10 ml-2"
|
||||
data-cy="btn-close"
|
||||
@click="emit('close')"
|
||||
>
|
||||
<span class="flex items-center">
|
||||
{{ $t(text.evaluationFinish) }}
|
||||
<it-icon-check class="ml-2 h-6 w-6"></it-icon-check>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -76,30 +76,8 @@ async function startEvaluation() {
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
{{
|
||||
$t("assignment.x hat die Ergebnisse am y um z Uhr abgegeben", {
|
||||
x: props.assignmentUser.first_name + " " + props.assignmentUser.last_name,
|
||||
y: dayjs(props.assignmentCompletion.submitted_at).format("DD.MM.YYYY"),
|
||||
z: dayjs(props.assignmentCompletion.submitted_at).format("HH.mm"),
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
|
||||
<h3 data-cy="title">{{ $t(text.evaluationTitle) }}</h3>
|
||||
|
||||
<p v-if="props.dueDate" class="my-4" data-cy="evaluation-duedate">
|
||||
{{
|
||||
$t(
|
||||
"assignment.Du musst die Bewertung bis am x um y Uhr abschliessen und freigeben",
|
||||
{
|
||||
x: props.dueDate.format("DD.MM.YYYY"),
|
||||
y: props.dueDate.format("HH.mm"),
|
||||
}
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
|
||||
<p class="my-4" data-cy="instruction">
|
||||
{{
|
||||
$t(text.evaluationInstruction, {
|
||||
|
|
@ -125,6 +103,11 @@ async function startEvaluation() {
|
|||
</a>
|
||||
</p>
|
||||
|
||||
<p v-if="props.dueDate" class="my-4" data-cy="evaluation-duedate">
|
||||
{{ "a.Freigabetermin Bewertung" }}: {{ props.dueDate.format("DD.MM.YYYY") }}
|
||||
{{ props.dueDate.format("HH.mm") }}
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<button
|
||||
class="btn-primary text-large"
|
||||
|
|
@ -148,6 +131,18 @@ async function startEvaluation() {
|
|||
<span v-else>{{ $t(text.evaluationStart) }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section class="mt-8">
|
||||
<div
|
||||
v-for="historyEntry in props.assignmentCompletion.additional_json_data
|
||||
?.submission_history ?? []"
|
||||
:key="historyEntry.timestamp"
|
||||
>
|
||||
{{ dayjs(historyEntry.timestamp).format("DD.MM.YYYY HH.mm") }}:
|
||||
{{ $t(historyEntry.translation_key) }}
|
||||
({{ historyEntry.user_display_name }})
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
|
||||
import ItTextarea from "@/components/ui/ItTextarea.vue";
|
||||
import RichText from "@/components/ui/RichText.vue";
|
||||
import { useCurrentCourseSession } from "@/composables";
|
||||
import { UPSERT_ASSIGNMENT_COMPLETION_MUTATION } from "@/graphql/mutations";
|
||||
|
|
@ -16,7 +17,7 @@ import type {
|
|||
import { useMutation } from "@urql/vue";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import * as log from "loglevel";
|
||||
import { computed, reactive } from "vue";
|
||||
import { computed, reactive, ref } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
assignmentUser: CourseSessionUser;
|
||||
|
|
@ -40,6 +41,10 @@ const upsertAssignmentCompletionMutation = useMutation(
|
|||
UPSERT_ASSIGNMENT_COMPLETION_MUTATION
|
||||
);
|
||||
|
||||
const evaluationComment = ref(
|
||||
props.assignmentCompletion.completion_data.expert_evaluation_comment?.text ?? ""
|
||||
);
|
||||
|
||||
const text = computed(() => {
|
||||
if (props.assignment.assignment_type === "CASEWORK") {
|
||||
return {
|
||||
|
|
@ -77,7 +82,11 @@ async function submitEvaluation() {
|
|||
courseSessionId: courseSession.value.id,
|
||||
assignmentUserId: props.assignmentUser.id,
|
||||
completionStatus: "EVALUATION_SUBMITTED",
|
||||
completionDataString: JSON.stringify({}),
|
||||
completionDataString: JSON.stringify({
|
||||
expert_evaluation_comment: {
|
||||
text: evaluationComment.value,
|
||||
},
|
||||
}),
|
||||
evaluationPoints: userPoints.value,
|
||||
// next line used for urql
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
|
|
@ -112,9 +121,48 @@ const maxPoints = computed(() => {
|
|||
}
|
||||
return maxAssignmentPoints(props.assignment);
|
||||
});
|
||||
|
||||
const userPoints = computed(() =>
|
||||
userAssignmentPoints(props.assignment, props.assignmentCompletion)
|
||||
);
|
||||
|
||||
const userPointsWithDeduction = computed(() => {
|
||||
const points = userPoints.value;
|
||||
if (points && props.assignmentCompletion.evaluation_points_deducted > 0) {
|
||||
return points - props.assignmentCompletion.evaluation_points_deducted;
|
||||
}
|
||||
return points;
|
||||
});
|
||||
|
||||
const percentage = computed(() => {
|
||||
if (props.assignmentCompletion.completion_status === "EVALUATION_SUBMITTED") {
|
||||
return (
|
||||
((props.assignmentCompletion?.evaluation_points_final ??
|
||||
userPointsWithDeduction.value ??
|
||||
0) /
|
||||
(props.assignmentCompletion?.evaluation_max_points ?? 1)) *
|
||||
100
|
||||
);
|
||||
} else {
|
||||
return ((userPointsWithDeduction.value ?? 0) / (maxPoints.value ?? 1)) * 100;
|
||||
}
|
||||
});
|
||||
|
||||
const showNotPassed = computed(() => {
|
||||
if (props.assignment.assignment_type === "CASEWORK") {
|
||||
return percentage.value < 55;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const showPassed = computed(() => {
|
||||
if (props.assignment.assignment_type === "CASEWORK") {
|
||||
return percentage.value >= 55;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -136,26 +184,23 @@ const userPoints = computed(() =>
|
|||
<section v-if="props.assignment.assignment_type === 'CASEWORK'">
|
||||
<div class="flex items-center">
|
||||
<div class="heading-1 py-4" data-cy="user-points">
|
||||
<template
|
||||
v-if="
|
||||
props.assignmentCompletion.completion_status == 'EVALUATION_SUBMITTED'
|
||||
"
|
||||
>
|
||||
{{ props.assignmentCompletion.evaluation_points_final }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ userPoints }}
|
||||
</template>
|
||||
{{ userPointsWithDeduction }}
|
||||
</div>
|
||||
<div class="pl-2" data-cy="total-points">
|
||||
{{ $t("assignment.von x Punkten", { x: maxPoints }) }}
|
||||
({{
|
||||
(
|
||||
((props.assignmentCompletion?.evaluation_points_final ?? 0) /
|
||||
(props.assignmentCompletion?.evaluation_max_points ?? 1)) *
|
||||
100
|
||||
).toFixed(0)
|
||||
}}%)
|
||||
({{ percentage.toFixed(0) }}%)
|
||||
</div>
|
||||
|
||||
<div v-if="showNotPassed" class="ml-2">
|
||||
<span class="my-2 rounded-md bg-error-red-200 px-2.5 py-0.5">
|
||||
{{ $t("a.Nicht Bestanden") }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="showPassed" class="ml-2">
|
||||
<span class="my-2 rounded-md bg-green-200 px-2.5 py-0.5">
|
||||
{{ $t("a.Bestanden") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -206,6 +251,11 @@ const userPoints = computed(() =>
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<p v-if="props.dueDate" class="my-4" data-cy="evaluation-duedate">
|
||||
{{ "a.Freigabetermin Bewertung" }}: {{ props.dueDate.format("DD.MM.YYYY") }}
|
||||
{{ props.dueDate.format("HH.mm") }}
|
||||
</p>
|
||||
|
||||
<p
|
||||
v-if="
|
||||
props.assignment.assignment_type === 'PRAXIS_ASSIGNMENT' &&
|
||||
|
|
@ -214,18 +264,23 @@ const userPoints = computed(() =>
|
|||
>
|
||||
{{ $t("a.assignment.evaluationFeedbackDescriptionText") }}
|
||||
</p>
|
||||
|
||||
<section>
|
||||
<div
|
||||
v-for="historyEntry in props.assignmentCompletion.additional_json_data
|
||||
?.submission_history ?? []"
|
||||
:key="historyEntry.timestamp"
|
||||
>
|
||||
{{ dayjs(historyEntry.timestamp).format("DD.MM.YYYY HH.mm") }}:
|
||||
{{ $t(historyEntry.translation_key) }}
|
||||
({{ historyEntry.user_display_name }})
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div
|
||||
v-if="props.assignmentCompletion.completion_status === 'EVALUATION_SUBMITTED'"
|
||||
v-if="props.assignmentCompletion.completion_status !== 'EVALUATION_SUBMITTED'"
|
||||
class="mt-8"
|
||||
>
|
||||
{{ $t("assignment.dueDateEvaluation") }}:
|
||||
{{
|
||||
dayjs(props.assignmentCompletion.evaluation_submitted_at).format("DD.MM.YYYY")
|
||||
}}
|
||||
um
|
||||
{{ dayjs(props.assignmentCompletion.evaluation_submitted_at).format("HH.mm") }}
|
||||
Uhr
|
||||
</div>
|
||||
<div v-else>
|
||||
<button
|
||||
class="btn-primary text-large"
|
||||
data-cy="submit-evaluation"
|
||||
|
|
@ -233,6 +288,16 @@ const userPoints = computed(() =>
|
|||
>
|
||||
{{ $t(text.evaluationSubmit) }}
|
||||
</button>
|
||||
|
||||
<ItTextarea
|
||||
v-model="evaluationComment"
|
||||
class="mt-8"
|
||||
:placeholder="`${$t('a.Kommentar erfassen')}...`"
|
||||
data-cy="reason-text"
|
||||
></ItTextarea>
|
||||
</div>
|
||||
<div v-else-if="evaluationComment" class="mt-4">
|
||||
{{ evaluationComment }}
|
||||
</div>
|
||||
|
||||
<div v-if="state.showSuccessInfo" class="mt-4">
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-
|
|||
*/
|
||||
const documents = {
|
||||
"\n mutation AttendanceCheckMutation(\n $attendanceCourseId: ID!\n $attendanceUserList: [AttendanceUserInputType]!\n ) {\n update_course_session_attendance_course_users(\n id: $attendanceCourseId\n attendance_user_list: $attendanceUserList\n ) {\n course_session_attendance_course {\n id\n attendance_user_list {\n user_id\n first_name\n last_name\n email\n status\n }\n }\n }\n }\n": types.AttendanceCheckMutationDocument,
|
||||
"\n mutation UpsertAssignmentCompletion(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n $completionStatus: AssignmentCompletionStatus!\n $completionDataString: String!\n $evaluationPoints: Float\n $initializeCompletion: Boolean\n $evaluationUserId: ID\n ) {\n upsert_assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n assignment_user_id: $assignmentUserId\n completion_status: $completionStatus\n completion_data_string: $completionDataString\n evaluation_points: $evaluationPoints\n initialize_completion: $initializeCompletion\n evaluation_user_id: $evaluationUserId\n ) {\n assignment_completion {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_points\n completion_data\n task_completion_data\n }\n }\n }\n": types.UpsertAssignmentCompletionDocument,
|
||||
"\n mutation UpsertAssignmentCompletion(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n $completionStatus: AssignmentCompletionStatus!\n $completionDataString: String!\n $evaluationPoints: Float\n $initializeCompletion: Boolean\n $evaluationUserId: ID\n ) {\n upsert_assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n assignment_user_id: $assignmentUserId\n completion_status: $completionStatus\n completion_data_string: $completionDataString\n evaluation_points: $evaluationPoints\n initialize_completion: $initializeCompletion\n evaluation_user_id: $evaluationUserId\n ) {\n assignment_completion {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_points_deducted\n evaluation_points_deducted_reason\n evaluation_points_final\n completion_data\n task_completion_data\n additional_json_data\n }\n }\n }\n": types.UpsertAssignmentCompletionDocument,
|
||||
"\n mutation UpdateCourseSessionProfile($input: CourseSessionProfileMutationInput!) {\n update_course_session_profile(input: $input) {\n clientMutationId\n result {\n __typename\n ... on UpdateCourseProfileSuccess {\n user {\n id\n chosen_profile\n }\n }\n ... on UpdateCourseProfileError {\n message\n }\n }\n }\n }\n": types.UpdateCourseSessionProfileDocument,
|
||||
"\n fragment CoursePageFields on CoursePageInterface {\n title\n id\n slug\n content_type\n frontend_url\n }\n": types.CoursePageFieldsFragmentDoc,
|
||||
"\n query attendanceCheckQuery($courseSessionId: ID!) {\n course_session_attendance_course(id: $courseSessionId) {\n id\n attendance_user_list {\n user_id\n status\n }\n }\n }\n": types.AttendanceCheckQueryDocument,
|
||||
"\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_points_deducted\n evaluation_points_deducted_reason\n evaluation_points_final\n\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n": types.AssignmentCompletionQueryDocument,
|
||||
"\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_points_deducted\n evaluation_points_deducted_reason\n evaluation_points_final\n\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n\n additional_json_data\n }\n }\n": types.AssignmentCompletionQueryDocument,
|
||||
"\n query competenceCertificateQuery(\n $courseSlug: String!\n $courseSessionId: ID!\n $userIds: [UUID!]\n ) {\n competence_certificate_list(course_slug: $courseSlug, user_ids: $userIds) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n competence_certificate_weight\n completions(course_session_id: $courseSessionId) {\n id\n assignment_user {\n id\n }\n completion_status\n submitted_at\n evaluation_points\n evaluation_points_deducted\n evaluation_points_final\n evaluation_max_points\n evaluation_passed\n evaluation_percent\n course_session {\n id\n title\n }\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateQueryDocument,
|
||||
"\n query competenceCertificateForUserQuery(\n $courseSlug: String!\n $courseSessionId: ID!\n $userIds: [UUID!]!\n ) {\n competence_certificate_list(course_slug: $courseSlug, user_ids: $userIds) {\n ...CoursePageFields\n competence_certificates {\n ...CoursePageFields\n assignments {\n ...CoursePageFields\n assignment_type\n max_points\n competence_certificate_weight\n completions(course_session_id: $courseSessionId) {\n id\n completion_status\n submitted_at\n evaluation_points\n evaluation_points_final\n evaluation_points_deducted\n evaluation_max_points\n evaluation_passed\n evaluation_percent\n assignment_user {\n id\n }\n course_session {\n id\n title\n }\n }\n learning_content {\n ...CoursePageFields\n circle {\n id\n title\n slug\n }\n }\n }\n }\n }\n }\n": types.CompetenceCertificateForUserQueryDocument,
|
||||
"\n query courseSessionDetail($courseSessionId: ID!) {\n course_session(id: $courseSessionId) {\n id\n title\n course {\n id\n title\n slug\n configuration {\n id\n enable_circle_documents\n enable_learning_mentor\n enable_competence_certificates\n }\n }\n users {\n id\n user_id\n first_name\n last_name\n email\n avatar_url\n role\n circles {\n id\n title\n slug\n }\n optional_attendance\n }\n attendance_courses {\n id\n location\n trainer\n due_date {\n id\n start\n end\n }\n learning_content_id\n learning_content {\n id\n title\n circle {\n id\n title\n slug\n }\n }\n }\n assignments {\n id\n submission_deadline {\n id\n start\n }\n evaluation_deadline {\n id\n start\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n edoniq_tests {\n id\n deadline {\n id\n start\n end\n }\n learning_content {\n id\n title\n content_assignment {\n id\n title\n assignment_type\n }\n }\n }\n }\n }\n": types.CourseSessionDetailDocument,
|
||||
|
|
@ -53,7 +53,7 @@ export function graphql(source: "\n mutation AttendanceCheckMutation(\n $att
|
|||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n mutation UpsertAssignmentCompletion(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n $completionStatus: AssignmentCompletionStatus!\n $completionDataString: String!\n $evaluationPoints: Float\n $initializeCompletion: Boolean\n $evaluationUserId: ID\n ) {\n upsert_assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n assignment_user_id: $assignmentUserId\n completion_status: $completionStatus\n completion_data_string: $completionDataString\n evaluation_points: $evaluationPoints\n initialize_completion: $initializeCompletion\n evaluation_user_id: $evaluationUserId\n ) {\n assignment_completion {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_points\n completion_data\n task_completion_data\n }\n }\n }\n"): (typeof documents)["\n mutation UpsertAssignmentCompletion(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n $completionStatus: AssignmentCompletionStatus!\n $completionDataString: String!\n $evaluationPoints: Float\n $initializeCompletion: Boolean\n $evaluationUserId: ID\n ) {\n upsert_assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n assignment_user_id: $assignmentUserId\n completion_status: $completionStatus\n completion_data_string: $completionDataString\n evaluation_points: $evaluationPoints\n initialize_completion: $initializeCompletion\n evaluation_user_id: $evaluationUserId\n ) {\n assignment_completion {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_points\n completion_data\n task_completion_data\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n mutation UpsertAssignmentCompletion(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n $completionStatus: AssignmentCompletionStatus!\n $completionDataString: String!\n $evaluationPoints: Float\n $initializeCompletion: Boolean\n $evaluationUserId: ID\n ) {\n upsert_assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n assignment_user_id: $assignmentUserId\n completion_status: $completionStatus\n completion_data_string: $completionDataString\n evaluation_points: $evaluationPoints\n initialize_completion: $initializeCompletion\n evaluation_user_id: $evaluationUserId\n ) {\n assignment_completion {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_points_deducted\n evaluation_points_deducted_reason\n evaluation_points_final\n completion_data\n task_completion_data\n additional_json_data\n }\n }\n }\n"): (typeof documents)["\n mutation UpsertAssignmentCompletion(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n $completionStatus: AssignmentCompletionStatus!\n $completionDataString: String!\n $evaluationPoints: Float\n $initializeCompletion: Boolean\n $evaluationUserId: ID\n ) {\n upsert_assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n learning_content_page_id: $learningContentId\n assignment_user_id: $assignmentUserId\n completion_status: $completionStatus\n completion_data_string: $completionDataString\n evaluation_points: $evaluationPoints\n initialize_completion: $initializeCompletion\n evaluation_user_id: $evaluationUserId\n ) {\n assignment_completion {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_points\n evaluation_max_points\n evaluation_points_deducted\n evaluation_points_deducted_reason\n evaluation_points_final\n completion_data\n task_completion_data\n additional_json_data\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
|
@ -69,7 +69,7 @@ export function graphql(source: "\n query attendanceCheckQuery($courseSessionId
|
|||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_points_deducted\n evaluation_points_deducted_reason\n evaluation_points_final\n\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n"): (typeof documents)["\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_points_deducted\n evaluation_points_deducted_reason\n evaluation_points_final\n\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n }\n }\n"];
|
||||
export function graphql(source: "\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_points_deducted\n evaluation_points_deducted_reason\n evaluation_points_final\n\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n\n additional_json_data\n }\n }\n"): (typeof documents)["\n query assignmentCompletionQuery(\n $assignmentId: ID!\n $courseSessionId: ID!\n $learningContentId: ID\n $assignmentUserId: UUID\n ) {\n assignment(id: $assignmentId) {\n assignment_type\n needs_expert_evaluation\n max_points\n content_type\n effort_required\n evaluation_description\n evaluation_document_url\n evaluation_tasks\n id\n intro_text\n performance_objectives\n slug\n tasks\n title\n translation_key\n solution_sample {\n id\n url\n }\n competence_certificate {\n ...CoursePageFields\n }\n }\n assignment_completion(\n assignment_id: $assignmentId\n course_session_id: $courseSessionId\n assignment_user_id: $assignmentUserId\n learning_content_page_id: $learningContentId\n ) {\n id\n completion_status\n submitted_at\n evaluation_submitted_at\n evaluation_user {\n id\n first_name\n last_name\n }\n assignment_user {\n avatar_url\n first_name\n last_name\n id\n }\n evaluation_points\n evaluation_max_points\n evaluation_points_deducted\n evaluation_points_deducted_reason\n evaluation_points_final\n\n evaluation_passed\n edoniq_extended_time_flag\n completion_data\n task_completion_data\n\n additional_json_data\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -594,7 +594,7 @@ type AssignmentCompletionObjectType {
|
|||
course_session: CourseSessionObjectType!
|
||||
completion_status: AssignmentAssignmentCompletionCompletionStatusChoices!
|
||||
completion_data: GenericScalar
|
||||
additional_json_data: JSONString!
|
||||
additional_json_data: GenericScalar
|
||||
task_completion_data: GenericScalar
|
||||
learning_content_page_id: ID
|
||||
evaluation_points: Float
|
||||
|
|
@ -650,14 +650,6 @@ String, Boolean, Int, Float, List or Object.
|
|||
"""
|
||||
scalar GenericScalar
|
||||
|
||||
"""
|
||||
Allows use of a JSON String for input / output from the GraphQL schema.
|
||||
|
||||
Use of this type is *not recommended* as you lose the benefits of having a defined, static
|
||||
schema (one of the key benefits of GraphQL).
|
||||
"""
|
||||
scalar JSONString
|
||||
|
||||
type ContentDocumentObjectType {
|
||||
id: ID!
|
||||
display_text: String!
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ export const GenericScalar = "GenericScalar";
|
|||
export const ID = "ID";
|
||||
export const Int = "Int";
|
||||
export const JSONStreamField = "JSONStreamField";
|
||||
export const JSONString = "JSONString";
|
||||
export const LearningContentAssignmentObjectType = "LearningContentAssignmentObjectType";
|
||||
export const LearningContentAttendanceCourseObjectType = "LearningContentAttendanceCourseObjectType";
|
||||
export const LearningContentDocumentListObjectType = "LearningContentDocumentListObjectType";
|
||||
|
|
|
|||
|
|
@ -52,8 +52,13 @@ export const UPSERT_ASSIGNMENT_COMPLETION_MUTATION = graphql(`
|
|||
submitted_at
|
||||
evaluation_submitted_at
|
||||
evaluation_points
|
||||
evaluation_max_points
|
||||
evaluation_points_deducted
|
||||
evaluation_points_deducted_reason
|
||||
evaluation_points_final
|
||||
completion_data
|
||||
task_completion_data
|
||||
additional_json_data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,8 @@ export const ASSIGNMENT_COMPLETION_QUERY = graphql(`
|
|||
edoniq_extended_time_flag
|
||||
completion_data
|
||||
task_completion_data
|
||||
|
||||
additional_json_data
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ describe("praxisauftrag.cy.js", () => {
|
|||
cy.get('[data-cy="next-step"]').click();
|
||||
|
||||
cy.get('[data-cy="submit-evaluation"]').click();
|
||||
cy.get('[data-cy="next-step"]').click();
|
||||
cy.get('[data-cy="btn-close"]').click();
|
||||
|
||||
cy.visit("/");
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ DATABASES = {
|
|||
default="postgres://postgres@localhost:5432/vbv_lernwelt",
|
||||
)
|
||||
}
|
||||
|
||||
DATABASES["default"]["ATOMIC_REQUESTS"] = env.bool(
|
||||
"DATABASE_ATOMIC_REQUESTS", default=True
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
from django.contrib import admin
|
||||
from django.db.models import JSONField
|
||||
|
||||
from vbv_lernwelt.assignment.models import AssignmentCompletion
|
||||
from vbv_lernwelt.assignment.models import (
|
||||
AssignmentCompletion,
|
||||
AssignmentCompletionAuditLog,
|
||||
)
|
||||
from vbv_lernwelt.core.admin import LogAdmin
|
||||
from vbv_lernwelt.core.admin_utils import PrettyJSONWidget
|
||||
|
||||
|
||||
|
|
@ -16,6 +20,7 @@ class AssignmentCompletionAdmin(admin.ModelAdmin):
|
|||
"assignment",
|
||||
"get_circle",
|
||||
"assignment_user",
|
||||
"evaluation_user",
|
||||
"course_session",
|
||||
"completion_status",
|
||||
"evaluation_points",
|
||||
|
|
@ -27,7 +32,14 @@ class AssignmentCompletionAdmin(admin.ModelAdmin):
|
|||
"course_session__course",
|
||||
"course_session",
|
||||
]
|
||||
search_fields = ["assignment_user__email"]
|
||||
search_fields = [
|
||||
"assignment_user__email",
|
||||
"assignment_user__first_name",
|
||||
"assignment_user__last_name",
|
||||
"evaluation_user__email",
|
||||
"evaluation_user__first_name",
|
||||
"evaluation_user__last_name",
|
||||
]
|
||||
readonly_fields = [
|
||||
"assignment_user",
|
||||
"assignment",
|
||||
|
|
@ -53,3 +65,40 @@ class AssignmentCompletionAdmin(admin.ModelAdmin):
|
|||
if change and "evaluation_points_deducted" in form.changed_data:
|
||||
obj.evaluation_points_deducted_user = request.user
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
|
||||
@admin.register(AssignmentCompletionAuditLog)
|
||||
class AssignmentCompletionAuditLogAdmin(LogAdmin):
|
||||
date_hierarchy = "created_at"
|
||||
list_display = [
|
||||
"created_at",
|
||||
"assignment",
|
||||
"get_circle",
|
||||
"assignment_user",
|
||||
"evaluation_user",
|
||||
"course_session",
|
||||
"completion_status",
|
||||
"evaluation_points",
|
||||
]
|
||||
list_filter = [
|
||||
"completion_status",
|
||||
"assignment__assignment_type",
|
||||
"course_session__course",
|
||||
"course_session",
|
||||
]
|
||||
search_fields = [
|
||||
"assignment_user__email",
|
||||
"assignment_user__first_name",
|
||||
"assignment_user__last_name",
|
||||
"evaluation_user__email",
|
||||
"evaluation_user__first_name",
|
||||
"evaluation_user__last_name",
|
||||
]
|
||||
|
||||
def get_circle(self, obj):
|
||||
try:
|
||||
return obj.learning_content_page.specific.get_circle().title
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
get_circle.short_description = "Circle"
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class AssignmentCompletionObjectType(DjangoObjectType):
|
|||
completion_data = GenericScalar()
|
||||
task_completion_data = GenericScalar()
|
||||
learning_content_page_id = graphene.ID(source="learning_content_page_id")
|
||||
additional_json_data = GenericScalar()
|
||||
|
||||
# rounded to sensible representation
|
||||
evaluation_points = graphene.Float()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
import djclick as click
|
||||
import structlog
|
||||
|
||||
from vbv_lernwelt.assignment.models import AssignmentCompletion
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
def create_initial_submission_history(apps=None, schema_editor=None):
|
||||
if apps is None:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from vbv_lernwelt.assignment.models import AssignmentCompletion
|
||||
else:
|
||||
AssignmentCompletion = apps.get_model("assignment", "AssignmentCompletion")
|
||||
|
||||
for ac in AssignmentCompletion.objects.filter(
|
||||
assignment__assignment_type__in=["PRAXIS_ASSIGNMENT", "CASEWORK"]
|
||||
):
|
||||
num_entries = ac_create_initial_submission_history(ac)
|
||||
# print(f"Created initial submission history for {ac} {num_entries}")
|
||||
|
||||
|
||||
def ac_create_initial_submission_history(ac: AssignmentCompletion):
|
||||
submission_history = ac.additional_json_data.get("submission_history", [])
|
||||
|
||||
num_entries = 0
|
||||
|
||||
if len(submission_history) > 0:
|
||||
return num_entries
|
||||
|
||||
if ac.submitted_at:
|
||||
entry = {
|
||||
"timestamp": ac.submitted_at.isoformat(),
|
||||
"status": "SUBMITTED",
|
||||
"translation_key": "a.Ergebnisse abgegeben",
|
||||
"user_id": str(ac.assignment_user.id),
|
||||
"user_email": ac.assignment_user.email,
|
||||
"user_display_name": (
|
||||
f"{ac.assignment_user.first_name} {ac.assignment_user.last_name}"
|
||||
),
|
||||
}
|
||||
submission_history.append(entry)
|
||||
num_entries += 1
|
||||
|
||||
if ac.evaluation_submitted_at:
|
||||
translation_key = "a.Bewertung freigegeben"
|
||||
if ac.assignment.assignment_type == "PRAXIS_ASSIGNMENT":
|
||||
translation_key = "Feedback freigegeben"
|
||||
|
||||
entry = {
|
||||
"timestamp": ac.evaluation_submitted_at.isoformat(),
|
||||
"status": "EVALUATION_SUBMITTED",
|
||||
"translation_key": translation_key,
|
||||
"user_id": str(ac.evaluation_user.id),
|
||||
"user_email": ac.evaluation_user.email,
|
||||
"user_display_name": (
|
||||
f"{ac.evaluation_user.first_name} {ac.evaluation_user.last_name}"
|
||||
),
|
||||
}
|
||||
submission_history.append(entry)
|
||||
num_entries += 1
|
||||
|
||||
ac.additional_json_data["submission_history"] = submission_history
|
||||
ac.save()
|
||||
|
||||
return num_entries
|
||||
|
||||
|
||||
@click.command()
|
||||
def command():
|
||||
create_initial_submission_history()
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# Generated by Django 4.2.13 on 2024-09-16 12:00
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("assignment", "0014_evaluation_points_deducted"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="assignmentcompletionauditlog",
|
||||
name="evaluation_points_deducted",
|
||||
field=models.FloatField(default=0.0, verbose_name="Punkteabzug"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="assignmentcompletionauditlog",
|
||||
name="evaluation_points_deducted_reason",
|
||||
field=models.TextField(
|
||||
blank=True, default="", verbose_name="Punkteabzug Begründung"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="assignmentcompletionauditlog",
|
||||
name="evaluation_points_deducted_user",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="+",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="assignmentcompletionauditlog",
|
||||
name="evaluation_points_deducted_user_email",
|
||||
field=models.CharField(blank=True, default="", max_length=255),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.2.13 on 2024-09-17 11:37
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
from vbv_lernwelt.assignment.management.commands.assignment_create_initial_submission_history import (
|
||||
create_initial_submission_history,
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
(
|
||||
"assignment",
|
||||
"0015_assignmentcompletionauditlog_evaluation_points_deducted_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [migrations.RunPython(create_initial_submission_history)]
|
||||
|
|
@ -485,6 +485,23 @@ class AssignmentCompletionAuditLog(models.Model):
|
|||
evaluation_max_points = models.FloatField(null=True, blank=True)
|
||||
evaluation_passed = models.BooleanField(null=True, blank=True)
|
||||
|
||||
evaluation_points_deducted = models.FloatField(
|
||||
default=0.0, verbose_name="Punkteabzug"
|
||||
)
|
||||
evaluation_points_deducted_reason = models.TextField(
|
||||
default="", blank=True, verbose_name="Punkteabzug Begründung"
|
||||
)
|
||||
evaluation_points_deducted_user = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="+",
|
||||
)
|
||||
evaluation_points_deducted_user_email = models.CharField(
|
||||
max_length=255, blank=True, default=""
|
||||
)
|
||||
|
||||
|
||||
def recalculate_assignment_passed(ac: AssignmentCompletion):
|
||||
if ac.evaluation_points_final is not None and ac.evaluation_max_points is not None:
|
||||
|
|
|
|||
|
|
@ -142,6 +142,13 @@ def update_assignment_completion(
|
|||
}
|
||||
)
|
||||
|
||||
evaluation_reopened = False
|
||||
if (
|
||||
completion_status == AssignmentCompletionStatus.EVALUATION_IN_PROGRESS
|
||||
and ac.completion_status == "EVALUATION_SUBMITTED"
|
||||
):
|
||||
evaluation_reopened = True
|
||||
|
||||
if evaluation_max_points is None:
|
||||
ac.evaluation_max_points = assignment.get_max_points()
|
||||
else:
|
||||
|
|
@ -229,9 +236,16 @@ def update_assignment_completion(
|
|||
evaluation_points=evaluation_points,
|
||||
evaluation_max_points=ac.evaluation_max_points,
|
||||
evaluation_passed=ac.evaluation_passed,
|
||||
evaluation_points_deducted=ac.evaluation_points_deducted,
|
||||
evaluation_points_deducted_reason=ac.evaluation_points_deducted_reason,
|
||||
evaluation_points_deducted_user=ac.evaluation_points_deducted_user,
|
||||
)
|
||||
if evaluation_user:
|
||||
acl.evaluation_user_email = evaluation_user.email
|
||||
if ac.evaluation_points_deducted_user:
|
||||
acl.evaluation_points_deducted_user_email = (
|
||||
ac.evaluation_points_deducted_user.email
|
||||
)
|
||||
|
||||
# copy over the question data, so that we don't lose the context
|
||||
subtasks = assignment.get_input_tasks()
|
||||
|
|
@ -241,23 +255,66 @@ def update_assignment_completion(
|
|||
acl.completion_data[key].update(task_data)
|
||||
acl.save()
|
||||
|
||||
if completion_status in [
|
||||
if (
|
||||
completion_status
|
||||
in [
|
||||
AssignmentCompletionStatus.EVALUATION_SUBMITTED,
|
||||
AssignmentCompletionStatus.EVALUATION_IN_PROGRESS,
|
||||
AssignmentCompletionStatus.SUBMITTED,
|
||||
]:
|
||||
learning_content = (
|
||||
learning_content_page
|
||||
if learning_content_page
|
||||
else assignment.find_attached_learning_content()
|
||||
]
|
||||
or evaluation_reopened
|
||||
) and assignment.assignment_type != AssignmentType.EDONIQ_TEST.value:
|
||||
# make history entry
|
||||
submission_history = ac.additional_json_data.get("submission_history", [])
|
||||
entry = {
|
||||
"timestamp": timezone.now().isoformat(),
|
||||
"status": completion_status.value,
|
||||
}
|
||||
|
||||
if completion_status == AssignmentCompletionStatus.SUBMITTED:
|
||||
entry["translation_key"] = "a.Ergebnisse abgegeben"
|
||||
entry["user_id"] = str(assignment_user.id)
|
||||
entry["user_email"] = assignment_user.email
|
||||
entry["user_display_name"] = (
|
||||
f"{assignment_user.first_name} {assignment_user.last_name}"
|
||||
)
|
||||
else:
|
||||
entry["user_id"] = str(evaluation_user.id)
|
||||
entry["user_email"] = evaluation_user.email
|
||||
entry["user_display_name"] = (
|
||||
f"{evaluation_user.first_name} {evaluation_user.last_name}"
|
||||
)
|
||||
if completion_status == AssignmentCompletionStatus.EVALUATION_SUBMITTED:
|
||||
if assignment.assignment_type == AssignmentType.PRAXIS_ASSIGNMENT.value:
|
||||
entry["translation_key"] = "a.Feedback freigegeben"
|
||||
else:
|
||||
entry["translation_key"] = "a.Bewertung freigegeben"
|
||||
elif completion_status == AssignmentCompletionStatus.EVALUATION_IN_PROGRESS:
|
||||
if assignment.assignment_type == AssignmentType.PRAXIS_ASSIGNMENT.value:
|
||||
entry["translation_key"] = "a.Feedback erneut bearbeitet"
|
||||
else:
|
||||
entry["translation_key"] = "a.Bewertung erneut bearbeitet"
|
||||
|
||||
submission_history.append(entry)
|
||||
ac.additional_json_data["submission_history"] = submission_history
|
||||
ac.save()
|
||||
|
||||
if completion_status in [
|
||||
AssignmentCompletionStatus.EVALUATION_SUBMITTED,
|
||||
AssignmentCompletionStatus.EVALUATION_IN_PROGRESS,
|
||||
AssignmentCompletionStatus.SUBMITTED,
|
||||
]:
|
||||
learning_content = (
|
||||
learning_content_page
|
||||
if learning_content_page
|
||||
else assignment.find_attached_learning_content()
|
||||
)
|
||||
if learning_content:
|
||||
mark_course_completion(
|
||||
user=assignment_user,
|
||||
page=learning_content,
|
||||
course_session=course_session,
|
||||
completion_status=CourseCompletionStatus.SUCCESS.value,
|
||||
)
|
||||
if learning_content:
|
||||
mark_course_completion(
|
||||
user=assignment_user,
|
||||
page=learning_content,
|
||||
course_session=course_session,
|
||||
completion_status=CourseCompletionStatus.SUCCESS.value,
|
||||
)
|
||||
|
||||
return ac, created
|
||||
|
||||
|
|
@ -267,7 +324,8 @@ def _remove_unknown_entries(assignment, completion_data):
|
|||
Removes all entries from completion_data which are not known to the assignment
|
||||
"""
|
||||
input_task_ids = [task["id"] for task in assignment.get_input_tasks()]
|
||||
keys = set(input_task_ids) | {"expert_evaluation_comment"}
|
||||
filtered_completion_data = {
|
||||
key: value for key, value in completion_data.items() if key in input_task_ids
|
||||
key: value for key, value in completion_data.items() if key in keys
|
||||
}
|
||||
return filtered_completion_data
|
||||
|
|
|
|||
|
|
@ -190,6 +190,13 @@ class UpdateAssignmentCompletionTestCase(TestCase):
|
|||
"test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice",
|
||||
)
|
||||
|
||||
# will create submission_history entry
|
||||
submission_history = ac.additional_json_data.get("submission_history", [])
|
||||
self.assertEqual(len(submission_history), 1)
|
||||
entry = submission_history[0]
|
||||
self.assertEqual(entry["status"], "SUBMITTED")
|
||||
self.assertEqual(entry["user_email"], "student")
|
||||
|
||||
# AssignmentCompletionAuditLog entry will remain event after deletion of foreign keys
|
||||
ac.delete()
|
||||
self.user.delete()
|
||||
|
|
@ -512,6 +519,13 @@ class UpdateAssignmentCompletionTestCase(TestCase):
|
|||
user_input["user_data"], {"text": "Ich würde nichts weiteres empfehlen."}
|
||||
)
|
||||
|
||||
# will create submission_history entry
|
||||
submission_history = ac.additional_json_data.get("submission_history", [])
|
||||
self.assertEqual(len(submission_history), 1)
|
||||
entry = submission_history[0]
|
||||
self.assertEqual(entry["status"], "EVALUATION_SUBMITTED")
|
||||
self.assertEqual(entry["user_email"], "admin")
|
||||
|
||||
# will create AssignmentCompletionAuditLog entry
|
||||
acl = AssignmentCompletionAuditLog.objects.get(
|
||||
assignment_user=self.user,
|
||||
|
|
@ -553,3 +567,156 @@ class UpdateAssignmentCompletionTestCase(TestCase):
|
|||
)
|
||||
self.assertIsNone(acl.assignment_user)
|
||||
self.assertIsNone(acl.assignment)
|
||||
|
||||
def test_can_reopen_evaluated_submission(self):
|
||||
subtasks = self.assignment.filter_user_subtasks(
|
||||
subtask_types=["user_text_input"]
|
||||
)
|
||||
user_text_input = find_first(
|
||||
subtasks,
|
||||
pred=lambda x: (value := x.get("value"))
|
||||
and value.get("text", "").startswith(
|
||||
"Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest?"
|
||||
),
|
||||
)
|
||||
|
||||
ac = AssignmentCompletion.objects.create(
|
||||
assignment_user=self.user,
|
||||
assignment=self.assignment,
|
||||
course_session=self.course_session,
|
||||
completion_status=AssignmentCompletionStatus.SUBMITTED.value,
|
||||
completion_data={
|
||||
user_text_input["id"]: {
|
||||
"user_data": {"text": "Ich würde nichts weiteres empfehlen."}
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
evaluation_task = self.assignment.get_evaluation_tasks()[0]
|
||||
|
||||
update_assignment_completion(
|
||||
assignment_user=self.user,
|
||||
assignment=self.assignment,
|
||||
course_session=self.course_session,
|
||||
completion_data={
|
||||
evaluation_task["id"]: {
|
||||
"expert_data": {"points": 2, "text": "Gut gemacht!"}
|
||||
},
|
||||
},
|
||||
completion_status=AssignmentCompletionStatus.EVALUATION_IN_PROGRESS,
|
||||
evaluation_user=self.trainer,
|
||||
)
|
||||
|
||||
update_assignment_completion(
|
||||
assignment_user=self.user,
|
||||
assignment=self.assignment,
|
||||
course_session=self.course_session,
|
||||
completion_data={},
|
||||
completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED,
|
||||
evaluation_user=self.trainer,
|
||||
evaluation_points=16,
|
||||
)
|
||||
|
||||
ac = AssignmentCompletion.objects.get(
|
||||
assignment_user=self.user,
|
||||
assignment=self.assignment,
|
||||
course_session=self.course_session,
|
||||
)
|
||||
|
||||
self.assertEqual(ac.completion_status, "EVALUATION_SUBMITTED")
|
||||
self.assertEqual(ac.evaluation_points, 16)
|
||||
self.assertEqual(ac.evaluation_max_points, 24)
|
||||
self.assertTrue(ac.evaluation_passed)
|
||||
trainer_input = ac.completion_data[evaluation_task["id"]]
|
||||
self.assertDictEqual(
|
||||
trainer_input["expert_data"], {"points": 2, "text": "Gut gemacht!"}
|
||||
)
|
||||
user_input = ac.completion_data[user_text_input["id"]]
|
||||
self.assertDictEqual(
|
||||
user_input["user_data"], {"text": "Ich würde nichts weiteres empfehlen."}
|
||||
)
|
||||
|
||||
update_assignment_completion(
|
||||
assignment_user=self.user,
|
||||
assignment=self.assignment,
|
||||
course_session=self.course_session,
|
||||
completion_data={},
|
||||
completion_status=AssignmentCompletionStatus.EVALUATION_IN_PROGRESS,
|
||||
evaluation_user=self.trainer,
|
||||
)
|
||||
|
||||
ac = AssignmentCompletion.objects.get(
|
||||
assignment_user=self.user,
|
||||
assignment=self.assignment,
|
||||
course_session=self.course_session,
|
||||
)
|
||||
self.assertEqual(ac.completion_status, "EVALUATION_IN_PROGRESS")
|
||||
|
||||
update_assignment_completion(
|
||||
assignment_user=self.user,
|
||||
assignment=self.assignment,
|
||||
course_session=self.course_session,
|
||||
completion_data={
|
||||
evaluation_task["id"]: {
|
||||
"expert_data": {
|
||||
"points": 2,
|
||||
"text": "Gut gemacht. Ich musste es noch einmal anschauen.",
|
||||
}
|
||||
},
|
||||
},
|
||||
completion_status=AssignmentCompletionStatus.EVALUATION_IN_PROGRESS,
|
||||
evaluation_user=self.trainer,
|
||||
)
|
||||
|
||||
# will create submission_history entry
|
||||
submission_history = ac.additional_json_data.get("submission_history", [])
|
||||
self.assertEqual(len(submission_history), 2)
|
||||
entry = submission_history[1]
|
||||
self.assertEqual(entry["status"], "EVALUATION_IN_PROGRESS")
|
||||
self.assertEqual(entry["user_email"], "admin")
|
||||
|
||||
update_assignment_completion(
|
||||
assignment_user=self.user,
|
||||
assignment=self.assignment,
|
||||
course_session=self.course_session,
|
||||
completion_data={},
|
||||
completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED,
|
||||
evaluation_user=self.trainer,
|
||||
evaluation_points=16,
|
||||
)
|
||||
|
||||
ac = AssignmentCompletion.objects.get(
|
||||
assignment_user=self.user,
|
||||
assignment=self.assignment,
|
||||
course_session=self.course_session,
|
||||
)
|
||||
|
||||
# will create submission_history entry
|
||||
submission_history = ac.additional_json_data.get("submission_history", [])
|
||||
self.assertEqual(len(submission_history), 3)
|
||||
entry = submission_history[2]
|
||||
self.assertEqual(entry["status"], "EVALUATION_SUBMITTED")
|
||||
self.assertEqual(entry["user_email"], "admin")
|
||||
|
||||
self.assertEqual(ac.completion_status, "EVALUATION_SUBMITTED")
|
||||
self.assertEqual(ac.evaluation_points, 16)
|
||||
self.assertEqual(ac.evaluation_max_points, 24)
|
||||
self.assertTrue(ac.evaluation_passed)
|
||||
trainer_input = ac.completion_data[evaluation_task["id"]]
|
||||
self.assertDictEqual(
|
||||
trainer_input["expert_data"],
|
||||
{"points": 2, "text": "Gut gemacht. Ich musste es noch einmal anschauen."},
|
||||
)
|
||||
user_input = ac.completion_data[user_text_input["id"]]
|
||||
self.assertDictEqual(
|
||||
user_input["user_data"], {"text": "Ich würde nichts weiteres empfehlen."}
|
||||
)
|
||||
|
||||
# it will have created another AssignmentCompletionAuditLog entry
|
||||
acl_qs = AssignmentCompletionAuditLog.objects.filter(
|
||||
assignment_user=self.user,
|
||||
assignment=self.assignment,
|
||||
course_session=self.course_session,
|
||||
completion_status="EVALUATION_SUBMITTED",
|
||||
)
|
||||
self.assertEqual(acl_qs.count(), 2)
|
||||
|
|
|
|||
Loading…
Reference in New Issue