VBV-321: squash current work
Load and display AssignmentCompletionStatus Datenmodell um Bewertungskriterien erweitern Refactor assignment pages Show user results Update some data fields Show user assignment evaluation Changes after rebase Rename `grading` -> `evaluation` Add evaluation data to AssignmentCompletion Refactor usage of `assignmentStore` Store points for evaluation Only debounce text Show user responses on evaluation page
This commit is contained in:
parent
90d9734f9b
commit
fef864df25
|
|
@ -1,12 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
export type StatusCountKey = "fail" | "success" | "unknown";
|
||||||
|
export type StatusCount = Record<StatusCountKey, number>;
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
statusCount?: {
|
statusCount?: StatusCount;
|
||||||
fail: number;
|
|
||||||
success: number;
|
|
||||||
unknown: number;
|
|
||||||
};
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const total = computed(() => {
|
const total = computed(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h2 v-if="label" class="heading-1 mb-8 block">{{ label }}</h2>
|
<div v-if="label" class="mb-2 block">{{ label }}</div>
|
||||||
<textarea
|
<textarea
|
||||||
:value="modelValue"
|
:value="modelValue"
|
||||||
class="h-40 w-full border-gray-500"
|
class="h-40 w-full border-gray-500"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
import { useCockpitStore } from "@/stores/cockpit";
|
||||||
import { useCompetenceStore } from "@/stores/competence";
|
import { useCompetenceStore } from "@/stores/competence";
|
||||||
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import { useLearningPathStore } from "@/stores/learningPath";
|
import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
|
|
@ -15,12 +16,15 @@ const props = defineProps<{
|
||||||
const cockpitStore = useCockpitStore();
|
const cockpitStore = useCockpitStore();
|
||||||
const competenceStore = useCompetenceStore();
|
const competenceStore = useCompetenceStore();
|
||||||
const learningPathStore = useLearningPathStore();
|
const learningPathStore = useLearningPathStore();
|
||||||
|
const courseSessionStore = useCourseSessionsStore();
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
log.debug("CockpitParentPage mounted", props.courseSlug);
|
log.debug("CockpitParentPage mounted", props.courseSlug);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await cockpitStore.loadCourseSessionUsers(props.courseSlug);
|
const currentCourseSession = courseSessionStore.currentCourseSession;
|
||||||
|
if (currentCourseSession?.id) {
|
||||||
|
await cockpitStore.loadCourseSessionUsers(currentCourseSession.id);
|
||||||
cockpitStore.courseSessionUsers?.forEach((csu) => {
|
cockpitStore.courseSessionUsers?.forEach((csu) => {
|
||||||
competenceStore.loadCompetenceProfilePage(
|
competenceStore.loadCompetenceProfilePage(
|
||||||
props.courseSlug + "-competence",
|
props.courseSlug + "-competence",
|
||||||
|
|
@ -30,6 +34,7 @@ onMounted(async () => {
|
||||||
learningPathStore.loadLearningPath(props.courseSlug + "-lp", csu.user_id);
|
learningPathStore.loadLearningPath(props.courseSlug + "-lp", csu.user_id);
|
||||||
});
|
});
|
||||||
learningPathStore.loadLearningPath(props.courseSlug + "-lp", useUserStore().id);
|
learningPathStore.loadLearningPath(props.courseSlug + "-lp", useUserStore().id);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(error);
|
log.error(error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { StatusCount, StatusCountKey } from "@/components/ui/ItProgress.vue";
|
||||||
|
import UserEvaluation from "@/pages/cockpit/assignmentEvaluationPage/UserEvaluation.vue";
|
||||||
|
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
|
||||||
|
import { useAssignmentStore } from "@/stores/assignmentStore";
|
||||||
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
|
import {
|
||||||
|
Assignment,
|
||||||
|
AssignmentCompletion,
|
||||||
|
CourseSessionAssignmentDetails,
|
||||||
|
CourseSessionUser,
|
||||||
|
} from "@/types";
|
||||||
|
import log from "loglevel";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
courseSlug: string;
|
||||||
|
assignmentId: number;
|
||||||
|
userId: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
log.debug("AssignmentEvaluationPage created", props.assignmentId, props.userId);
|
||||||
|
|
||||||
|
export interface StateInterface {
|
||||||
|
assignment: Assignment | undefined;
|
||||||
|
courseSessionAssignmentDetails: CourseSessionAssignmentDetails | undefined;
|
||||||
|
completionData: AssignmentCompletion | undefined;
|
||||||
|
assignmentUser: CourseSessionUser | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state: StateInterface = reactive({
|
||||||
|
assignment: undefined,
|
||||||
|
courseSessionAssignmentDetails: undefined,
|
||||||
|
completionData: undefined,
|
||||||
|
assignmentUser: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const assignmentStore = useAssignmentStore();
|
||||||
|
const courseSessionStore = useCourseSessionsStore();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
log.debug("AssignmentView mounted", props.assignmentId, props.learningContent);
|
||||||
|
|
||||||
|
if (courseSessionStore.currentCourseSession) {
|
||||||
|
state.assignmentUser = courseSessionStore.currentCourseSession.users.find(
|
||||||
|
(user) => user.user_id == props.userId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
state.assignment = await assignmentStore.loadAssignment(props.assignmentId);
|
||||||
|
state.completionData = await assignmentStore.loadAssignmentCompletion(
|
||||||
|
props.assignmentId,
|
||||||
|
courseSessionStore.currentCourseSession.id,
|
||||||
|
props.userId
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
log.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function exit() {
|
||||||
|
router.push({
|
||||||
|
path: `/course/${props.courseSlug}/cockpit/assignment`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="state.assignment && state.completionData">
|
||||||
|
<header
|
||||||
|
class="relative flex h-12 w-full items-center justify-between border-b border-b-gray-400 bg-white px-4 lg:h-16 lg:px-8"
|
||||||
|
>
|
||||||
|
<div>Geleitete Fallarbeit: {{ state.assignment.title }}</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="absolute right-2 top-2 h-8 w-8 cursor-pointer lg:right-4 lg:top-4"
|
||||||
|
data-cy="close-learning-content"
|
||||||
|
@click="exit()"
|
||||||
|
>
|
||||||
|
<it-icon-close></it-icon-close>
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<div class="w-1/2 flex-initial bg-white p-10">
|
||||||
|
<h3>Ergebnisse</h3>
|
||||||
|
<div class="my-6 flex items-center">
|
||||||
|
<img
|
||||||
|
:src="state.assignmentUser?.avatar_url"
|
||||||
|
class="mr-4 h-11 w-11 rounded-full"
|
||||||
|
/>
|
||||||
|
<div class="font-bold">
|
||||||
|
{{ state.assignmentUser?.first_name }}
|
||||||
|
{{ state.assignmentUser?.last_name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<AssignmentSubmissionResponses
|
||||||
|
:assignment="state.assignment"
|
||||||
|
:assignment-completion-data="state.completionData?.completion_data"
|
||||||
|
:allow-edit="false"
|
||||||
|
></AssignmentSubmissionResponses>
|
||||||
|
</div>
|
||||||
|
<div class="w-1/2 flex-initial bg-gray-200 p-10">
|
||||||
|
<UserEvaluation
|
||||||
|
:assignment-completion="state.completionData"
|
||||||
|
:assignment-user="state.assignmentUser"
|
||||||
|
:assignment="state.assignment"
|
||||||
|
></UserEvaluation>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ItTextarea from "@/components/ui/ItTextarea.vue";
|
||||||
|
import { useAssignmentStore } from "@/stores/assignmentStore";
|
||||||
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
|
import type {
|
||||||
|
Assignment,
|
||||||
|
AssignmentCompletionData,
|
||||||
|
CourseSessionUser,
|
||||||
|
ExpertData,
|
||||||
|
} from "@/types";
|
||||||
|
import { useDebounceFn } from "@vueuse/core";
|
||||||
|
import * as log from "loglevel";
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
assignmentUser: CourseSessionUser;
|
||||||
|
assignment: Assignment;
|
||||||
|
taskIndex: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
log.debug("EvaluationTask setup", props.taskIndex);
|
||||||
|
|
||||||
|
const courseSessionStore = useCourseSessionsStore();
|
||||||
|
const assignmentStore = useAssignmentStore();
|
||||||
|
|
||||||
|
const task = computed(() => props.assignment.evaluation_tasks[props.taskIndex]);
|
||||||
|
|
||||||
|
const expertData = computed(() => {
|
||||||
|
const data = (assignmentStore.assignmentCompletion?.completion_data?.[task.value.id]
|
||||||
|
?.expert_data ?? {
|
||||||
|
points: 0,
|
||||||
|
text: "",
|
||||||
|
}) as ExpertData;
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
|
||||||
|
function changePoints(points: number) {
|
||||||
|
log.debug("changePoints", points);
|
||||||
|
evaluateAssignmentCompletion({
|
||||||
|
[task.value.id]: {
|
||||||
|
expert_data: { points, text: expertData.value.text },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function onUpdateText(value: string) {
|
||||||
|
// log.debug("onUpdateText", value);
|
||||||
|
evaluateAssignmentCompletionDebounced({
|
||||||
|
[task.value.id]: {
|
||||||
|
expert_data: {
|
||||||
|
text: value,
|
||||||
|
points: expertData.value.points,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function evaluateAssignmentCompletion(completionData: AssignmentCompletionData) {
|
||||||
|
log.debug("evaluateAssignmentCompletion", completionData);
|
||||||
|
|
||||||
|
return assignmentStore.evaluateAssignmentCompletion({
|
||||||
|
assignment_user_id: Number(props.assignmentUser.user_id),
|
||||||
|
assignment_id: props.assignment.id,
|
||||||
|
course_session_id: courseSessionStore.currentCourseSession!.id,
|
||||||
|
completion_data: completionData,
|
||||||
|
completion_status: "evaluation_in_progress",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const evaluateAssignmentCompletionDebounced = useDebounceFn(
|
||||||
|
evaluateAssignmentCompletion,
|
||||||
|
500
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="text-bold mb-4 text-sm">
|
||||||
|
Beurteilungskriterium {{ taskIndex + 1 }} /
|
||||||
|
{{ props.assignment.evaluation_tasks.length }}
|
||||||
|
{{ task.value.title }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="mb-8">{{ task.value.description }}</h3>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-for="(subTask, index) in task.value.sub_tasks"
|
||||||
|
:key="index"
|
||||||
|
class="mb-4 flex items-center last:mb-0"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
:id="String(index)"
|
||||||
|
name="coursesessions"
|
||||||
|
type="radio"
|
||||||
|
:value="subTask.points"
|
||||||
|
:checked="expertData.points === subTask.points"
|
||||||
|
class="focus:ring-indigo-900 h-4 w-4 border-gray-300 text-blue-900"
|
||||||
|
@change="changePoints(subTask.points)"
|
||||||
|
/>
|
||||||
|
<label :for="String(index)" class="ml-4 block">
|
||||||
|
<div>{{ subTask.title }}</div>
|
||||||
|
<div
|
||||||
|
v-if="subTask.description"
|
||||||
|
class="default-wagtail-rich-text"
|
||||||
|
v-html="subTask.description"
|
||||||
|
></div>
|
||||||
|
<div class="text-sm text-gray-800">{{ subTask.points }} Punkte</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<ItTextarea
|
||||||
|
class="mt-8"
|
||||||
|
:model-value="expertData.text ?? ''"
|
||||||
|
label="Begründung"
|
||||||
|
@update:model-value="onUpdateText($event)"
|
||||||
|
></ItTextarea>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import UserEvaluationTask from "@/pages/cockpit/assignmentEvaluationPage/EvaluationTask.vue";
|
||||||
|
import type { Assignment, AssignmentCompletion, CourseSessionUser } from "@/types";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import * as log from "loglevel";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
assignmentUser: CourseSessionUser;
|
||||||
|
assignmentCompletion: AssignmentCompletion;
|
||||||
|
assignment: Assignment;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
log.debug("UserEvaluation setup");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="mb-4">
|
||||||
|
{{ props.assignmentUser.first_name }} {{ props.assignmentUser.last_name }} hat die
|
||||||
|
Ergebnisse am
|
||||||
|
{{ dayjs(props.assignmentCompletion.submitted_at).format("DD.MM.YYYY") }} um
|
||||||
|
{{ dayjs(props.assignmentCompletion.submitted_at).format("HH.mm") }} Uhr
|
||||||
|
abgegeben.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Bewertung</h3>
|
||||||
|
|
||||||
|
<UserEvaluationTask
|
||||||
|
:assignment-user="props.assignmentUser"
|
||||||
|
:assignment="props.assignment"
|
||||||
|
:task-index="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$nav-height: 86px;
|
||||||
|
|
||||||
|
.h-content {
|
||||||
|
height: calc(100vh - $nav-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
height: $nav-height;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||||
|
import type { StatusCount, StatusCountKey } from "@/components/ui/ItProgress.vue";
|
||||||
|
import AssignmentSubmissionProgress from "@/pages/cockpit/assignmentsPage/AssignmentSubmissionProgress.vue";
|
||||||
|
import type { AssignmentLearningContent } from "@/services/assignmentService";
|
||||||
|
import { loadAssignmentCompletionStatusData } from "@/services/assignmentService";
|
||||||
|
import { useCockpitStore } from "@/stores/cockpit";
|
||||||
|
import type { AssignmentCompletionStatus, CourseSession } from "@/types";
|
||||||
|
import log from "loglevel";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
courseSession: CourseSession;
|
||||||
|
assignment: AssignmentLearningContent;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
log.debug("AssignmentDetails created", props.assignment.assignmentId);
|
||||||
|
|
||||||
|
const cockpitStore = useCockpitStore();
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
statusByUser: [] as {
|
||||||
|
userStatus: AssignmentCompletionStatus;
|
||||||
|
progressStatus: StatusCountKey;
|
||||||
|
userId: number;
|
||||||
|
}[],
|
||||||
|
progressStatusCount: {} as StatusCount,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
state.statusByUser = await loadAssignmentCompletionStatusData(
|
||||||
|
props.assignment.assignmentId,
|
||||||
|
props.courseSession.id
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function submissionStatusForUser(userId: number) {
|
||||||
|
return state.statusByUser.find((s) => s.userId === userId);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="state.statusByUser.length">
|
||||||
|
<div class="text-large font-bold">
|
||||||
|
{{ assignment.title }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<AssignmentSubmissionProgress
|
||||||
|
:course-session="courseSession"
|
||||||
|
:assignment="assignment"
|
||||||
|
:show-title="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="cockpitStore.courseSessionUsers?.length" class="mt-6">
|
||||||
|
<ul>
|
||||||
|
<ItPersonRow
|
||||||
|
v-for="csu in cockpitStore.courseSessionUsers"
|
||||||
|
:key="csu.user_id + csu.session_title"
|
||||||
|
:name="`${csu.first_name} ${csu.last_name}`"
|
||||||
|
:avatar-url="csu.avatar_url"
|
||||||
|
>
|
||||||
|
<template #center>
|
||||||
|
{{ submissionStatusForUser(csu.user_id)?.userStatus || "-" }}
|
||||||
|
</template>
|
||||||
|
<template #link>
|
||||||
|
<div
|
||||||
|
v-if="submissionStatusForUser(csu.user_id)?.progressStatus === 'success'"
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
:to="`/course/${props.courseSession.course.slug}/cockpit/assignment/${assignment.assignmentId}/${csu.user_id}`"
|
||||||
|
class="w-full text-right underline"
|
||||||
|
>
|
||||||
|
Ergebnisse anzeigen
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ItPersonRow>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { StatusCount, StatusCountKey } from "@/components/ui/ItProgress.vue";
|
||||||
|
import ItProgress from "@/components/ui/ItProgress.vue";
|
||||||
|
import type { AssignmentLearningContent } from "@/services/assignmentService";
|
||||||
|
import { loadAssignmentCompletionStatusData } from "@/services/assignmentService";
|
||||||
|
import type { AssignmentCompletionStatus, CourseSession } from "@/types";
|
||||||
|
import { countBy } from "lodash";
|
||||||
|
import log from "loglevel";
|
||||||
|
import { onMounted, reactive } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
courseSession: CourseSession;
|
||||||
|
assignment: AssignmentLearningContent;
|
||||||
|
showTitle: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
log.debug("AssignmentSubmissionProgress created", props.assignment.assignmentId);
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
statusByUser: [] as {
|
||||||
|
userStatus: AssignmentCompletionStatus;
|
||||||
|
progressStatus: StatusCountKey;
|
||||||
|
userId: number;
|
||||||
|
}[],
|
||||||
|
progressStatusCount: {} as StatusCount,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
state.statusByUser = await loadAssignmentCompletionStatusData(
|
||||||
|
props.assignment.assignmentId,
|
||||||
|
props.courseSession.id
|
||||||
|
);
|
||||||
|
|
||||||
|
state.progressStatusCount = countBy(
|
||||||
|
state.statusByUser,
|
||||||
|
"progressStatus"
|
||||||
|
) as StatusCount;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="state.statusByUser.length">
|
||||||
|
<div v-if="showTitle">
|
||||||
|
{{ props.assignment.title }}
|
||||||
|
</div>
|
||||||
|
<div><ItProgress :status-count="state.progressStatusCount" /></div>
|
||||||
|
<div>
|
||||||
|
{{ state.progressStatusCount.success || 0 }} von
|
||||||
|
{{
|
||||||
|
(state.progressStatusCount.success || 0) +
|
||||||
|
(state.progressStatusCount.unknown || 0)
|
||||||
|
}}
|
||||||
|
Lernenden haben ihre Ergebnisse eingereicht.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import AssignmentDetails from "@/pages/cockpit/assignmentsPage/AssignmentDetails.vue";
|
||||||
|
import { calcAssignmentLearningContents } from "@/services/assignmentService";
|
||||||
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
|
import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import * as log from "loglevel";
|
||||||
|
import { computed, onMounted } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
courseSlug: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
log.debug("AssignmentsPage created", props.courseSlug);
|
||||||
|
|
||||||
|
const learningPathStore = useLearningPathStore();
|
||||||
|
const courseSessionStore = useCourseSessionsStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
log.debug("AssignmentsPage mounted");
|
||||||
|
});
|
||||||
|
|
||||||
|
const assignments = computed(() => {
|
||||||
|
// TODO: filter by selected circle
|
||||||
|
if (!courseSessionStore.currentCourseSession) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return calcAssignmentLearningContents(
|
||||||
|
learningPathStore.learningPathForUser(
|
||||||
|
courseSessionStore.currentCourseSession.course.slug,
|
||||||
|
userStore.id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bg-gray-200">
|
||||||
|
<div v-if="courseSessionStore.currentCourseSession" class="container-large">
|
||||||
|
<nav class="py-4 pb-4">
|
||||||
|
<router-link
|
||||||
|
class="btn-text inline-flex items-center pl-0"
|
||||||
|
:to="`/course/${props.courseSlug}/cockpit`"
|
||||||
|
>
|
||||||
|
<it-icon-arrow-left />
|
||||||
|
<span>{{ $t("general.back") }}</span>
|
||||||
|
</router-link>
|
||||||
|
</nav>
|
||||||
|
<header>
|
||||||
|
<h2 class="heading-2 mb-4 flex items-center gap-2">
|
||||||
|
<it-icon-lc-assignment class="h-16 w-16"></it-icon-lc-assignment>
|
||||||
|
<div>Geleitete Fallarbeiten</div>
|
||||||
|
</h2>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div v-for="assignment in assignments" :key="assignment.id">
|
||||||
|
<div class="bg-white p-6">
|
||||||
|
<AssignmentDetails
|
||||||
|
:course-session="courseSessionStore.currentCourseSession"
|
||||||
|
:assignment="assignment"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import AssignmentSubmissionProgress from "@/pages/cockpit/assignmentsPage/AssignmentSubmissionProgress.vue";
|
||||||
|
import { calcAssignmentLearningContents } from "@/services/assignmentService";
|
||||||
|
import { useCockpitStore } from "@/stores/cockpit";
|
||||||
|
import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import type { CourseSession } from "@/types";
|
||||||
|
import log from "loglevel";
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
courseSession: CourseSession;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
log.debug("AssignmentsTile created", props.courseSession.id);
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const cockpitStore = useCockpitStore();
|
||||||
|
const learningPathStore = useLearningPathStore();
|
||||||
|
|
||||||
|
const assignments = computed(() => {
|
||||||
|
// TODO: filter by selected circle
|
||||||
|
return calcAssignmentLearningContents(
|
||||||
|
learningPathStore.learningPathForUser(props.courseSession.course.slug, userStore.id)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bg-white px-6 py-5">
|
||||||
|
<div v-if="cockpitStore.courseSessionUsers">
|
||||||
|
<h3 class="heading-3 mb-4 flex items-center gap-2">
|
||||||
|
<it-icon-lc-assignment class="h-16 w-16"></it-icon-lc-assignment>
|
||||||
|
<div>Geleitete Fallarbeiten</div>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div v-for="assignment in assignments" :key="assignment.id">
|
||||||
|
<AssignmentSubmissionProgress
|
||||||
|
:show-title="true"
|
||||||
|
:course-session="props.courseSession"
|
||||||
|
:assignment="assignment"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6">
|
||||||
|
<router-link
|
||||||
|
:to="`/course/${props.courseSession.course.slug}/cockpit/assignment`"
|
||||||
|
class="link"
|
||||||
|
>
|
||||||
|
Alle anzeigen
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
|
@ -5,6 +5,7 @@ import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||||
import ItProgress from "@/components/ui/ItProgress.vue";
|
import ItProgress from "@/components/ui/ItProgress.vue";
|
||||||
import type { LearningPath } from "@/services/learningPath";
|
import type { LearningPath } from "@/services/learningPath";
|
||||||
|
|
||||||
|
import AssignmentsTile from "@/pages/cockpit/cockpitPage/AssignmentsTile.vue";
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
import { useCockpitStore } from "@/stores/cockpit";
|
||||||
import { useCompetenceStore } from "@/stores/competence";
|
import { useCompetenceStore } from "@/stores/competence";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
|
|
@ -92,7 +93,7 @@ function setActiveClasses(translationKey: string) {
|
||||||
<button
|
<button
|
||||||
class="mr-4 rounded-full border-2 border-blue-900 px-4 last:mr-0"
|
class="mr-4 rounded-full border-2 border-blue-900 px-4 last:mr-0"
|
||||||
:class="setActiveClasses(circle.translation_key)"
|
:class="setActiveClasses(circle.translation_key)"
|
||||||
@click="cockpitStore.toggleCourseSelection(circle.translation_key)"
|
@click="cockpitStore.toggleCircleSelection(circle.translation_key)"
|
||||||
>
|
>
|
||||||
{{ circle.title }}
|
{{ circle.title }}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -101,17 +102,10 @@ function setActiveClasses(translationKey: string) {
|
||||||
</div>
|
</div>
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<div class="mb-4 grid grid-rows-2 gap-4 lg:grid-cols-2 lg:grid-rows-none">
|
<div class="mb-4 grid grid-rows-2 gap-4 lg:grid-cols-2 lg:grid-rows-none">
|
||||||
<div class="bg-white px-6 py-5">
|
<AssignmentsTile
|
||||||
<h1
|
v-if="courseSessionStore.currentCourseSession"
|
||||||
class="heading-3 mb-4 bg-assignment bg-60 bg-no-repeat pl-[68px] leading-[60px]"
|
:course-session="courseSessionStore.currentCourseSession"
|
||||||
>
|
/>
|
||||||
{{ $t("general.transferTask", 2) }}
|
|
||||||
</h1>
|
|
||||||
<div class="mb-4">
|
|
||||||
<ItProgress :status-count="data.transferProgress"></ItProgress>
|
|
||||||
</div>
|
|
||||||
<p>{{ $t("cockpit.tasksDone") }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white px-6 py-5">
|
<div class="bg-white px-6 py-5">
|
||||||
<h1
|
<h1
|
||||||
class="heading-3 mb-4 bg-test bg-60 bg-no-repeat pl-[68px] leading-[60px]"
|
class="heading-3 mb-4 bg-test bg-60 bg-no-repeat pl-[68px] leading-[60px]"
|
||||||
|
|
@ -55,8 +55,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="mt-8">{{ $t("assignment.assessmentTitle") }}</h3>
|
<h3 class="mt-8">{{ $t("assignment.assessmentTitle") }}</h3>
|
||||||
<p class="text-large">{{ props.assignment.assessment_description }}</p>
|
<p class="text-large">{{ props.assignment.evaluation_description }}</p>
|
||||||
<a :href="props.assignment.assessment_document_url" class="text-large underline">
|
<a :href="props.assignment.evaluation_document_url" class="text-large underline">
|
||||||
{{ $t("assignment.showAssessmentDocument") }}
|
{{ $t("assignment.showAssessmentDocument") }}
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,27 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { UserDataText } from "@/stores/assignmentStore";
|
import type { AssignmentCompletionData, AssignmentTask, UserDataText } from "@/types";
|
||||||
import { useAssignmentStore } from "@/stores/assignmentStore";
|
import { Assignment } from "@/types";
|
||||||
import type { AssignmentTask } from "@/types";
|
|
||||||
import { computed } from "vue";
|
const props = defineProps<{
|
||||||
|
assignment: Assignment;
|
||||||
|
assignmentCompletionData: AssignmentCompletionData;
|
||||||
|
allowEdit: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "editTask", task: AssignmentTask): void;
|
(e: "editTask", task: AssignmentTask): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const assignmentStore = useAssignmentStore();
|
|
||||||
|
|
||||||
const completionData = computed(() => assignmentStore.completion_data);
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-for="task in assignmentStore.assignment?.tasks ?? []"
|
v-for="task in props.assignment.tasks ?? []"
|
||||||
:key="task.id"
|
:key="task.id"
|
||||||
class="mb-6 border-t border-gray-400"
|
class="mb-6 border-t border-gray-400"
|
||||||
>
|
>
|
||||||
<div class="flex flex-row justify-between pt-8">
|
<div class="flex flex-row justify-between pt-8">
|
||||||
<p class="text-sm text-gray-900">{{ task.value.title }}</p>
|
<p class="text-sm text-gray-900">{{ task.value.title }}</p>
|
||||||
<button
|
<button
|
||||||
|
v-if="props.allowEdit"
|
||||||
class="link whitespace-nowrap pl-2text-sm"
|
class="link whitespace-nowrap pl-2text-sm"
|
||||||
@click="emit('editTask', task)"
|
@click="emit('editTask', task)"
|
||||||
>
|
>
|
||||||
|
|
@ -28,9 +29,18 @@ const completionData = computed(() => assignmentStore.completion_data);
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="taskBlock in task.value.content" :key="taskBlock.id">
|
<div v-for="taskBlock in task.value.content" :key="taskBlock.id">
|
||||||
<p class="pt-6 text-base font-bold">{{ taskBlock.value.text }}</p>
|
<p
|
||||||
<p v-if="completionData && taskBlock.id in completionData" class="font-normal">
|
class="default-wagtail-rich-text pt-6 text-base font-bold"
|
||||||
{{ (completionData[taskBlock.id].user_data as UserDataText).text }}
|
v-html="taskBlock.value.text"
|
||||||
|
></p>
|
||||||
|
<p
|
||||||
|
v-if="
|
||||||
|
props.assignmentCompletionData &&
|
||||||
|
taskBlock.id in props.assignmentCompletionData
|
||||||
|
"
|
||||||
|
class="font-normal"
|
||||||
|
>
|
||||||
|
{{ (assignmentCompletionData[taskBlock.id].user_data as UserDataText).text }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import ItSuccessAlert from "@/components/ui/ItSuccessAlert.vue";
|
||||||
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
|
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
|
||||||
import { useAssignmentStore } from "@/stores/assignmentStore";
|
import { useAssignmentStore } from "@/stores/assignmentStore";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import type { Assignment, AssignmentTask } from "@/types";
|
import type { Assignment, AssignmentCompletionData, AssignmentTask } from "@/types";
|
||||||
import type { Dayjs } from "dayjs";
|
import type { Dayjs } from "dayjs";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { computed, reactive } from "vue";
|
import { computed, reactive } from "vue";
|
||||||
|
|
@ -13,6 +13,7 @@ import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
assignment: Assignment;
|
assignment: Assignment;
|
||||||
|
assignmentCompletionData: AssignmentCompletionData;
|
||||||
courseSessionId: number;
|
courseSessionId: number;
|
||||||
dueDate: Dayjs;
|
dueDate: Dayjs;
|
||||||
}>();
|
}>();
|
||||||
|
|
@ -49,12 +50,12 @@ const onSubmit = async () => {
|
||||||
log.error("Invalid courseSessionId");
|
log.error("Invalid courseSessionId");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await assignmentStore.upsertAssignmentCompletion(
|
await assignmentStore.upsertAssignmentCompletion({
|
||||||
props.assignment.id,
|
assignment_id: props.assignment.id,
|
||||||
{},
|
course_session_id: courseSessionId,
|
||||||
courseSessionId,
|
completion_data: {},
|
||||||
true
|
completion_status: "submitted",
|
||||||
);
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Could not submit assignment", error);
|
log.error("Could not submit assignment", error);
|
||||||
}
|
}
|
||||||
|
|
@ -126,6 +127,9 @@ const onSubmit = async () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AssignmentSubmissionResponses
|
<AssignmentSubmissionResponses
|
||||||
|
:assignment="props.assignment"
|
||||||
|
:assignment-completion-data="props.assignmentCompletionData"
|
||||||
|
:allow-edit="true"
|
||||||
@edit-task="onEditTask"
|
@edit-task="onEditTask"
|
||||||
></AssignmentSubmissionResponses>
|
></AssignmentSubmissionResponses>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
import ItCheckbox from "@/components/ui/ItCheckbox.vue";
|
||||||
import ItTextarea from "@/components/ui/ItTextarea.vue";
|
import ItTextarea from "@/components/ui/ItTextarea.vue";
|
||||||
import type {
|
|
||||||
AssignmentCompletionData,
|
|
||||||
BlockId,
|
|
||||||
UserDataConfirmation,
|
|
||||||
UserDataText,
|
|
||||||
} from "@/stores/assignmentStore";
|
|
||||||
import { useAssignmentStore } from "@/stores/assignmentStore";
|
import { useAssignmentStore } from "@/stores/assignmentStore";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import type { AssignmentTask } from "@/types";
|
import type {
|
||||||
|
AssignmentCompletionData,
|
||||||
|
AssignmentTask,
|
||||||
|
UserDataConfirmation,
|
||||||
|
UserDataText,
|
||||||
|
} from "@/types";
|
||||||
import { useDebounceFn } from "@vueuse/core";
|
import { useDebounceFn } from "@vueuse/core";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { reactive, ref } from "vue";
|
import { reactive, ref } from "vue";
|
||||||
|
|
@ -22,7 +21,7 @@ const props = defineProps<{
|
||||||
const lastSaved = ref(dayjs());
|
const lastSaved = ref(dayjs());
|
||||||
const lastSaveUnsuccessful = ref(false);
|
const lastSaveUnsuccessful = ref(false);
|
||||||
|
|
||||||
const checkboxState = reactive({} as Record<BlockId, boolean>);
|
const checkboxState = reactive({} as Record<string, boolean>);
|
||||||
|
|
||||||
const courseSessionStore = useCourseSessionsStore();
|
const courseSessionStore = useCourseSessionsStore();
|
||||||
const assignmentStore = useAssignmentStore();
|
const assignmentStore = useAssignmentStore();
|
||||||
|
|
@ -34,12 +33,12 @@ async function upsertAssignmentCompletion(completion_data: AssignmentCompletionD
|
||||||
console.error("Invalid courseSessionId");
|
console.error("Invalid courseSessionId");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await assignmentStore.upsertAssignmentCompletion(
|
await assignmentStore.upsertAssignmentCompletion({
|
||||||
props.assignmentId,
|
assignment_id: props.assignmentId,
|
||||||
completion_data,
|
course_session_id: courseSessionId,
|
||||||
courseSessionId,
|
completion_data: completion_data,
|
||||||
false
|
completion_status: "in_progress",
|
||||||
);
|
});
|
||||||
lastSaved.value = dayjs();
|
lastSaved.value = dayjs();
|
||||||
lastSaveUnsuccessful.value = false;
|
lastSaveUnsuccessful.value = false;
|
||||||
console.debug("Saved user input");
|
console.debug("Saved user input");
|
||||||
|
|
@ -54,7 +53,7 @@ const upsertAssignmentCompletionDebounced = useDebounceFn(
|
||||||
500
|
500
|
||||||
);
|
);
|
||||||
|
|
||||||
const onUpdateText = (id: BlockId, value: string) => {
|
const onUpdateText = (id: string, value: string) => {
|
||||||
const data: AssignmentCompletionData = {};
|
const data: AssignmentCompletionData = {};
|
||||||
data[id] = {
|
data[id] = {
|
||||||
user_data: {
|
user_data: {
|
||||||
|
|
@ -64,7 +63,7 @@ const onUpdateText = (id: BlockId, value: string) => {
|
||||||
upsertAssignmentCompletionDebounced(data);
|
upsertAssignmentCompletionDebounced(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUpdateConfirmation = (id: BlockId, value: boolean) => {
|
const onUpdateConfirmation = (id: string, value: boolean) => {
|
||||||
const data: AssignmentCompletionData = {};
|
const data: AssignmentCompletionData = {};
|
||||||
data[id] = {
|
data[id] = {
|
||||||
user_data: {
|
user_data: {
|
||||||
|
|
@ -74,7 +73,7 @@ const onUpdateConfirmation = (id: BlockId, value: boolean) => {
|
||||||
upsertAssignmentCompletion(data);
|
upsertAssignmentCompletion(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getBlockData = (id: BlockId) => {
|
const getBlockData = (id: string) => {
|
||||||
const userData = assignmentStore.getCompletionDataForUserInput(id)?.user_data;
|
const userData = assignmentStore.getCompletionDataForUserInput(id)?.user_data;
|
||||||
if (userData && "text" in userData) {
|
if (userData && "text" in userData) {
|
||||||
return userData.text;
|
return userData.text;
|
||||||
|
|
@ -84,7 +83,7 @@ const getBlockData = (id: BlockId) => {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onToggleCheckbox = (id: BlockId) => {
|
const onToggleCheckbox = (id: string) => {
|
||||||
checkboxState[id] = !checkboxState[id];
|
checkboxState[id] = !checkboxState[id];
|
||||||
onUpdateConfirmation(id, checkboxState[id]);
|
onUpdateConfirmation(id, checkboxState[id]);
|
||||||
};
|
};
|
||||||
|
|
@ -94,7 +93,7 @@ const onToggleCheckbox = (id: BlockId) => {
|
||||||
<div class="flex flex-col space-y-10">
|
<div class="flex flex-col space-y-10">
|
||||||
<div v-for="(block, index) in props.task.value.content" :key="block.id">
|
<div v-for="(block, index) in props.task.value.content" :key="block.id">
|
||||||
<div v-if="block.type === 'explanation'">
|
<div v-if="block.type === 'explanation'">
|
||||||
<p class="text-large">{{ block.value.text }}</p>
|
<p class="default-wagtail-rich-text text-large" v-html="block.value.text"></p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="block.type === 'user_confirmation'">
|
<div v-if="block.type === 'user_confirmation'">
|
||||||
<ItCheckbox
|
<ItCheckbox
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ import AssignmentIntroductionView from "@/pages/learningPath/learningContentPage
|
||||||
import AssignmentSubmissionView from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionView.vue";
|
import AssignmentSubmissionView from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionView.vue";
|
||||||
import AssignmentTaskView from "@/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue";
|
import AssignmentTaskView from "@/pages/learningPath/learningContentPage/assignment/AssignmentTaskView.vue";
|
||||||
import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue";
|
import LearningContentMultiLayout from "@/pages/learningPath/learningContentPage/layouts/LearningContentMultiLayout.vue";
|
||||||
import type { AssignmentCompletionData } from "@/stores/assignmentStore";
|
|
||||||
import { useAssignmentStore } from "@/stores/assignmentStore";
|
import { useAssignmentStore } from "@/stores/assignmentStore";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import type {
|
import type {
|
||||||
Assignment,
|
Assignment,
|
||||||
|
AssignmentCompletion,
|
||||||
AssignmentTask,
|
AssignmentTask,
|
||||||
CourseSessionAssignmentDetails,
|
CourseSessionAssignmentDetails,
|
||||||
LearningContent,
|
LearningContent,
|
||||||
|
|
@ -24,14 +24,14 @@ const assignmentStore = useAssignmentStore();
|
||||||
interface State {
|
interface State {
|
||||||
assignment: Assignment | undefined;
|
assignment: Assignment | undefined;
|
||||||
courseSessionAssignmentDetails: CourseSessionAssignmentDetails | undefined;
|
courseSessionAssignmentDetails: CourseSessionAssignmentDetails | undefined;
|
||||||
assignmentCompletionData: AssignmentCompletionData | undefined;
|
assignmentCompletion: AssignmentCompletion | undefined;
|
||||||
pageIndex: number;
|
pageIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state: State = reactive({
|
const state: State = reactive({
|
||||||
assignment: undefined,
|
assignment: undefined,
|
||||||
courseSessionAssignmentDetails: undefined,
|
courseSessionAssignmentDetails: undefined,
|
||||||
assignmentCompletionData: undefined,
|
assignmentCompletion: undefined,
|
||||||
// 0 = introduction, 1 - n = tasks, n+1 = submission
|
// 0 = introduction, 1 - n = tasks, n+1 = submission
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
});
|
});
|
||||||
|
|
@ -51,7 +51,7 @@ onMounted(async () => {
|
||||||
state.courseSessionAssignmentDetails = courseSessionsStore.findAssignmentDetails(
|
state.courseSessionAssignmentDetails = courseSessionsStore.findAssignmentDetails(
|
||||||
props.learningContent.id
|
props.learningContent.id
|
||||||
);
|
);
|
||||||
state.assignmentCompletionData = await assignmentStore.loadAssignmentCompletion(
|
state.assignmentCompletion = await assignmentStore.loadAssignmentCompletion(
|
||||||
props.assignmentId,
|
props.assignmentId,
|
||||||
courseSessionId.value
|
courseSessionId.value
|
||||||
);
|
);
|
||||||
|
|
@ -115,6 +115,7 @@ const getTitle = () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div v-if="state.assignment && state.assignmentCompletion"></div>
|
||||||
<LearningContentMultiLayout
|
<LearningContentMultiLayout
|
||||||
:current-step="state.pageIndex"
|
:current-step="state.pageIndex"
|
||||||
:subtitle="state.assignment?.title ?? ''"
|
:subtitle="state.assignment?.title ?? ''"
|
||||||
|
|
@ -146,6 +147,7 @@ const getTitle = () => {
|
||||||
v-if="state.pageIndex + 1 === numPages && state.assignment && courseSessionId"
|
v-if="state.pageIndex + 1 === numPages && state.assignment && courseSessionId"
|
||||||
:due-date="dueDate"
|
:due-date="dueDate"
|
||||||
:assignment="state.assignment!"
|
:assignment="state.assignment!"
|
||||||
|
:assignment-completion-data="state.assignmentCompletion?.completion_data ?? {}"
|
||||||
:course-session-id="courseSessionId!"
|
:course-session-id="courseSessionId!"
|
||||||
@edit-task="jumpToTask($event)"
|
@edit-task="jumpToTask($event)"
|
||||||
></AssignmentSubmissionView>
|
></AssignmentSubmissionView>
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ const router = createRouter({
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
component: () => import("@/pages/cockpit/CockpitIndexPage.vue"),
|
component: () => import("@/pages/cockpit/cockpitPage/CockpitPage.vue"),
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -129,6 +129,20 @@ const router = createRouter({
|
||||||
component: () => import("@/pages/cockpit/FeedbackPage.vue"),
|
component: () => import("@/pages/cockpit/FeedbackPage.vue"),
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "assignment",
|
||||||
|
component: () =>
|
||||||
|
import("@/pages/cockpit/assignmentsPage/AssignmentsPage.vue"),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "assignment/:assignmentId/:userId",
|
||||||
|
component: () =>
|
||||||
|
import(
|
||||||
|
"@/pages/cockpit/assignmentEvaluationPage/AssignmentEvaluationPage.vue"
|
||||||
|
),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
import type { StatusCountKey } from "@/components/ui/ItProgress.vue";
|
||||||
|
import { itGet } from "@/fetchHelpers";
|
||||||
|
import type { LearningPath } from "@/services/learningPath";
|
||||||
|
import { useCockpitStore } from "@/stores/cockpit";
|
||||||
|
import type {
|
||||||
|
AssignmentCompletionStatus,
|
||||||
|
CourseSessionUser,
|
||||||
|
LearningContent,
|
||||||
|
} from "@/types";
|
||||||
|
|
||||||
|
export interface AssignmentLearningContent extends LearningContent {
|
||||||
|
assignmentId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calcAssignmentLearningContents(learningPath: LearningPath) {
|
||||||
|
// TODO: filter by circle
|
||||||
|
return learningPath.circles.flatMap((circle) => {
|
||||||
|
const learningContents = circle.flatLearningContents.filter(
|
||||||
|
(lc) => lc.contents[0].type === "assignment"
|
||||||
|
);
|
||||||
|
return learningContents.map((lc) => {
|
||||||
|
return {
|
||||||
|
...lc,
|
||||||
|
// @ts-ignore
|
||||||
|
assignmentId: lc.contents[0].value.assignment,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}) as AssignmentLearningContent[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadAssignmentCompletionStatusData(
|
||||||
|
assignmentId: number,
|
||||||
|
courseSessionId: number
|
||||||
|
) {
|
||||||
|
const cockpitStore = useCockpitStore();
|
||||||
|
|
||||||
|
const assignmentCompletionData = await itGet(
|
||||||
|
`/api/assignment/${assignmentId}/${courseSessionId}/status/`
|
||||||
|
);
|
||||||
|
|
||||||
|
const courseSessionUsers = await cockpitStore.loadCourseSessionUsers(courseSessionId);
|
||||||
|
|
||||||
|
return calcUserAssignmentCompletionStatus(
|
||||||
|
courseSessionUsers,
|
||||||
|
assignmentCompletionData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calcUserAssignmentCompletionStatus(
|
||||||
|
courseSessionUsers: CourseSessionUser[],
|
||||||
|
assignmentCompletionStatusData: any
|
||||||
|
) {
|
||||||
|
return courseSessionUsers.map((u) => {
|
||||||
|
let userStatus = "unknown" as AssignmentCompletionStatus;
|
||||||
|
const userAssignmentStatus = assignmentCompletionStatusData?.find(
|
||||||
|
(s) => s.assignment_user_id === u.user_id
|
||||||
|
);
|
||||||
|
if (userAssignmentStatus) {
|
||||||
|
userStatus = userAssignmentStatus.completion_status;
|
||||||
|
}
|
||||||
|
let progressStatus: StatusCountKey = "unknown";
|
||||||
|
if (["submitted", "evaluation_in_progress", "evaluated"].includes(userStatus)) {
|
||||||
|
progressStatus = "success";
|
||||||
|
}
|
||||||
|
|
||||||
|
return { userId: u.user_id, userStatus, progressStatus };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -1,39 +1,25 @@
|
||||||
import { itGet, itPost } from "@/fetchHelpers";
|
import { itGet, itPost } from "@/fetchHelpers";
|
||||||
import type { Assignment } from "@/types";
|
import type {
|
||||||
|
Assignment,
|
||||||
|
AssignmentCompletion,
|
||||||
|
EvaluationCompletionData,
|
||||||
|
UpsertUserAssignmentCompletion,
|
||||||
|
} from "@/types";
|
||||||
|
import { merge } from "lodash";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
export type AssignmentStoreState = {
|
export type AssignmentStoreState = {
|
||||||
assignment: Assignment | undefined;
|
assignment: Assignment | undefined;
|
||||||
completion_data: AssignmentCompletionData;
|
assignmentCompletion: AssignmentCompletion | undefined;
|
||||||
submitted: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BlockId = string;
|
|
||||||
|
|
||||||
export interface UserDataText {
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserDataConfirmation {
|
|
||||||
confirmation: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AssignmentCompletionData {
|
|
||||||
// {
|
|
||||||
// "<user_text_input:uuid>": {"user_data": {"text": "some text from user"}},
|
|
||||||
// "<user_confirmation:uuid>": {"user_data": {"confirmation": true}},
|
|
||||||
// }
|
|
||||||
[key: BlockId]: { user_data: UserDataText | UserDataConfirmation };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useAssignmentStore = defineStore({
|
export const useAssignmentStore = defineStore({
|
||||||
id: "assignmentStore",
|
id: "assignmentStore",
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
assignment: undefined,
|
assignment: undefined,
|
||||||
completion_data: {},
|
assignmentCompletion: undefined,
|
||||||
submitted: false,
|
|
||||||
} as AssignmentStoreState;
|
} as AssignmentStoreState;
|
||||||
},
|
},
|
||||||
getters: {},
|
getters: {},
|
||||||
|
|
@ -48,37 +34,46 @@ export const useAssignmentStore = defineStore({
|
||||||
this.assignment = assignmentData;
|
this.assignment = assignmentData;
|
||||||
return this.assignment;
|
return this.assignment;
|
||||||
},
|
},
|
||||||
async loadAssignmentCompletion(assignmentId: number, courseSessionId: number) {
|
async loadAssignmentCompletion(
|
||||||
log.debug("load assignment completion", assignmentId, courseSessionId);
|
assignmentId: number,
|
||||||
|
courseSessionId: number,
|
||||||
|
userId: string | undefined = undefined
|
||||||
|
) {
|
||||||
|
log.debug("load assignment completion", assignmentId, courseSessionId, userId);
|
||||||
try {
|
try {
|
||||||
const data = await itGet(`/api/assignment/${assignmentId}/${courseSessionId}/`);
|
let url = `/api/assignment/${assignmentId}/${courseSessionId}/`;
|
||||||
this.completion_data = data.completion_data;
|
if (userId) {
|
||||||
this.submitted = data.completion_status === "submitted";
|
url += `${userId}/`;
|
||||||
|
}
|
||||||
|
this.assignmentCompletion = await itGet(url);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.debug("no completion data found ", e);
|
log.debug("no completion data found ", e);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return this.completion_data;
|
return this.assignmentCompletion;
|
||||||
},
|
},
|
||||||
getCompletionDataForUserInput(id: BlockId) {
|
getCompletionDataForUserInput(id: string) {
|
||||||
return this.completion_data[id];
|
return this.assignmentCompletion?.completion_data[id];
|
||||||
},
|
},
|
||||||
async upsertAssignmentCompletion(
|
async upsertAssignmentCompletion(data: UpsertUserAssignmentCompletion) {
|
||||||
assignmentId: number,
|
if (this.assignmentCompletion) {
|
||||||
completion_data: AssignmentCompletionData,
|
merge(this.assignmentCompletion.completion_data, data.completion_data);
|
||||||
courseSessionId: number,
|
this.assignmentCompletion.completion_status = data.completion_status;
|
||||||
submit: boolean
|
}
|
||||||
) {
|
|
||||||
const data = {
|
|
||||||
assignment_id: assignmentId,
|
|
||||||
completion_status: submit ? "submitted" : "in_progress",
|
|
||||||
course_session_id: courseSessionId,
|
|
||||||
completion_data: completion_data,
|
|
||||||
};
|
|
||||||
const responseData = await itPost(`/api/assignment/upsert/`, data);
|
const responseData = await itPost(`/api/assignment/upsert/`, data);
|
||||||
if (responseData) {
|
if (responseData) {
|
||||||
this.completion_data = responseData.completion_data;
|
this.assignmentCompletion = responseData;
|
||||||
this.submitted = responseData.completion_status === "submitted";
|
}
|
||||||
|
return responseData;
|
||||||
|
},
|
||||||
|
async evaluateAssignmentCompletion(data: EvaluationCompletionData) {
|
||||||
|
if (this.assignmentCompletion) {
|
||||||
|
merge(this.assignmentCompletion.completion_data, data.completion_data);
|
||||||
|
this.assignmentCompletion.completion_status = data.completion_status;
|
||||||
|
}
|
||||||
|
const responseData = await itPost(`/api/assignment/evaluate/`, data);
|
||||||
|
if (responseData) {
|
||||||
|
this.assignmentCompletion = responseData;
|
||||||
}
|
}
|
||||||
return responseData;
|
return responseData;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,14 @@ export const useCockpitStore = defineStore({
|
||||||
} as CockpitStoreState;
|
} as CockpitStoreState;
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async loadCourseSessionUsers(courseSlug: string, reload = false) {
|
async loadCourseSessionUsers(courseSessionId: number, reload = false) {
|
||||||
log.debug("loadCockpitData called");
|
log.debug("loadCockpitData called");
|
||||||
const users = (await itGetCached(`/api/course/sessions/${courseSlug}/users/`, {
|
const users = (await itGetCached(
|
||||||
|
`/api/course/sessions/${courseSessionId}/users/`,
|
||||||
|
{
|
||||||
reload: reload,
|
reload: reload,
|
||||||
})) as CourseSessionUser[];
|
}
|
||||||
|
)) as CourseSessionUser[];
|
||||||
|
|
||||||
this.courseSessionUsers = users.filter((user) => user.role === "MEMBER");
|
this.courseSessionUsers = users.filter((user) => user.role === "MEMBER");
|
||||||
|
|
||||||
|
|
@ -45,7 +48,7 @@ export const useCockpitStore = defineStore({
|
||||||
}
|
}
|
||||||
return this.courseSessionUsers;
|
return this.courseSessionUsers;
|
||||||
},
|
},
|
||||||
toggleCourseSelection(translationKey: string) {
|
toggleCircleSelection(translationKey: string) {
|
||||||
if (this.selectedCircles.indexOf(translationKey) < 0) {
|
if (this.selectedCircles.indexOf(translationKey) < 0) {
|
||||||
this.selectedCircles.push(translationKey);
|
this.selectedCircles.push(translationKey);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -39,12 +39,9 @@ function loadCourseSessionsData(reload = false) {
|
||||||
// TODO: refactor after implementing of Klassenkonzept
|
// TODO: refactor after implementing of Klassenkonzept
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
courseSessions.value.map(async (cs) => {
|
courseSessions.value.map(async (cs) => {
|
||||||
const users = (await itGetCached(
|
const users = (await itGetCached(`/api/course/sessions/${cs.id}/users/`, {
|
||||||
`/api/course/sessions/${cs.course.slug}/users/`,
|
|
||||||
{
|
|
||||||
reload: reload,
|
reload: reload,
|
||||||
}
|
})) as CourseSessionUser[];
|
||||||
)) as CourseSessionUser[];
|
|
||||||
cs.users = users;
|
cs.users = users;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,11 @@ export interface BaseLearningContentBlock {
|
||||||
|
|
||||||
export interface AssignmentBlock extends BaseLearningContentBlock {
|
export interface AssignmentBlock extends BaseLearningContentBlock {
|
||||||
readonly type: "assignment";
|
readonly type: "assignment";
|
||||||
|
readonly value: {
|
||||||
|
description: string;
|
||||||
|
url: string;
|
||||||
|
assignment: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BookBlock extends BaseLearningContentBlock {
|
export interface BookBlock extends BaseLearningContentBlock {
|
||||||
|
|
@ -332,14 +337,32 @@ export interface AssignmentTask {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AssignmentEvaluationSubTask {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
points: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssignmentEvaluationTask {
|
||||||
|
readonly type: "task";
|
||||||
|
readonly id: string;
|
||||||
|
readonly value: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
max_points: number;
|
||||||
|
sub_tasks: AssignmentEvaluationSubTask[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface Assignment extends BaseCourseWagtailPage {
|
export interface Assignment extends BaseCourseWagtailPage {
|
||||||
readonly type: "assignment.Assignment";
|
readonly type: "assignment.Assignment";
|
||||||
readonly starting_position: string;
|
readonly starting_position: string;
|
||||||
readonly effort_required: string;
|
readonly effort_required: string;
|
||||||
readonly performance_objectives: AssignmentPerformanceObjective[];
|
readonly performance_objectives: AssignmentPerformanceObjective[];
|
||||||
readonly assessment_description: string;
|
readonly evaluation_description: string;
|
||||||
readonly assessment_document_url: string;
|
readonly evaluation_document_url: string;
|
||||||
readonly tasks: AssignmentTask[];
|
readonly tasks: AssignmentTask[];
|
||||||
|
readonly evaluation_tasks: AssignmentEvaluationTask[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PerformanceCriteria extends BaseCourseWagtailPage {
|
export interface PerformanceCriteria extends BaseCourseWagtailPage {
|
||||||
|
|
@ -479,3 +502,59 @@ export interface Notification {
|
||||||
actor_avatar_url: string | null;
|
actor_avatar_url: string | null;
|
||||||
course: string | null;
|
course: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AssignmentCompletionStatus =
|
||||||
|
| "unknwown"
|
||||||
|
| "in_progress"
|
||||||
|
| "submitted"
|
||||||
|
| "evaluation_in_progress"
|
||||||
|
| "evaluated";
|
||||||
|
|
||||||
|
export interface UserDataText {
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserDataConfirmation {
|
||||||
|
confirmation: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExpertData {
|
||||||
|
points?: number;
|
||||||
|
text?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssignmentCompletionData {
|
||||||
|
// {
|
||||||
|
// "<user_text_input:uuid>": {"user_data": {"text": "some text from user"}},
|
||||||
|
// "<user_confirmation:uuid>": {"user_data": {"confirmation": true}},
|
||||||
|
// }
|
||||||
|
[key: string]: {
|
||||||
|
user_data?: UserDataText | UserDataConfirmation;
|
||||||
|
expert_data?: ExpertData;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssignmentCompletion {
|
||||||
|
id: number;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
submitted_at: string;
|
||||||
|
evaluated_at: string | null;
|
||||||
|
assignment_user: number;
|
||||||
|
assignment: number;
|
||||||
|
course_session: number;
|
||||||
|
completion_status: AssignmentCompletionStatus;
|
||||||
|
evaluation_user: number | null;
|
||||||
|
completion_data: AssignmentCompletionData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpsertUserAssignmentCompletion = {
|
||||||
|
assignment_id: number;
|
||||||
|
course_session_id: number;
|
||||||
|
completion_status: AssignmentCompletionStatus;
|
||||||
|
completion_data: AssignmentCompletionData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type EvaluationCompletionData = UpsertUserAssignmentCompletion & {
|
||||||
|
assignment_user_id: number;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,11 @@ body {
|
||||||
hyphens: auto;
|
hyphens: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.default-wagtail-rich-text ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
margin-left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@apply fill-current;
|
@apply fill-current;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,15 @@ from django.urls import include, path, re_path
|
||||||
from django.views import defaults as default_views
|
from django.views import defaults as default_views
|
||||||
from grapple import urls as grapple_urls
|
from grapple import urls as grapple_urls
|
||||||
from ratelimit.exceptions import Ratelimited
|
from ratelimit.exceptions import Ratelimited
|
||||||
|
from wagtail import urls as wagtail_urls
|
||||||
|
from wagtail.admin import urls as wagtailadmin_urls
|
||||||
|
from wagtail.documents import urls as wagtaildocs_urls
|
||||||
|
|
||||||
from vbv_lernwelt.assignment.views import (
|
from vbv_lernwelt.assignment.views import (
|
||||||
grade_assignment_completion,
|
evaluate_assignment_completion,
|
||||||
request_assignment_completion,
|
request_assignment_completion,
|
||||||
request_assignment_completion_for_user,
|
request_assignment_completion_for_user,
|
||||||
|
request_assignment_completion_status,
|
||||||
upsert_user_assignment_completion,
|
upsert_user_assignment_completion,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
|
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
|
||||||
|
|
@ -44,9 +48,6 @@ from vbv_lernwelt.feedback.views import (
|
||||||
get_feedback_for_circle,
|
get_feedback_for_circle,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.notify.views import email_notification_settings
|
from vbv_lernwelt.notify.views import email_notification_settings
|
||||||
from wagtail import urls as wagtail_urls
|
|
||||||
from wagtail.admin import urls as wagtailadmin_urls
|
|
||||||
from wagtail.documents import urls as wagtaildocs_urls
|
|
||||||
|
|
||||||
|
|
||||||
def raise_example_error(request):
|
def raise_example_error(request):
|
||||||
|
|
@ -87,7 +88,7 @@ urlpatterns = [
|
||||||
|
|
||||||
# course
|
# course
|
||||||
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
|
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
|
||||||
path(r"api/course/sessions/<course_slug>/users/", get_course_session_users,
|
path(r"api/course/sessions/<course_session_id>/users/", get_course_session_users,
|
||||||
name="get_course_session_users"),
|
name="get_course_session_users"),
|
||||||
path(r"api/course/page/<slug_or_id>/", course_page_api_view,
|
path(r"api/course/page/<slug_or_id>/", course_page_api_view,
|
||||||
name="course_page_api_view"),
|
name="course_page_api_view"),
|
||||||
|
|
@ -102,11 +103,14 @@ urlpatterns = [
|
||||||
# assignment
|
# assignment
|
||||||
path(r"api/assignment/upsert/", upsert_user_assignment_completion,
|
path(r"api/assignment/upsert/", upsert_user_assignment_completion,
|
||||||
name="upsert_user_assignment_completion"),
|
name="upsert_user_assignment_completion"),
|
||||||
path(r"api/assignment/grade/", grade_assignment_completion,
|
path(r"api/assignment/evaluate/", evaluate_assignment_completion,
|
||||||
name="grade_assignment_completion"),
|
name="grade_assignment_completion"),
|
||||||
path(r"api/assignment/<int:assignment_id>/<int:course_session_id>/",
|
path(r"api/assignment/<int:assignment_id>/<int:course_session_id>/",
|
||||||
request_assignment_completion,
|
request_assignment_completion,
|
||||||
name="request_assignment_completion"),
|
name="request_assignment_completion"),
|
||||||
|
path(r"api/assignment/<int:assignment_id>/<int:course_session_id>/status/",
|
||||||
|
request_assignment_completion_status,
|
||||||
|
name="request_assignment_completion_status"),
|
||||||
path(r"api/assignment/<int:assignment_id>/<int:course_session_id>/<int:user_id>/",
|
path(r"api/assignment/<int:assignment_id>/<int:course_session_id>/<int:user_id>/",
|
||||||
request_assignment_completion_for_user,
|
request_assignment_completion_for_user,
|
||||||
name="request_assignment_completion_for_user"),
|
name="request_assignment_completion_for_user"),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
from vbv_lernwelt.assignment.models import TaskContentStreamBlock
|
from wagtail.blocks import StreamValue
|
||||||
|
from wagtail.blocks.list_block import ListBlock, ListValue
|
||||||
|
from wagtail.rich_text import RichText
|
||||||
|
|
||||||
|
from vbv_lernwelt.assignment.models import (
|
||||||
|
EvaluationSubTaskBlock,
|
||||||
|
TaskContentStreamBlock,
|
||||||
|
)
|
||||||
from vbv_lernwelt.assignment.tests.assignment_factories import (
|
from vbv_lernwelt.assignment.tests.assignment_factories import (
|
||||||
AssignmentFactory,
|
AssignmentFactory,
|
||||||
AssignmentListPageFactory,
|
AssignmentListPageFactory,
|
||||||
|
EvaluationSubTaskBlockFactory,
|
||||||
|
EvaluationTaskBlockFactory,
|
||||||
ExplanationBlockFactory,
|
ExplanationBlockFactory,
|
||||||
PerformanceObjectiveBlockFactory,
|
PerformanceObjectiveBlockFactory,
|
||||||
TaskBlockFactory,
|
TaskBlockFactory,
|
||||||
|
|
@ -10,7 +19,6 @@ from vbv_lernwelt.assignment.tests.assignment_factories import (
|
||||||
from vbv_lernwelt.core.utils import replace_whitespace
|
from vbv_lernwelt.core.utils import replace_whitespace
|
||||||
from vbv_lernwelt.course.consts import COURSE_TEST_ID, COURSE_UK
|
from vbv_lernwelt.course.consts import COURSE_TEST_ID, COURSE_UK
|
||||||
from vbv_lernwelt.course.models import CoursePage
|
from vbv_lernwelt.course.models import CoursePage
|
||||||
from wagtail.blocks import StreamValue
|
|
||||||
|
|
||||||
|
|
||||||
def create_uk_assignments(course_id=COURSE_UK):
|
def create_uk_assignments(course_id=COURSE_UK):
|
||||||
|
|
@ -46,8 +54,187 @@ def create_uk_assignments(course_id=COURSE_UK):
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
assessment_document_url="https://www.vbv.ch",
|
evaluation_document_url="https://www.vbv.ch",
|
||||||
assessment_description="Diese geleitete Fallarbeit wird auf Grund des folgenden Beurteilungsintrument bewertet.",
|
evaluation_description="Diese geleitete Fallarbeit wird auf Grund des folgenden Beurteilungsintrument bewertet.",
|
||||||
|
)
|
||||||
|
|
||||||
|
assignment.evaluation_tasks = []
|
||||||
|
assignment.evaluation_tasks.append(
|
||||||
|
(
|
||||||
|
"task",
|
||||||
|
EvaluationTaskBlockFactory(
|
||||||
|
title="Ausgangslage des Auftrags",
|
||||||
|
description=RichText(
|
||||||
|
"Beschreibt der/die Lernende die Ausgangslage des Auftrags vollständig?"
|
||||||
|
),
|
||||||
|
max_points=6,
|
||||||
|
sub_tasks=ListValue(
|
||||||
|
ListBlock(EvaluationSubTaskBlock()),
|
||||||
|
values=[
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Ausgangslage des Auftrag ist vollständig beschrieben.",
|
||||||
|
description=RichText(
|
||||||
|
replace_whitespace(
|
||||||
|
"""
|
||||||
|
<ul>
|
||||||
|
<li>Worum geht es? Was ist die Aufgabe?</li>
|
||||||
|
<li>Sind das Kundenprofil und die Kundenbeziehung vollständig und nachvollziehbar dargestellt?</li>
|
||||||
|
<li>Ist das Alter des Fahrzeugs dokumentiert?</li>
|
||||||
|
<li>Welche Ressourcen stehen zur Verfügung?</li>
|
||||||
|
</ul>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
),
|
||||||
|
points=6,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Ausgangslage ist grösstenteils vollständig beschrieben.",
|
||||||
|
points=4,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Ausgangslage ist unvollständig - nur 2 Punkte wurden beschrieben.",
|
||||||
|
points=2,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Ausgangslage des Auftrag ist unvollständig - es fehlen mehr als 2 Punkte in der Beschreibung.",
|
||||||
|
points=0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assignment.evaluation_tasks.append(
|
||||||
|
(
|
||||||
|
"task",
|
||||||
|
EvaluationTaskBlockFactory(
|
||||||
|
title="Inhaltsanalyse und Struktur",
|
||||||
|
max_points=6,
|
||||||
|
description=RichText(
|
||||||
|
"Sind die Deckungen der Police vollständig und nachvollziehbar dokumentiert?"
|
||||||
|
),
|
||||||
|
sub_tasks=ListValue(
|
||||||
|
ListBlock(EvaluationSubTaskBlock()),
|
||||||
|
values=[
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Analyse beinhaltet alle in der Police vorhandenen Deckungen und ist logisch aufgebaut.",
|
||||||
|
points=6,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Analyse beinhaltet die meisten vorhandenen Deckungen in der Police und ist grösstenteils logisch aufgebaut.",
|
||||||
|
points=4,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Analyse ist unvollständig (es fehlen mehr als 3 Deckungen) und der rote Faden ist nicht erkennbar.",
|
||||||
|
points=2,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Analyse ist insgesamt nicht nachvollziehbar und es fehlen einige Deckungen.",
|
||||||
|
points=0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assignment.evaluation_tasks.append(
|
||||||
|
(
|
||||||
|
"task",
|
||||||
|
EvaluationTaskBlockFactory(
|
||||||
|
title="Sinnvolle Empfehlungen",
|
||||||
|
max_points=6,
|
||||||
|
description=RichText(
|
||||||
|
"Leitet die lernende Person sinnvolle und geeignete Empfehlungen ab?"
|
||||||
|
),
|
||||||
|
sub_tasks=ListValue(
|
||||||
|
ListBlock(EvaluationSubTaskBlock()),
|
||||||
|
values=[
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Empfehlungen sind durchgängig sinnvoll und nachvollziehbar begründet.",
|
||||||
|
points=6,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Empfehlungen sind grösstenteils sinnvoll und nachvollziehbar begründet.",
|
||||||
|
points=4,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Empfehlungen sind wenig sinnvoll und unvollständig begründet.",
|
||||||
|
points=2,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Empfehlungen sind weder sinnvoll nch nachvollziehbar begründet.",
|
||||||
|
points=0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assignment.evaluation_tasks.append(
|
||||||
|
(
|
||||||
|
"task",
|
||||||
|
EvaluationTaskBlockFactory(
|
||||||
|
title="Qualität der Reflexion",
|
||||||
|
max_points=3,
|
||||||
|
description=RichText(
|
||||||
|
"Reflektiert die lernende Person die Durchführung der geleiteten Fallarbeit?"
|
||||||
|
),
|
||||||
|
sub_tasks=ListValue(
|
||||||
|
ListBlock(EvaluationSubTaskBlock()),
|
||||||
|
values=[
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Reflexion bezieht sich auf die geleitete Fallarbeit und umfasst nachvollziehbare positive wie negative Aspekte.",
|
||||||
|
points=3,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Reflexion bezieht sich auf die geleitete Fallarbeit und umfasst grösstenteils nachvollziehbare positive wie negative Aspekte.",
|
||||||
|
points=2,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Reflexion ist unvollständig.",
|
||||||
|
points=1,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Reflexion bezieht sich nicht auf die geleitete Fallarbeit.",
|
||||||
|
points=0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assignment.evaluation_tasks.append(
|
||||||
|
(
|
||||||
|
"task",
|
||||||
|
EvaluationTaskBlockFactory(
|
||||||
|
title="Eignung der Learnings",
|
||||||
|
max_points=3,
|
||||||
|
description=RichText(
|
||||||
|
"Leitet die lernende Person geeignete Learnings aus der Reflexion ab?"
|
||||||
|
),
|
||||||
|
sub_tasks=ListValue(
|
||||||
|
ListBlock(EvaluationSubTaskBlock()),
|
||||||
|
values=[
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Learnings beziehen sich auf die geleitete Fallarbeit und sind inhaltlich sinnvoll.",
|
||||||
|
points=3,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Learnings beziehen sich grösstenteils auf die geleitete Fallarbeit und sind inhaltlich sinnvoll.",
|
||||||
|
points=2,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Learnings beziehen sich teilweise auf die geleitete Fallarbeit und sind inhaltlich wenig sinnvoll.",
|
||||||
|
points=1,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Learnings beziehen sich nicht auf die geleitete Fallarbeit.",
|
||||||
|
points=0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
assignment.tasks = []
|
assignment.tasks = []
|
||||||
|
|
@ -57,18 +244,24 @@ def create_uk_assignments(course_id=COURSE_UK):
|
||||||
TaskBlockFactory(
|
TaskBlockFactory(
|
||||||
title="Teilaufgabe 1: Beispiel einer Versicherungspolice finden",
|
title="Teilaufgabe 1: Beispiel einer Versicherungspolice finden",
|
||||||
# it is hard to create a StreamValue programmatically, we have to
|
# it is hard to create a StreamValue programmatically, we have to
|
||||||
# create a `StreamValue` manually. Ask the Daniel and/or Ramon
|
# create a `StreamValue` manually. Ask Daniel and/or Ramon
|
||||||
content=StreamValue(
|
content=StreamValue(
|
||||||
TaskContentStreamBlock(),
|
TaskContentStreamBlock(),
|
||||||
stream_data=[
|
stream_data=[
|
||||||
(
|
(
|
||||||
"explanation",
|
"explanation",
|
||||||
ExplanationBlockFactory(text="Dies ist ein Beispieltext."),
|
ExplanationBlockFactory(
|
||||||
|
text=RichText(
|
||||||
|
"Bitte jemand aus deiner Familie oder deinem Freundeskreis darum, dir seine/ihre Motorfahrzeugversicherungspolice zur Verfügung zu stellen."
|
||||||
|
)
|
||||||
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_confirmation",
|
"user_confirmation",
|
||||||
ExplanationBlockFactory(
|
ExplanationBlockFactory(
|
||||||
text="Ja, ich habe Motorfahrzeugversicherungspolice von jemandem aus meiner Familie oder meinem Freundeskreis erhalten."
|
text=RichText(
|
||||||
|
"Ja, ich habe Motorfahrzeugversicherungspolice von jemandem aus meiner Familie oder meinem Freundeskreis erhalten."
|
||||||
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -88,13 +281,17 @@ def create_uk_assignments(course_id=COURSE_UK):
|
||||||
(
|
(
|
||||||
"explanation",
|
"explanation",
|
||||||
ExplanationBlockFactory(
|
ExplanationBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
|
replace_whitespace(
|
||||||
"""
|
"""
|
||||||
Erläutere die Kundensituation und die Ausgangslage.
|
Erläutere die Kundensituation und die Ausgangslage.
|
||||||
* Hast du alle Informationen, die du für den Policen-Check benötigst?
|
<ul>
|
||||||
* Halte die wichtigsten Eckwerte des aktuellen Versicherungsverhältnisse in deiner Dokumentation fest (z.B wie lang wo versichert, Alter des Fahrzeugs, Kundenprofil, etc.)
|
<li>Hast du alle Informationen, die du für den Policen-Check benötigst?</li>
|
||||||
|
<li>Halte die wichtigsten Eckwerte des aktuellen Versicherungsverhältnisse in deiner Dokumentation fest (z.B wie lang wo versichert, Alter des Fahrzeugs, Kundenprofil, etc.</li>
|
||||||
|
</ul>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
("user_text_input", UserTextInputBlockFactory()),
|
("user_text_input", UserTextInputBlockFactory()),
|
||||||
|
|
@ -116,10 +313,8 @@ def create_uk_assignments(course_id=COURSE_UK):
|
||||||
(
|
(
|
||||||
"explanation",
|
"explanation",
|
||||||
ExplanationBlockFactory(
|
ExplanationBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Zeige nun detailliert auf, wie dein Kundenbeispiel momentan versichert ist."
|
||||||
Zeige nun detailliert auf, wie dein Kundenbeispiel momentan versichert ist.
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -141,40 +336,32 @@ def create_uk_assignments(course_id=COURSE_UK):
|
||||||
(
|
(
|
||||||
"explanation",
|
"explanation",
|
||||||
ExplanationBlockFactory(
|
ExplanationBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Erarbeite nun basierend auf deinen Erkenntnissen eine Empfehlung für die Person."
|
||||||
Erarbeite nun basierend auf deinen Erkenntnissen eine Empfehlung für die Person.
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest? Begründe deine Empfehlung"
|
||||||
Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest? Begründe deine Empfehlung
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Gibt es Deckungen, die du streichen würdest? Begründe deine Empfehlung."
|
||||||
Gibt es Deckungen, die du streichen würdest? Begründe deine Empfehlung.
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Wenn die Person gemäss deiner Einschätzung genau richtig versichert ist, argumentiere, warum dies der Fall ist."
|
||||||
Wenn die Person gemäss deiner Einschätzung genau richtig versichert ist, argumentiere, warum dies der Fall ist.
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -195,40 +382,32 @@ def create_uk_assignments(course_id=COURSE_UK):
|
||||||
(
|
(
|
||||||
"explanation",
|
"explanation",
|
||||||
ExplanationBlockFactory(
|
ExplanationBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Reflektiere dein Handeln und halte deine Erkenntnisse fest. Frage dich dabei:"
|
||||||
Reflektiere dein Handeln und halte deine Erkenntnisse fest. Frage dich dabei:
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"War die Bearbeitung dieser geleiteten Fallarbeit erfolgreich? Begründe deine Einschätzung."
|
||||||
War die Bearbeitung dieser geleiteten Fallarbeit erfolgreich? Begründe deine Einschätzung.
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Was ist dir bei der Bearbeitung des Auftrags gut gelungen?"
|
||||||
Was ist dir bei der Bearbeitung des Auftrags gut gelungen?
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Was ist dir bei der Bearbeitung des Auftrags weniger gut gelungen?"
|
||||||
Was ist dir bei der Bearbeitung des Auftrags weniger gut gelungen?
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -249,30 +428,24 @@ def create_uk_assignments(course_id=COURSE_UK):
|
||||||
(
|
(
|
||||||
"explanation",
|
"explanation",
|
||||||
ExplanationBlockFactory(
|
ExplanationBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Leite aus der Teilaufgabe 5 deine persönlichen Learnings ab."
|
||||||
Leite aus der Teilaufgabe 5 deine persönlichen Learnings ab.
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Was würdest du beim nächsten Mal anders machen?"
|
||||||
Was würdest du beim nächsten Mal anders machen?
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Was hast du beim Bearbeiten des Auftrags Neues gelernt?"
|
||||||
Was hast du beim Bearbeiten des Auftrags Neues gelernt?
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -318,8 +491,8 @@ def create_test_assignment(course_id=COURSE_TEST_ID):
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
assessment_document_url="https://www.vbv.ch",
|
evaluation_document_url="https://www.vbv.ch",
|
||||||
assessment_description="Diese geleitete Fallarbeit wird auf Grund des folgenden Beurteilungsintrument bewertet.",
|
evaluation_description="Diese geleitete Fallarbeit wird auf Grund des folgenden Beurteilungsintrument bewertet.",
|
||||||
)
|
)
|
||||||
|
|
||||||
assignment.tasks = []
|
assignment.tasks = []
|
||||||
|
|
@ -335,12 +508,16 @@ def create_test_assignment(course_id=COURSE_TEST_ID):
|
||||||
stream_data=[
|
stream_data=[
|
||||||
(
|
(
|
||||||
"explanation",
|
"explanation",
|
||||||
ExplanationBlockFactory(text="Dies ist ein Beispieltext."),
|
ExplanationBlockFactory(
|
||||||
|
text=RichText("Dies ist ein Beispieltext.")
|
||||||
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_confirmation",
|
"user_confirmation",
|
||||||
ExplanationBlockFactory(
|
ExplanationBlockFactory(
|
||||||
text="Ja, ich habe Motorfahrzeugversicherungspolice von jemandem aus meiner Familie oder meinem Freundeskreis erhalten."
|
text=RichText(
|
||||||
|
"Ja, ich habe Motorfahrzeugversicherungspolice von jemandem aus meiner Familie oder meinem Freundeskreis erhalten."
|
||||||
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -360,13 +537,17 @@ def create_test_assignment(course_id=COURSE_TEST_ID):
|
||||||
(
|
(
|
||||||
"explanation",
|
"explanation",
|
||||||
ExplanationBlockFactory(
|
ExplanationBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
|
replace_whitespace(
|
||||||
"""
|
"""
|
||||||
Erläutere die Kundensituation und die Ausgangslage.
|
Erläutere die Kundensituation und die Ausgangslage.
|
||||||
* Hast du alle Informationen, die du für den Policen-Check benötigst?
|
<ul>
|
||||||
* Halte die wichtigsten Eckwerte des aktuellen Versicherungsverhältnisse in deiner Dokumentation fest (z.B wie lang wo versichert, Alter des Fahrzeugs, Kundenprofil, etc.)
|
<li>Hast du alle Informationen, die du für den Policen-Check benötigst?</li>
|
||||||
|
<li>Halte die wichtigsten Eckwerte des aktuellen Versicherungsverhältnisse in deiner Dokumentation fest (z.B wie lang wo versichert, Alter des Fahrzeugs, Kundenprofil, etc.</li>
|
||||||
|
</ul>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
("user_text_input", UserTextInputBlockFactory()),
|
("user_text_input", UserTextInputBlockFactory()),
|
||||||
|
|
@ -388,10 +569,8 @@ def create_test_assignment(course_id=COURSE_TEST_ID):
|
||||||
(
|
(
|
||||||
"explanation",
|
"explanation",
|
||||||
ExplanationBlockFactory(
|
ExplanationBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Zeige nun detailliert auf, wie dein Kundenbeispiel momentan versichert ist."
|
||||||
Zeige nun detailliert auf, wie dein Kundenbeispiel momentan versichert ist.
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -413,40 +592,32 @@ def create_test_assignment(course_id=COURSE_TEST_ID):
|
||||||
(
|
(
|
||||||
"explanation",
|
"explanation",
|
||||||
ExplanationBlockFactory(
|
ExplanationBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Erarbeite nun basierend auf deinen Erkenntnissen eine Empfehlung für die Person."
|
||||||
Erarbeite nun basierend auf deinen Erkenntnissen eine Empfehlung für die Person.
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest? Begründe deine Empfehlung"
|
||||||
Gibt es zusätzliche Deckungen, die du der Person empfehlen würdest? Begründe deine Empfehlung
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Gibt es Deckungen, die du streichen würdest? Begründe deine Empfehlung."
|
||||||
Gibt es Deckungen, die du streichen würdest? Begründe deine Empfehlung.
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Wenn die Person gemäss deiner Einschätzung genau richtig versichert ist, argumentiere, warum dies der Fall ist."
|
||||||
Wenn die Person gemäss deiner Einschätzung genau richtig versichert ist, argumentiere, warum dies der Fall ist.
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -467,40 +638,32 @@ def create_test_assignment(course_id=COURSE_TEST_ID):
|
||||||
(
|
(
|
||||||
"explanation",
|
"explanation",
|
||||||
ExplanationBlockFactory(
|
ExplanationBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Reflektiere dein Handeln und halte deine Erkenntnisse fest. Frage dich dabei:"
|
||||||
Reflektiere dein Handeln und halte deine Erkenntnisse fest. Frage dich dabei:
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"War die Bearbeitung dieser geleiteten Fallarbeit erfolgreich? Begründe deine Einschätzung."
|
||||||
War die Bearbeitung dieser geleiteten Fallarbeit erfolgreich? Begründe deine Einschätzung.
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Was ist dir bei der Bearbeitung des Auftrags gut gelungen?"
|
||||||
Was ist dir bei der Bearbeitung des Auftrags gut gelungen?
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Was ist dir bei der Bearbeitung des Auftrags weniger gut gelungen?"
|
||||||
Was ist dir bei der Bearbeitung des Auftrags weniger gut gelungen?
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -521,30 +684,24 @@ def create_test_assignment(course_id=COURSE_TEST_ID):
|
||||||
(
|
(
|
||||||
"explanation",
|
"explanation",
|
||||||
ExplanationBlockFactory(
|
ExplanationBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Leite aus der Teilaufgabe 5 deine persönlichen Learnings ab."
|
||||||
Leite aus der Teilaufgabe 5 deine persönlichen Learnings ab.
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Was würdest du beim nächsten Mal anders machen?"
|
||||||
Was würdest du beim nächsten Mal anders machen?
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
UserTextInputBlockFactory(
|
UserTextInputBlockFactory(
|
||||||
text=replace_whitespace(
|
text=RichText(
|
||||||
"""
|
"Was hast du beim Bearbeiten des Auftrags Neues gelernt?"
|
||||||
Was hast du beim Bearbeiten des Auftrags Neues gelernt?
|
|
||||||
"""
|
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -554,6 +711,185 @@ def create_test_assignment(course_id=COURSE_TEST_ID):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assignment.evaluation_tasks = []
|
||||||
|
assignment.evaluation_tasks.append(
|
||||||
|
(
|
||||||
|
"task",
|
||||||
|
EvaluationTaskBlockFactory(
|
||||||
|
title="Ausgangslage des Auftrags",
|
||||||
|
description=RichText(
|
||||||
|
"Beschreibt der/die Lernende die Ausgangslage des Auftrags vollständig?"
|
||||||
|
),
|
||||||
|
max_points=6,
|
||||||
|
sub_tasks=ListValue(
|
||||||
|
ListBlock(EvaluationSubTaskBlock()),
|
||||||
|
values=[
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Ausgangslage des Auftrag ist vollständig beschrieben.",
|
||||||
|
description=RichText(
|
||||||
|
replace_whitespace(
|
||||||
|
"""
|
||||||
|
<ul>
|
||||||
|
<li>Worum geht es? Was ist die Aufgabe?</li>
|
||||||
|
<li>Sind das Kundenprofil und die Kundenbeziehung vollständig und nachvollziehbar dargestellt?</li>
|
||||||
|
<li>Ist das Alter des Fahrzeugs dokumentiert?</li>
|
||||||
|
<li>Welche Ressourcen stehen zur Verfügung?</li>
|
||||||
|
</ul>
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
),
|
||||||
|
points=6,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Ausgangslage ist grösstenteils vollständig beschrieben.",
|
||||||
|
points=4,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Ausgangslage ist unvollständig - nur 2 Punkte wurden beschrieben.",
|
||||||
|
points=2,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Ausgangslage des Auftrag ist unvollständig - es fehlen mehr als 2 Punkte in der Beschreibung.",
|
||||||
|
points=0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assignment.evaluation_tasks.append(
|
||||||
|
(
|
||||||
|
"task",
|
||||||
|
EvaluationTaskBlockFactory(
|
||||||
|
title="Inhaltsanalyse und Struktur",
|
||||||
|
max_points=6,
|
||||||
|
description=RichText(
|
||||||
|
"Sind die Deckungen der Police vollständig und nachvollziehbar dokumentiert?"
|
||||||
|
),
|
||||||
|
sub_tasks=ListValue(
|
||||||
|
ListBlock(EvaluationSubTaskBlock()),
|
||||||
|
values=[
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Analyse beinhaltet alle in der Police vorhandenen Deckungen und ist logisch aufgebaut.",
|
||||||
|
points=6,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Analyse beinhaltet die meisten vorhandenen Deckungen in der Police und ist grösstenteils logisch aufgebaut.",
|
||||||
|
points=4,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Analyse ist unvollständig (es fehlen mehr als 3 Deckungen) und der rote Faden ist nicht erkennbar.",
|
||||||
|
points=2,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Analyse ist insgesamt nicht nachvollziehbar und es fehlen einige Deckungen.",
|
||||||
|
points=0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assignment.evaluation_tasks.append(
|
||||||
|
(
|
||||||
|
"task",
|
||||||
|
EvaluationTaskBlockFactory(
|
||||||
|
title="Sinnvolle Empfehlungen",
|
||||||
|
max_points=6,
|
||||||
|
description=RichText(
|
||||||
|
"Leitet die lernende Person sinnvolle und geeignete Empfehlungen ab?"
|
||||||
|
),
|
||||||
|
sub_tasks=ListValue(
|
||||||
|
ListBlock(EvaluationSubTaskBlock()),
|
||||||
|
values=[
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Empfehlungen sind durchgängig sinnvoll und nachvollziehbar begründet.",
|
||||||
|
points=6,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Empfehlungen sind grösstenteils sinnvoll und nachvollziehbar begründet.",
|
||||||
|
points=4,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Empfehlungen sind wenig sinnvoll und unvollständig begründet.",
|
||||||
|
points=2,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Empfehlungen sind weder sinnvoll nch nachvollziehbar begründet.",
|
||||||
|
points=0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assignment.evaluation_tasks.append(
|
||||||
|
(
|
||||||
|
"task",
|
||||||
|
EvaluationTaskBlockFactory(
|
||||||
|
title="Qualität der Reflexion",
|
||||||
|
max_points=3,
|
||||||
|
description=RichText(
|
||||||
|
"Reflektiert die lernende Person die Durchführung der geleiteten Fallarbeit?"
|
||||||
|
),
|
||||||
|
sub_tasks=ListValue(
|
||||||
|
ListBlock(EvaluationSubTaskBlock()),
|
||||||
|
values=[
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Reflexion bezieht sich auf die geleitete Fallarbeit und umfasst nachvollziehbare positive wie negative Aspekte.",
|
||||||
|
points=3,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Reflexion bezieht sich auf die geleitete Fallarbeit und umfasst grösstenteils nachvollziehbare positive wie negative Aspekte.",
|
||||||
|
points=2,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Reflexion ist unvollständig.",
|
||||||
|
points=1,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Reflexion bezieht sich nicht auf die geleitete Fallarbeit.",
|
||||||
|
points=0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assignment.evaluation_tasks.append(
|
||||||
|
(
|
||||||
|
"task",
|
||||||
|
EvaluationTaskBlockFactory(
|
||||||
|
title="Eignung der Learnings",
|
||||||
|
max_points=3,
|
||||||
|
description=RichText(
|
||||||
|
"Leitet die lernende Person geeignete Learnings aus der Reflexion ab?"
|
||||||
|
),
|
||||||
|
sub_tasks=ListValue(
|
||||||
|
ListBlock(EvaluationSubTaskBlock()),
|
||||||
|
values=[
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Learnings beziehen sich auf die geleitete Fallarbeit und sind inhaltlich sinnvoll.",
|
||||||
|
points=3,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Learnings beziehen sich grösstenteils auf die geleitete Fallarbeit und sind inhaltlich sinnvoll.",
|
||||||
|
points=2,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Learnings beziehen sich teilweise auf die geleitete Fallarbeit und sind inhaltlich wenig sinnvoll.",
|
||||||
|
points=1,
|
||||||
|
),
|
||||||
|
EvaluationSubTaskBlockFactory(
|
||||||
|
title="Die Learnings beziehen sich nicht auf die geleitete Fallarbeit.",
|
||||||
|
points=0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
assignment.save()
|
assignment.save()
|
||||||
|
|
||||||
return assignment
|
return assignment
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,20 @@
|
||||||
# Generated by Django 3.2.13 on 2023-04-11 09:30
|
# Generated by Django 3.2.13 on 2023-05-01 15:43
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import wagtail.blocks
|
import wagtail.blocks
|
||||||
import wagtail.fields
|
import wagtail.fields
|
||||||
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
import vbv_lernwelt.assignment.models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("wagtailcore", "0069_log_entry_jsonfield"),
|
("course", "0006_alter_coursesession_attendance_days"),
|
||||||
|
("wagtailcore", "0083_workflowcontenttype"),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
|
@ -33,7 +34,9 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"starting_position",
|
"starting_position",
|
||||||
models.TextField(help_text="Erläuterung der Ausgangslage"),
|
wagtail.fields.RichTextField(
|
||||||
|
help_text="Erläuterung der Ausgangslage"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"effort_required",
|
"effort_required",
|
||||||
|
|
@ -57,20 +60,6 @@ class Migration(migrations.Migration):
|
||||||
use_json_field=True,
|
use_json_field=True,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
|
||||||
"assessment_description",
|
|
||||||
models.TextField(
|
|
||||||
blank=True, help_text="Beschreibung der Bewertung"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"assessment_document_url",
|
|
||||||
models.CharField(
|
|
||||||
blank=True,
|
|
||||||
help_text="URL zum Beurteilungsinstrument",
|
|
||||||
max_length=255,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
"tasks",
|
"tasks",
|
||||||
wagtail.fields.StreamField(
|
wagtail.fields.StreamField(
|
||||||
|
|
@ -94,14 +83,35 @@ class Migration(migrations.Migration):
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"text",
|
"text",
|
||||||
wagtail.blocks.TextBlock(),
|
wagtail.blocks.RichTextBlock(
|
||||||
|
features=[
|
||||||
|
"ul",
|
||||||
|
"bold",
|
||||||
|
"italic",
|
||||||
|
]
|
||||||
|
),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_text_input",
|
"user_text_input",
|
||||||
vbv_lernwelt.assignment.models.UserTextInputBlock(),
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"text",
|
||||||
|
wagtail.blocks.RichTextBlock(
|
||||||
|
blank=True,
|
||||||
|
features=[
|
||||||
|
"ul",
|
||||||
|
"bold",
|
||||||
|
"italic",
|
||||||
|
],
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"user_confirmation",
|
"user_confirmation",
|
||||||
|
|
@ -109,7 +119,13 @@ class Migration(migrations.Migration):
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"text",
|
"text",
|
||||||
wagtail.blocks.TextBlock(),
|
wagtail.blocks.RichTextBlock(
|
||||||
|
features=[
|
||||||
|
"ul",
|
||||||
|
"bold",
|
||||||
|
"italic",
|
||||||
|
]
|
||||||
|
),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
@ -127,6 +143,78 @@ class Migration(migrations.Migration):
|
||||||
use_json_field=True,
|
use_json_field=True,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"evaluation_description",
|
||||||
|
wagtail.fields.RichTextField(
|
||||||
|
blank=True, help_text="Beschreibung der Bewertung"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"evaluation_document_url",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="URL zum Beurteilungsinstrument",
|
||||||
|
max_length=255,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"evaluation_tasks",
|
||||||
|
wagtail.fields.StreamField(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"task",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("title", wagtail.blocks.TextBlock()),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
wagtail.blocks.RichTextBlock(
|
||||||
|
blank=True,
|
||||||
|
features=["ul", "bold", "italic"],
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("max_points", wagtail.blocks.IntegerBlock()),
|
||||||
|
(
|
||||||
|
"sub_tasks",
|
||||||
|
wagtail.blocks.ListBlock(
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"title",
|
||||||
|
wagtail.blocks.TextBlock(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
wagtail.blocks.RichTextBlock(
|
||||||
|
blank=True,
|
||||||
|
features=[
|
||||||
|
"ul",
|
||||||
|
"bold",
|
||||||
|
"italic",
|
||||||
|
],
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"points",
|
||||||
|
wagtail.blocks.IntegerBlock(),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
blank=True,
|
||||||
|
use_json_field=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
blank=True,
|
||||||
|
help_text="Beurteilungsschritte",
|
||||||
|
use_json_field=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
"verbose_name": "Auftrag",
|
"verbose_name": "Auftrag",
|
||||||
|
|
@ -153,4 +241,148 @@ class Migration(migrations.Migration):
|
||||||
},
|
},
|
||||||
bases=("wagtailcore.page",),
|
bases=("wagtailcore.page",),
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="AssignmentCompletionAuditLog",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
(
|
||||||
|
"completion_status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
(1, "in_progress"),
|
||||||
|
(2, "submitted"),
|
||||||
|
(3, "evaluation_in_progress"),
|
||||||
|
(4, "evaluated"),
|
||||||
|
],
|
||||||
|
default="in_progress",
|
||||||
|
max_length=255,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("completion_data", models.JSONField(default=dict)),
|
||||||
|
("additional_json_data", models.JSONField(default=dict)),
|
||||||
|
("assignment_user_email", models.CharField(max_length=255)),
|
||||||
|
("assignment_slug", models.CharField(max_length=255)),
|
||||||
|
(
|
||||||
|
"evaluation_user_email",
|
||||||
|
models.CharField(blank=True, default="", max_length=255),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"assignment",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="+",
|
||||||
|
to="assignment.assignment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"assignment_user",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="+",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"course_session",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="+",
|
||||||
|
to="course.coursesession",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"evaluation_user",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="+",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="AssignmentCompletion",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
("submitted_at", models.DateTimeField(blank=True, null=True)),
|
||||||
|
("evaluated_at", models.DateTimeField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"completion_status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
(1, "in_progress"),
|
||||||
|
(2, "submitted"),
|
||||||
|
(3, "evaluation_in_progress"),
|
||||||
|
(4, "evaluated"),
|
||||||
|
],
|
||||||
|
default="in_progress",
|
||||||
|
max_length=255,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("completion_data", models.JSONField(default=dict)),
|
||||||
|
("additional_json_data", models.JSONField(default=dict)),
|
||||||
|
(
|
||||||
|
"assignment",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="assignment.assignment",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"assignment_user",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"course_session",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="course.coursesession",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"evaluation_user",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="+",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="assignmentcompletion",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("assignment_user", "assignment", "course_session"),
|
||||||
|
name="assignment_completion_unique_user_assignment_course_session",
|
||||||
|
),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,161 +0,0 @@
|
||||||
# Generated by Django 3.2.13 on 2023-04-25 06:49
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("course", "0004_coursesession_assignment_details_list"),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
("assignment", "0001_initial"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="AssignmentCompletionAuditLog",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
||||||
(
|
|
||||||
"completion_status",
|
|
||||||
models.CharField(
|
|
||||||
choices=[
|
|
||||||
(1, "in_progress"),
|
|
||||||
(2, "submitted"),
|
|
||||||
(3, "grading_in_progress"),
|
|
||||||
(4, "graded"),
|
|
||||||
],
|
|
||||||
default="in_progress",
|
|
||||||
max_length=255,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("completion_data", models.JSONField(default=dict)),
|
|
||||||
("additional_json_data", models.JSONField(default=dict)),
|
|
||||||
("assignment_user_email", models.CharField(max_length=255)),
|
|
||||||
("assignment_slug", models.CharField(max_length=255)),
|
|
||||||
(
|
|
||||||
"grading_user_email",
|
|
||||||
models.CharField(blank=True, default="", max_length=255),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"assignment",
|
|
||||||
models.ForeignKey(
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.SET_NULL,
|
|
||||||
related_name="+",
|
|
||||||
to="assignment.assignment",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"assignment_user",
|
|
||||||
models.ForeignKey(
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.SET_NULL,
|
|
||||||
related_name="+",
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"course_session",
|
|
||||||
models.ForeignKey(
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.SET_NULL,
|
|
||||||
related_name="+",
|
|
||||||
to="course.coursesession",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"grading_user",
|
|
||||||
models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.SET_NULL,
|
|
||||||
related_name="+",
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="AssignmentCompletion",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
||||||
("updated_at", models.DateTimeField(auto_now=True)),
|
|
||||||
("submitted_at", models.DateTimeField(blank=True, null=True)),
|
|
||||||
("graded_at", models.DateTimeField(blank=True, null=True)),
|
|
||||||
(
|
|
||||||
"completion_status",
|
|
||||||
models.CharField(
|
|
||||||
choices=[
|
|
||||||
(1, "in_progress"),
|
|
||||||
(2, "submitted"),
|
|
||||||
(3, "grading_in_progress"),
|
|
||||||
(4, "graded"),
|
|
||||||
],
|
|
||||||
default="in_progress",
|
|
||||||
max_length=255,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("completion_data", models.JSONField(default=dict)),
|
|
||||||
("additional_json_data", models.JSONField(default=dict)),
|
|
||||||
(
|
|
||||||
"assignment",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="assignment.assignment",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"assignment_user",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"course_session",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to="course.coursesession",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"grading_user",
|
|
||||||
models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="+",
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AddConstraint(
|
|
||||||
model_name="assignmentcompletion",
|
|
||||||
constraint=models.UniqueConstraint(
|
|
||||||
fields=("assignment_user", "assignment", "course_session"),
|
|
||||||
name="assignment_completion_unique_user_assignment_course_session",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -5,9 +5,10 @@ from django.db.models import UniqueConstraint
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
from wagtail import blocks
|
from wagtail import blocks
|
||||||
from wagtail.admin.panels import FieldPanel
|
from wagtail.admin.panels import FieldPanel
|
||||||
from wagtail.fields import StreamField
|
from wagtail.fields import RichTextField, StreamField
|
||||||
from wagtail.models import Page
|
from wagtail.models import Page
|
||||||
|
|
||||||
|
from vbv_lernwelt.core.constants import DEFAULT_RICH_TEXT_FEATURES
|
||||||
from vbv_lernwelt.core.model_utils import find_available_slug
|
from vbv_lernwelt.core.model_utils import find_available_slug
|
||||||
from vbv_lernwelt.core.models import User
|
from vbv_lernwelt.core.models import User
|
||||||
from vbv_lernwelt.course.models import CourseBasePage
|
from vbv_lernwelt.course.models import CourseBasePage
|
||||||
|
|
@ -28,12 +29,8 @@ class AssignmentListPage(CourseBasePage):
|
||||||
return f"{self.title}"
|
return f"{self.title}"
|
||||||
|
|
||||||
|
|
||||||
# class AssignmentSubmission(modModel):
|
|
||||||
# created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ExplanationBlock(blocks.StructBlock):
|
class ExplanationBlock(blocks.StructBlock):
|
||||||
text = blocks.TextBlock()
|
text = blocks.RichTextBlock(features=DEFAULT_RICH_TEXT_FEATURES)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
icon = "comment"
|
icon = "comment"
|
||||||
|
|
@ -47,14 +44,16 @@ class PerformanceObjectiveBlock(blocks.StructBlock):
|
||||||
|
|
||||||
|
|
||||||
class UserTextInputBlock(blocks.StructBlock):
|
class UserTextInputBlock(blocks.StructBlock):
|
||||||
text = blocks.TextBlock(blank=True)
|
text = blocks.RichTextBlock(
|
||||||
|
blank=True, required=False, features=DEFAULT_RICH_TEXT_FEATURES
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
icon = "edit"
|
icon = "edit"
|
||||||
|
|
||||||
|
|
||||||
class UserConfirmationBlock(blocks.StructBlock):
|
class UserConfirmationBlock(blocks.StructBlock):
|
||||||
text = blocks.TextBlock()
|
text = blocks.RichTextBlock(features=DEFAULT_RICH_TEXT_FEATURES)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
icon = "tick-inverse"
|
icon = "tick-inverse"
|
||||||
|
|
@ -78,17 +77,47 @@ class TaskBlock(blocks.StructBlock):
|
||||||
label = "Teilauftrag"
|
label = "Teilauftrag"
|
||||||
|
|
||||||
|
|
||||||
|
class EvaluationSubTaskBlock(blocks.StructBlock):
|
||||||
|
title = blocks.TextBlock()
|
||||||
|
description = blocks.RichTextBlock(
|
||||||
|
blank=True, required=False, features=DEFAULT_RICH_TEXT_FEATURES
|
||||||
|
)
|
||||||
|
points = blocks.IntegerBlock()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
icon = "tick"
|
||||||
|
label = "Beurteilung"
|
||||||
|
|
||||||
|
|
||||||
|
class EvaluationTaskBlock(blocks.StructBlock):
|
||||||
|
title = blocks.TextBlock()
|
||||||
|
description = blocks.RichTextBlock(
|
||||||
|
blank=True, required=False, features=DEFAULT_RICH_TEXT_FEATURES
|
||||||
|
)
|
||||||
|
max_points = blocks.IntegerBlock()
|
||||||
|
sub_tasks = blocks.ListBlock(
|
||||||
|
EvaluationSubTaskBlock(), blank=True, use_json_field=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
icon = "tasks"
|
||||||
|
label = "Beurteilungskriterium"
|
||||||
|
|
||||||
|
|
||||||
class Assignment(CourseBasePage):
|
class Assignment(CourseBasePage):
|
||||||
serialize_field_names = [
|
serialize_field_names = [
|
||||||
"starting_position",
|
"starting_position",
|
||||||
"effort_required",
|
"effort_required",
|
||||||
"performance_objectives",
|
"performance_objectives",
|
||||||
"assessment_description",
|
"evaluation_description",
|
||||||
"assessment_document_url",
|
"evaluation_document_url",
|
||||||
"tasks",
|
"tasks",
|
||||||
|
"evaluation_tasks",
|
||||||
]
|
]
|
||||||
|
|
||||||
starting_position = models.TextField(help_text="Erläuterung der Ausgangslage")
|
starting_position = RichTextField(
|
||||||
|
help_text="Erläuterung der Ausgangslage", features=DEFAULT_RICH_TEXT_FEATURES
|
||||||
|
)
|
||||||
effort_required = models.CharField(
|
effort_required = models.CharField(
|
||||||
max_length=100, help_text="Zeitaufwand als Text", blank=True
|
max_length=100, help_text="Zeitaufwand als Text", blank=True
|
||||||
)
|
)
|
||||||
|
|
@ -101,14 +130,6 @@ class Assignment(CourseBasePage):
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="Leistungsziele des Auftrags",
|
help_text="Leistungsziele des Auftrags",
|
||||||
)
|
)
|
||||||
assessment_description = models.TextField(
|
|
||||||
blank=True, help_text="Beschreibung der Bewertung"
|
|
||||||
)
|
|
||||||
assessment_document_url = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
blank=True,
|
|
||||||
help_text="URL zum Beurteilungsinstrument",
|
|
||||||
)
|
|
||||||
|
|
||||||
tasks = StreamField(
|
tasks = StreamField(
|
||||||
[
|
[
|
||||||
|
|
@ -119,13 +140,34 @@ class Assignment(CourseBasePage):
|
||||||
help_text="Teilaufgaben",
|
help_text="Teilaufgaben",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
evaluation_description = RichTextField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Beschreibung der Bewertung",
|
||||||
|
features=DEFAULT_RICH_TEXT_FEATURES,
|
||||||
|
)
|
||||||
|
evaluation_document_url = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
blank=True,
|
||||||
|
help_text="URL zum Beurteilungsinstrument",
|
||||||
|
)
|
||||||
|
|
||||||
|
evaluation_tasks = StreamField(
|
||||||
|
[
|
||||||
|
("task", EvaluationTaskBlock()),
|
||||||
|
],
|
||||||
|
use_json_field=True,
|
||||||
|
blank=True,
|
||||||
|
help_text="Beurteilungsschritte",
|
||||||
|
)
|
||||||
|
|
||||||
content_panels = Page.content_panels + [
|
content_panels = Page.content_panels + [
|
||||||
FieldPanel("starting_position"),
|
FieldPanel("starting_position"),
|
||||||
FieldPanel("effort_required"),
|
FieldPanel("effort_required"),
|
||||||
FieldPanel("performance_objectives"),
|
FieldPanel("performance_objectives"),
|
||||||
FieldPanel("assessment_description"),
|
|
||||||
FieldPanel("assessment_document_url"),
|
|
||||||
FieldPanel("tasks"),
|
FieldPanel("tasks"),
|
||||||
|
FieldPanel("evaluation_description"),
|
||||||
|
FieldPanel("evaluation_document_url"),
|
||||||
|
FieldPanel("evaluation_tasks"),
|
||||||
]
|
]
|
||||||
|
|
||||||
subpage_types = []
|
subpage_types = []
|
||||||
|
|
@ -172,10 +214,13 @@ class Assignment(CourseBasePage):
|
||||||
if sub_dict["type"] in subtask_types
|
if sub_dict["type"] in subtask_types
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def filter_evaluation_tasks(self):
|
||||||
|
return self.evaluation_tasks.raw_data
|
||||||
|
|
||||||
|
|
||||||
AssignmentCompletionStatus = Enum(
|
AssignmentCompletionStatus = Enum(
|
||||||
"AssignmentCompletionStatus",
|
"AssignmentCompletionStatus",
|
||||||
["in_progress", "submitted", "grading_in_progress", "graded"],
|
["in_progress", "submitted", "evaluation_in_progress", "evaluated"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -184,8 +229,8 @@ class AssignmentCompletion(models.Model):
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
submitted_at = models.DateTimeField(null=True, blank=True)
|
submitted_at = models.DateTimeField(null=True, blank=True)
|
||||||
graded_at = models.DateTimeField(null=True, blank=True)
|
evaluated_at = models.DateTimeField(null=True, blank=True)
|
||||||
grading_user = models.ForeignKey(
|
evaluation_user = models.ForeignKey(
|
||||||
User,
|
User,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
null=True,
|
null=True,
|
||||||
|
|
@ -217,12 +262,12 @@ class AssignmentCompletion(models.Model):
|
||||||
|
|
||||||
class AssignmentCompletionAuditLog(models.Model):
|
class AssignmentCompletionAuditLog(models.Model):
|
||||||
"""
|
"""
|
||||||
This model is used to store the "submitted" and "graded" data separately
|
This model is used to store the "submitted" and "evaluated" data separately
|
||||||
"""
|
"""
|
||||||
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
grading_user = models.ForeignKey(
|
evaluation_user = models.ForeignKey(
|
||||||
User, on_delete=models.SET_NULL, null=True, blank=True, related_name="+"
|
User, on_delete=models.SET_NULL, null=True, blank=True, related_name="+"
|
||||||
)
|
)
|
||||||
assignment_user = models.ForeignKey(
|
assignment_user = models.ForeignKey(
|
||||||
|
|
@ -246,4 +291,4 @@ class AssignmentCompletionAuditLog(models.Model):
|
||||||
|
|
||||||
assignment_user_email = models.CharField(max_length=255)
|
assignment_user_email = models.CharField(max_length=255)
|
||||||
assignment_slug = models.CharField(max_length=255)
|
assignment_slug = models.CharField(max_length=255)
|
||||||
grading_user_email = models.CharField(max_length=255, blank=True, default="")
|
evaluation_user_email = models.CharField(max_length=255, blank=True, default="")
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,12 @@ class AssignmentCompletionSerializer(serializers.ModelSerializer):
|
||||||
"created_at",
|
"created_at",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
"submitted_at",
|
"submitted_at",
|
||||||
"graded_at",
|
"evaluated_at",
|
||||||
"assignment_user",
|
"assignment_user",
|
||||||
"assignment",
|
"assignment",
|
||||||
"course_session",
|
"course_session",
|
||||||
"completion_status",
|
"completion_status",
|
||||||
"completion_data",
|
"completion_data",
|
||||||
"grading_user",
|
"evaluation_user",
|
||||||
"additional_json_data",
|
"additional_json_data",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ def update_assignment_completion(
|
||||||
course_session: CourseSession,
|
course_session: CourseSession,
|
||||||
completion_data=None,
|
completion_data=None,
|
||||||
completion_status: Type[AssignmentCompletionStatus] = "in_progress",
|
completion_status: Type[AssignmentCompletionStatus] = "in_progress",
|
||||||
grading_user: User | None = None,
|
evaluation_user: User | None = None,
|
||||||
validate_completion_status_change: bool = True,
|
validate_completion_status_change: bool = True,
|
||||||
copy_task_data: bool = False,
|
copy_task_data: bool = False,
|
||||||
) -> AssignmentCompletion:
|
) -> AssignmentCompletion:
|
||||||
|
|
@ -40,7 +40,7 @@ def update_assignment_completion(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
:param copy_task_data: if true, the task data will be copied to the completion data
|
:param copy_task_data: if true, the task data will be copied to the completion data
|
||||||
used for "submitted" and "graded" status, so that we don't lose the question
|
used for "submitted" and "evaluated" status, so that we don't lose the question
|
||||||
context
|
context
|
||||||
:return: AssignmentCompletion
|
:return: AssignmentCompletion
|
||||||
"""
|
"""
|
||||||
|
|
@ -56,31 +56,35 @@ def update_assignment_completion(
|
||||||
if validate_completion_status_change:
|
if validate_completion_status_change:
|
||||||
# TODO: check time?
|
# TODO: check time?
|
||||||
if completion_status == "submitted":
|
if completion_status == "submitted":
|
||||||
if ac.completion_status in ["submitted", "grading_in_progress", "graded"]:
|
if ac.completion_status in [
|
||||||
|
"submitted",
|
||||||
|
"evaluation_in_progress",
|
||||||
|
"evaluated",
|
||||||
|
]:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
{
|
{
|
||||||
"completion_status": f"Cannot update completion status from {ac.completion_status} to submitted"
|
"completion_status": f"Cannot update completion status from {ac.completion_status} to submitted"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
elif completion_status == "graded":
|
elif completion_status == "evaluated":
|
||||||
if ac.completion_status == "graded":
|
if ac.completion_status == "evaluated":
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
{
|
{
|
||||||
"completion_status": f"Cannot update completion status from {ac.completion_status} to graded"
|
"completion_status": f"Cannot update completion status from {ac.completion_status} to evaluated"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if completion_status in ["graded", "grading_in_progress"]:
|
if completion_status in ["evaluated", "evaluation_in_progress"]:
|
||||||
if grading_user is None:
|
if evaluation_user is None:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
{"grading_user": "grading_user is required for graded status"}
|
{"evaluation_user": "evaluation_user is required for evaluated status"}
|
||||||
)
|
)
|
||||||
ac.grading_user = grading_user
|
ac.evaluation_user = evaluation_user
|
||||||
|
|
||||||
if completion_status == "submitted":
|
if completion_status == "submitted":
|
||||||
ac.submitted_at = timezone.now()
|
ac.submitted_at = timezone.now()
|
||||||
elif completion_status == "graded":
|
elif completion_status == "evaluated":
|
||||||
ac.graded_at = timezone.now()
|
ac.evaluated_at = timezone.now()
|
||||||
|
|
||||||
ac.completion_status = completion_status
|
ac.completion_status = completion_status
|
||||||
|
|
||||||
|
|
@ -101,19 +105,19 @@ def update_assignment_completion(
|
||||||
|
|
||||||
ac.save()
|
ac.save()
|
||||||
|
|
||||||
if completion_status in ["graded", "submitted"]:
|
if completion_status in ["evaluated", "submitted"]:
|
||||||
acl = AssignmentCompletionAuditLog.objects.create(
|
acl = AssignmentCompletionAuditLog.objects.create(
|
||||||
assignment_user=assignment_user,
|
assignment_user=assignment_user,
|
||||||
assignment=assignment,
|
assignment=assignment,
|
||||||
course_session=course_session,
|
course_session=course_session,
|
||||||
grading_user=grading_user,
|
evaluation_user=evaluation_user,
|
||||||
completion_status=completion_status,
|
completion_status=completion_status,
|
||||||
assignment_user_email=assignment_user.email,
|
assignment_user_email=assignment_user.email,
|
||||||
assignment_slug=assignment.slug,
|
assignment_slug=assignment.slug,
|
||||||
completion_data=deepcopy(ac.completion_data),
|
completion_data=deepcopy(ac.completion_data),
|
||||||
)
|
)
|
||||||
if grading_user:
|
if evaluation_user:
|
||||||
acl.grading_user_email = grading_user.email
|
acl.evaluation_user_email = evaluation_user.email
|
||||||
|
|
||||||
# copy over the question data, so that we don't lose the context
|
# copy over the question data, so that we don't lose the context
|
||||||
substasks = assignment.filter_user_subtasks()
|
substasks = assignment.filter_user_subtasks()
|
||||||
|
|
@ -132,9 +136,12 @@ def _remove_unknown_entries(assignment, completion_data):
|
||||||
possible_subtask_uuids = [
|
possible_subtask_uuids = [
|
||||||
subtask["id"] for subtask in assignment.filter_user_subtasks()
|
subtask["id"] for subtask in assignment.filter_user_subtasks()
|
||||||
]
|
]
|
||||||
|
possible_evaluation_uuids = [
|
||||||
|
task["id"] for task in assignment.filter_evaluation_tasks()
|
||||||
|
]
|
||||||
filtered_completion_data = {
|
filtered_completion_data = {
|
||||||
key: value
|
key: value
|
||||||
for key, value in completion_data.items()
|
for key, value in completion_data.items()
|
||||||
if key in possible_subtask_uuids
|
if key in possible_subtask_uuids or key in possible_evaluation_uuids
|
||||||
}
|
}
|
||||||
return filtered_completion_data
|
return filtered_completion_data
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
import wagtail_factories
|
import wagtail_factories
|
||||||
from factory import SubFactory
|
from factory import SubFactory
|
||||||
|
from wagtail.rich_text import RichText
|
||||||
|
|
||||||
from vbv_lernwelt.assignment.models import (
|
from vbv_lernwelt.assignment.models import (
|
||||||
Assignment,
|
Assignment,
|
||||||
AssignmentListPage,
|
AssignmentListPage,
|
||||||
|
EvaluationSubTaskBlock,
|
||||||
|
EvaluationTaskBlock,
|
||||||
ExplanationBlock,
|
ExplanationBlock,
|
||||||
PerformanceObjectiveBlock,
|
PerformanceObjectiveBlock,
|
||||||
TaskBlock,
|
TaskBlock,
|
||||||
|
|
@ -15,14 +18,16 @@ from vbv_lernwelt.core.utils import replace_whitespace
|
||||||
|
|
||||||
|
|
||||||
class ExplanationBlockFactory(wagtail_factories.StructBlockFactory):
|
class ExplanationBlockFactory(wagtail_factories.StructBlockFactory):
|
||||||
text = "Dies ist ein Beispieltext."
|
text = RichText("Dies ist ein Beispieltext.")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ExplanationBlock
|
model = ExplanationBlock
|
||||||
|
|
||||||
|
|
||||||
class UserConfirmationBlockFactory(wagtail_factories.StructBlockFactory):
|
class UserConfirmationBlockFactory(wagtail_factories.StructBlockFactory):
|
||||||
text = "Ja, ich habe Motorfahrzeugversicherungspolice von jemandem aus meiner Familie oder meinem Freundeskreis erhalten."
|
text = RichText(
|
||||||
|
"Ja, ich habe Motorfahrzeugversicherungspolice von jemandem aus meiner Familie oder meinem Freundeskreis erhalten."
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = UserConfirmationBlock
|
model = UserConfirmationBlock
|
||||||
|
|
@ -50,6 +55,24 @@ class TaskBlockFactory(wagtail_factories.StructBlockFactory):
|
||||||
model = TaskBlock
|
model = TaskBlock
|
||||||
|
|
||||||
|
|
||||||
|
class EvaluationSubTaskBlockFactory(wagtail_factories.StructBlockFactory):
|
||||||
|
title = "Beurteilung"
|
||||||
|
description = RichText("")
|
||||||
|
points = 6
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = EvaluationSubTaskBlock
|
||||||
|
|
||||||
|
|
||||||
|
class EvaluationTaskBlockFactory(wagtail_factories.StructBlockFactory):
|
||||||
|
title = "Beurteilungskriterum"
|
||||||
|
description = RichText("")
|
||||||
|
max_points = 6
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = EvaluationTaskBlock
|
||||||
|
|
||||||
|
|
||||||
class PerformanceObjectiveBlockFactory(wagtail_factories.StructBlockFactory):
|
class PerformanceObjectiveBlockFactory(wagtail_factories.StructBlockFactory):
|
||||||
text = "Die Teilnehmer können die wichtigsten Eckwerte eines Versicherungsverhältnisses erfassen."
|
text = "Die Teilnehmer können die wichtigsten Eckwerte eines Versicherungsverhältnisses erfassen."
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ class AssignmentApiTestCase(APITestCase):
|
||||||
"assignment_id": self.assignment.id,
|
"assignment_id": self.assignment.id,
|
||||||
"assignment_user_id": self.student.id,
|
"assignment_user_id": self.student.id,
|
||||||
"course_session_id": self.cs.id,
|
"course_session_id": self.cs.id,
|
||||||
"completion_status": "grading_in_progress",
|
"completion_status": "evaluation_in_progress",
|
||||||
"completion_data": {
|
"completion_data": {
|
||||||
user_text_input["id"]: {
|
user_text_input["id"]: {
|
||||||
"expert_data": {"points": 1, "comment": "Gut gemacht!"}
|
"expert_data": {"points": 1, "comment": "Gut gemacht!"}
|
||||||
|
|
@ -196,7 +196,7 @@ class AssignmentApiTestCase(APITestCase):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response_json["assignment_user"], self.student.id)
|
self.assertEqual(response_json["assignment_user"], self.student.id)
|
||||||
self.assertEqual(response_json["assignment"], self.assignment.id)
|
self.assertEqual(response_json["assignment"], self.assignment.id)
|
||||||
self.assertEqual(response_json["completion_status"], "grading_in_progress")
|
self.assertEqual(response_json["completion_status"], "evaluation_in_progress")
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
response_json["completion_data"],
|
response_json["completion_data"],
|
||||||
{
|
{
|
||||||
|
|
@ -212,7 +212,7 @@ class AssignmentApiTestCase(APITestCase):
|
||||||
course_session_id=self.cs.id,
|
course_session_id=self.cs.id,
|
||||||
assignment_id=self.assignment.id,
|
assignment_id=self.assignment.id,
|
||||||
)
|
)
|
||||||
self.assertEqual(db_entry.completion_status, "grading_in_progress")
|
self.assertEqual(db_entry.completion_status, "evaluation_in_progress")
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
db_entry.completion_data,
|
db_entry.completion_data,
|
||||||
{
|
{
|
||||||
|
|
@ -230,7 +230,7 @@ class AssignmentApiTestCase(APITestCase):
|
||||||
"assignment_id": self.assignment.id,
|
"assignment_id": self.assignment.id,
|
||||||
"assignment_user_id": self.student.id,
|
"assignment_user_id": self.student.id,
|
||||||
"course_session_id": self.cs.id,
|
"course_session_id": self.cs.id,
|
||||||
"completion_status": "graded",
|
"completion_status": "evaluated",
|
||||||
"completion_data": {
|
"completion_data": {
|
||||||
user_text_input["id"]: {
|
user_text_input["id"]: {
|
||||||
"expert_data": {"points": 1, "comment": "Gut gemacht!"}
|
"expert_data": {"points": 1, "comment": "Gut gemacht!"}
|
||||||
|
|
@ -245,7 +245,7 @@ class AssignmentApiTestCase(APITestCase):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response_json["assignment_user"], self.student.id)
|
self.assertEqual(response_json["assignment_user"], self.student.id)
|
||||||
self.assertEqual(response_json["assignment"], self.assignment.id)
|
self.assertEqual(response_json["assignment"], self.assignment.id)
|
||||||
self.assertEqual(response_json["completion_status"], "graded")
|
self.assertEqual(response_json["completion_status"], "evaluated")
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
response_json["completion_data"],
|
response_json["completion_data"],
|
||||||
{
|
{
|
||||||
|
|
@ -261,7 +261,7 @@ class AssignmentApiTestCase(APITestCase):
|
||||||
course_session_id=self.cs.id,
|
course_session_id=self.cs.id,
|
||||||
assignment_id=self.assignment.id,
|
assignment_id=self.assignment.id,
|
||||||
)
|
)
|
||||||
self.assertEqual(db_entry.completion_status, "graded")
|
self.assertEqual(db_entry.completion_status, "evaluated")
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
db_entry.completion_data,
|
db_entry.completion_data,
|
||||||
{
|
{
|
||||||
|
|
@ -272,12 +272,12 @@ class AssignmentApiTestCase(APITestCase):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# `graded` will create a new AssignmentCompletionAuditLog
|
# `evaluated` will create a new AssignmentCompletionAuditLog
|
||||||
acl = AssignmentCompletionAuditLog.objects.get(
|
acl = AssignmentCompletionAuditLog.objects.get(
|
||||||
assignment_user=self.student,
|
assignment_user=self.student,
|
||||||
course_session_id=self.cs.id,
|
course_session_id=self.cs.id,
|
||||||
assignment_id=self.assignment.id,
|
assignment_id=self.assignment.id,
|
||||||
completion_status="graded",
|
completion_status="evaluated",
|
||||||
)
|
)
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
|
|
|
||||||
|
|
@ -296,7 +296,42 @@ class UpdateAssignmentCompletionTestCase(TestCase):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_can_add_grading_data_without_loosing_user_input_data(self):
|
def test_can_evaluate_with_evaluation_tasks(self):
|
||||||
|
ac = AssignmentCompletion.objects.create(
|
||||||
|
assignment_user=self.user,
|
||||||
|
assignment=self.assignment,
|
||||||
|
course_session=self.course_session,
|
||||||
|
completion_status="submitted",
|
||||||
|
)
|
||||||
|
|
||||||
|
evaluation_task = self.assignment.filter_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="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")
|
||||||
|
trainer_input = ac.completion_data[evaluation_task["id"]]
|
||||||
|
self.assertDictEqual(
|
||||||
|
trainer_input["expert_data"], {"points": 2, "text": "Gut gemacht!"}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_can_add_evaluation_data_without_loosing_user_input_data(self):
|
||||||
subtasks = self.assignment.filter_user_subtasks(
|
subtasks = self.assignment.filter_user_subtasks(
|
||||||
subtask_types=["user_text_input"]
|
subtask_types=["user_text_input"]
|
||||||
)
|
)
|
||||||
|
|
@ -329,8 +364,8 @@ class UpdateAssignmentCompletionTestCase(TestCase):
|
||||||
"expert_data": {"points": 1, "comment": "Gut gemacht!"}
|
"expert_data": {"points": 1, "comment": "Gut gemacht!"}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
completion_status="grading_in_progress",
|
completion_status="evaluation_in_progress",
|
||||||
grading_user=self.trainer,
|
evaluation_user=self.trainer,
|
||||||
)
|
)
|
||||||
|
|
||||||
ac = AssignmentCompletion.objects.get(
|
ac = AssignmentCompletion.objects.get(
|
||||||
|
|
@ -339,7 +374,7 @@ class UpdateAssignmentCompletionTestCase(TestCase):
|
||||||
course_session=self.course_session,
|
course_session=self.course_session,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(ac.completion_status, "grading_in_progress")
|
self.assertEqual(ac.completion_status, "evaluation_in_progress")
|
||||||
user_input = ac.completion_data[user_text_input["id"]]
|
user_input = ac.completion_data[user_text_input["id"]]
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
user_input["expert_data"], {"points": 1, "comment": "Gut gemacht!"}
|
user_input["expert_data"], {"points": 1, "comment": "Gut gemacht!"}
|
||||||
|
|
@ -348,7 +383,7 @@ class UpdateAssignmentCompletionTestCase(TestCase):
|
||||||
user_input["user_data"]["text"], "Ich würde nichts weiteres empfehlen."
|
user_input["user_data"]["text"], "Ich würde nichts weiteres empfehlen."
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_cannot_grading_data_without_grading_user(self):
|
def test_cannot_evaluate_data_without_evaluation_user(self):
|
||||||
subtasks = self.assignment.filter_user_subtasks(
|
subtasks = self.assignment.filter_user_subtasks(
|
||||||
subtask_types=["user_text_input"]
|
subtask_types=["user_text_input"]
|
||||||
)
|
)
|
||||||
|
|
@ -377,7 +412,7 @@ class UpdateAssignmentCompletionTestCase(TestCase):
|
||||||
assignment_user=self.user,
|
assignment_user=self.user,
|
||||||
assignment=self.assignment,
|
assignment=self.assignment,
|
||||||
course_session=self.course_session,
|
course_session=self.course_session,
|
||||||
completion_status="grading_in_progress",
|
completion_status="evaluation_in_progress",
|
||||||
completion_data={
|
completion_data={
|
||||||
user_text_input["id"]: {
|
user_text_input["id"]: {
|
||||||
"expert_data": {"points": 1, "comment": "Gut gemacht!"}
|
"expert_data": {"points": 1, "comment": "Gut gemacht!"}
|
||||||
|
|
@ -386,5 +421,5 @@ class UpdateAssignmentCompletionTestCase(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
"grading_user" in error.exception.detail,
|
"evaluation_user" in error.exception.detail,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,19 @@ def request_assignment_completion_for_user(
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["GET"])
|
||||||
|
def request_assignment_completion_status(request, assignment_id, course_session_id):
|
||||||
|
# TODO quickfix before GraphQL...
|
||||||
|
if is_course_session_expert(request.user, course_session_id):
|
||||||
|
qs = AssignmentCompletion.objects.filter(
|
||||||
|
course_session_id=course_session_id,
|
||||||
|
assignment_id=assignment_id,
|
||||||
|
).values("id", "assignment_user_id", "completion_status")
|
||||||
|
return Response(status=200, data=qs)
|
||||||
|
|
||||||
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
|
||||||
@api_view(["POST"])
|
@api_view(["POST"])
|
||||||
def upsert_user_assignment_completion(request):
|
def upsert_user_assignment_completion(request):
|
||||||
try:
|
try:
|
||||||
|
|
@ -107,12 +120,14 @@ def upsert_user_assignment_completion(request):
|
||||||
|
|
||||||
|
|
||||||
@api_view(["POST"])
|
@api_view(["POST"])
|
||||||
def grade_assignment_completion(request):
|
def evaluate_assignment_completion(request):
|
||||||
try:
|
try:
|
||||||
assignment_id = request.data.get("assignment_id")
|
assignment_id = request.data.get("assignment_id")
|
||||||
assignment_user_id = request.data.get("assignment_user_id")
|
assignment_user_id = request.data.get("assignment_user_id")
|
||||||
course_session_id = request.data.get("course_session_id")
|
course_session_id = request.data.get("course_session_id")
|
||||||
completion_status = request.data.get("completion_status", "grading_in_progress")
|
completion_status = request.data.get(
|
||||||
|
"completion_status", "evaluation_in_progress"
|
||||||
|
)
|
||||||
completion_data = request.data.get("completion_data", {})
|
completion_data = request.data.get("completion_data", {})
|
||||||
|
|
||||||
assignment_page = Page.objects.get(id=assignment_id)
|
assignment_page = Page.objects.get(id=assignment_id)
|
||||||
|
|
@ -133,7 +148,7 @@ def grade_assignment_completion(request):
|
||||||
completion_data=completion_data,
|
completion_data=completion_data,
|
||||||
completion_status=completion_status,
|
completion_status=completion_status,
|
||||||
copy_task_data=False,
|
copy_task_data=False,
|
||||||
grading_user=request.user,
|
evaluation_user=request.user,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
|
@ -144,7 +159,7 @@ def grade_assignment_completion(request):
|
||||||
assignment_user_id=assignment_user_id,
|
assignment_user_id=assignment_user_id,
|
||||||
course_session_id=course_session_id,
|
course_session_id=course_session_id,
|
||||||
completion_status=completion_status,
|
completion_status=completion_status,
|
||||||
grading_user_id=request.user.id,
|
evaluation_user_id=request.user.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response(status=200, data=AssignmentCompletionSerializer(ac).data)
|
return Response(status=200, data=AssignmentCompletionSerializer(ac).data)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
DEFAULT_RICH_TEXT_FEATURES = [
|
||||||
|
"ul",
|
||||||
|
"bold",
|
||||||
|
"italic",
|
||||||
|
]
|
||||||
|
|
@ -176,6 +176,7 @@ def create_default_users(user_model=User, group_model=Group, default_password=No
|
||||||
first_name="Lina",
|
first_name="Lina",
|
||||||
last_name="Egger",
|
last_name="Egger",
|
||||||
avatar_url="/static/avatars/uk1.lina.egger.jpg",
|
avatar_url="/static/avatars/uk1.lina.egger.jpg",
|
||||||
|
password="myvbv1234",
|
||||||
)
|
)
|
||||||
_create_student_user(
|
_create_student_user(
|
||||||
email="evelyn.schmid@example.com",
|
email="evelyn.schmid@example.com",
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ def command(course):
|
||||||
slug="überbetriebliche-kurse-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"
|
slug="überbetriebliche-kurse-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"
|
||||||
),
|
),
|
||||||
course_session=CourseSession.objects.get(title="Bern 2023 a"),
|
course_session=CourseSession.objects.get(title="Bern 2023 a"),
|
||||||
user=User.objects.get(email="michael.meier@example.com"),
|
user=User.objects.get(email="lina.egger@example.com"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if COURSE_UK_FR in course:
|
if COURSE_UK_FR in course:
|
||||||
|
|
@ -320,6 +320,12 @@ def create_course_uk_de_assignment_completion_data(assignment, course_session, u
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
update_assignment_completion(
|
||||||
|
assignment_user=user,
|
||||||
|
assignment=assignment,
|
||||||
|
course_session=course_session,
|
||||||
|
completion_status="submitted",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_course_uk_de_completion_data(course_session):
|
def create_course_uk_de_completion_data(course_session):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 3.2.13 on 2023-04-26 16:28
|
||||||
|
|
||||||
|
import django_jsonform.models.fields
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("course", "0005_alter_coursesession_attendance_days"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="coursesession",
|
||||||
|
name="attendance_days",
|
||||||
|
field=django_jsonform.models.fields.JSONField(blank=True, default=list),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -149,12 +149,9 @@ def get_course_sessions(request):
|
||||||
|
|
||||||
|
|
||||||
@api_view(["GET"])
|
@api_view(["GET"])
|
||||||
def get_course_session_users(request, course_slug):
|
def get_course_session_users(request, course_session_id):
|
||||||
try:
|
try:
|
||||||
course_sessions = course_sessions_for_user_qs(request.user).filter(
|
qs = CourseSessionUser.objects.filter(course_session_id=course_session_id)
|
||||||
course__slug=course_slug
|
|
||||||
)
|
|
||||||
qs = CourseSessionUser.objects.filter(course_session__in=course_sessions)
|
|
||||||
|
|
||||||
user_data = [csu.to_dict() for csu in qs]
|
user_data = [csu.to_dict() for csu in qs]
|
||||||
return Response(status=200, data=user_data)
|
return Response(status=200, data=user_data)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue