Add summary component
This commit is contained in:
parent
88848aa292
commit
44ed154814
|
|
@ -0,0 +1,77 @@
|
||||||
|
<template>
|
||||||
|
<div class="px-6 py-5 bg-white mb-4">
|
||||||
|
<h2 class="bg-feedback bg-no-repeat pl-[68px] heading-3 bg-60 leading-[60px] mb-4">{{ $t("general.feedback", 2) }}</h2>
|
||||||
|
<ol>
|
||||||
|
<ItRow
|
||||||
|
v-for="feedbacks in feedbackSummary"
|
||||||
|
:key="feedbacks.circle_id"
|
||||||
|
>
|
||||||
|
<template #firstRow><span class="text-bold">{{ $t("feedback.circleFeedback") }}</span></template>
|
||||||
|
<template #center>
|
||||||
|
<div class="flex justify-between w-full">
|
||||||
|
<div>Circle: {{ feedbacks.circle.title }}</div>
|
||||||
|
<div>{{ $t("feedback.sentByUsers", feedbacks.count) }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #link>
|
||||||
|
<router-link
|
||||||
|
:to="`${url}/cockpit/feedback/`"
|
||||||
|
class="underline w-full text-right"
|
||||||
|
>
|
||||||
|
{{ $t("feedback.showDetails") }}
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
</ItRow>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
import ItRow from "@/components/ui/ItRow.vue";
|
||||||
|
import { itGet } from "@/fetchHelpers";
|
||||||
|
|
||||||
|
import type { Circle } from "@/services/circle";
|
||||||
|
|
||||||
|
interface FeedbackSummary {
|
||||||
|
circle_id: number;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FeedbackDisplaySummary extends FeedbackSummary {
|
||||||
|
circle: Circle;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeSummary(feedbackData: FeedbackSummary[], circles: Circle[], selectedCircles: string[]) {
|
||||||
|
const summary: FeedbackDisplaySummary[] = circles.filter(circle => selectedCircles.includes(circle.translation_key))
|
||||||
|
.reduce((acc: FeedbackDisplaySummary[], circle) => {
|
||||||
|
const circleFeedbacks = feedbackData
|
||||||
|
.filter(data => data.circle_id === circle.id)
|
||||||
|
.map(data => Object.assign({}, data, { circle }));
|
||||||
|
|
||||||
|
return acc.concat(circleFeedbacks);
|
||||||
|
}, []);
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
selctedCircles: string[];
|
||||||
|
circles: Circle[];
|
||||||
|
courseId: number;
|
||||||
|
url: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const feedbackSummary = ref<FeedbackDisplaySummary[]>([]);
|
||||||
|
let feedbackData: FeedbackSummary[] = [];
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
feedbackData = await itGet(`/api/core/feedback/${props.courseId}/summary`);
|
||||||
|
feedbackSummary.value = makeSummary(feedbackData, props.circles, props.selctedCircles);
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props, () => {
|
||||||
|
if (feedbackData.length > 0 && props.circles.length > 0 && props.selctedCircles.length > 0) {
|
||||||
|
feedbackSummary.value = makeSummary(feedbackData, props.circles, props.selctedCircles);
|
||||||
|
}
|
||||||
|
}, { deep: true });
|
||||||
|
</script>
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Dialog, DialogDescription, DialogPanel, DialogTitle } from "@headlessui/vue";
|
import { Dialog, DialogDescription, DialogPanel, DialogTitle } from "@headlessui/vue";
|
||||||
import { ref } from "vue";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue: boolean;
|
modelValue: boolean;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import ItRow from "@/components/ui/ItRow.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -6,20 +8,18 @@ const props = defineProps<{
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<li
|
<ItRow>
|
||||||
class="flex flex-col justify-between border-t border-gray-500 py-4 leading-[45px] lg:flex-row"
|
<template #firstRow>
|
||||||
>
|
<img class="h-[45px] rounded-full mr-2" :src="avatarUrl" />
|
||||||
<div class="flex flex-row items-center md:w-1/4">
|
|
||||||
<img class="mr-2 h-[45px] rounded-full" :src="avatarUrl" />
|
|
||||||
<p class="text-bold lg:leading-[45px]">{{ name }}</p>
|
<p class="text-bold lg:leading-[45px]">{{ name }}</p>
|
||||||
</div>
|
</template>
|
||||||
<div class="flex flex-1 items-center">
|
<template #center>
|
||||||
<slot name="center"></slot>
|
<slot name="center"></slot>
|
||||||
</div>
|
</template>
|
||||||
<div class="flex items-center">
|
<template #link>
|
||||||
<slot name="link"></slot>
|
<slot name="link"></slot>
|
||||||
</div>
|
</template>
|
||||||
</li>
|
</ItRow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<template>
|
||||||
|
<li
|
||||||
|
class="py-4 leading-[45px] border-t border-gray-500 flex flex-col lg:flex-row justify-between"
|
||||||
|
>
|
||||||
|
<div class="flex flex-row md:w-1/4 items-center">
|
||||||
|
<slot name="firstRow"></slot>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 flex items-center">
|
||||||
|
<slot name="center"></slot>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center md:w-1/4">
|
||||||
|
<slot name="link"></slot>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -131,7 +131,10 @@
|
||||||
"completionTitle": "Schicke dein Feedback an {name}",
|
"completionTitle": "Schicke dein Feedback an {name}",
|
||||||
"completionDescription": "Dein Feedback ist anonym. Dein Vor- und Nachname werden bei deiner Trainer/-in nicht angezeigt.",
|
"completionDescription": "Dein Feedback ist anonym. Dein Vor- und Nachname werden bei deiner Trainer/-in nicht angezeigt.",
|
||||||
"sendFeedback": "Feedback abschicken",
|
"sendFeedback": "Feedback abschicken",
|
||||||
"feedbackSent": "Dein Feedback wurde abgeschickt"
|
"feedbackSent": "Dein Feedback wurde abgeschickt",
|
||||||
|
"circleFeedback": "Feedback zum Circle",
|
||||||
|
"showDetails": "Details anzeigen",
|
||||||
|
"sentByUsers": "Von {count} Teilnehmern ausgefüllt"
|
||||||
},
|
},
|
||||||
"constants": {
|
"constants": {
|
||||||
"yes": "Ja",
|
"yes": "Ja",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
|
import LearningPathDiagram from "@/components/learningPath/LearningPathDiagram.vue";
|
||||||
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
import ItPersonRow from "@/components/ui/ItPersonRow.vue";
|
||||||
import ItProgress from "@/components/ui/ItProgress.vue";
|
import ItProgress from "@/components/ui/ItProgress.vue";
|
||||||
|
import FeedbackSummary from "@/components/feedback/feedbackSummary.vue";
|
||||||
import type { LearningPath } from "@/services/learningPath";
|
import type { LearningPath } from "@/services/learningPath";
|
||||||
|
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
import { useCockpitStore } from "@/stores/cockpit";
|
||||||
|
|
@ -10,6 +11,7 @@ import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
courseSlug: string;
|
courseSlug: string;
|
||||||
|
|
@ -21,6 +23,7 @@ const userStore = useUserStore();
|
||||||
const cockpitStore = useCockpitStore();
|
const cockpitStore = useCockpitStore();
|
||||||
const competenceStore = useCompetenceStore();
|
const competenceStore = useCompetenceStore();
|
||||||
const learningPathStore = useLearningPathStore();
|
const learningPathStore = useLearningPathStore();
|
||||||
|
const courseSessionStore = useCourseSessionsStore();
|
||||||
|
|
||||||
function userCountStatus(userId: number) {
|
function userCountStatus(userId: number) {
|
||||||
return competenceStore.calcStatusCount(
|
return competenceStore.calcStatusCount(
|
||||||
|
|
@ -92,8 +95,8 @@ function setActiveClasses(translationKey: string) {
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<div class="mb-4 grid grid-rows-3 gap-4 lg:grid-cols-3 lg:grid-rows-none">
|
<div class="grid gap-4 grid-rows-2 lg:grid-rows-none lg:grid-cols-2 mb-4">
|
||||||
<div class="bg-white px-6 py-5">
|
<div class="px-6 py-5 bg-white">
|
||||||
<h1
|
<h1
|
||||||
class="heading-3 mb-4 bg-assignment bg-60 bg-no-repeat pl-[68px] leading-[60px]"
|
class="heading-3 mb-4 bg-assignment bg-60 bg-no-repeat pl-[68px] leading-[60px]"
|
||||||
>
|
>
|
||||||
|
|
@ -106,18 +109,7 @@ function setActiveClasses(translationKey: string) {
|
||||||
</div>
|
</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-feedback bg-60 bg-no-repeat pl-[68px] leading-[60px]"
|
class="bg-test bg-no-repeat pl-[68px] heading-3 bg-60 leading-[60px] mb-4"
|
||||||
>
|
|
||||||
{{ $t("general.feedback", 2) }}
|
|
||||||
</h1>
|
|
||||||
<div class="mb-4">
|
|
||||||
<ItProgress :status-count="data.transferProgress"></ItProgress>
|
|
||||||
</div>
|
|
||||||
<p>{{ $t("cockpit.feedbacksDone") }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white px-6 py-5">
|
|
||||||
<h1
|
|
||||||
class="heading-3 mb-4 bg-test bg-60 bg-no-repeat pl-[68px] leading-[60px]"
|
|
||||||
>
|
>
|
||||||
{{ $t("general.examResult", 2) }}
|
{{ $t("general.examResult", 2) }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
@ -127,6 +119,13 @@ function setActiveClasses(translationKey: string) {
|
||||||
<p>{{ $t("cockpit.examsDone") }}</p>
|
<p>{{ $t("cockpit.examsDone") }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Feedback -->
|
||||||
|
<FeedbackSummary
|
||||||
|
:selcted-circles="cockpitStore.selectedCircles || []"
|
||||||
|
:circles="learningPathStore.learningPathForUser(props.courseSlug, userStore.id)?.circles || []"
|
||||||
|
:course-id="courseSessionStore.courseSessionForRoute?.course.id"
|
||||||
|
:url="courseSessionStore.courseSessionForRoute.course_url"
|
||||||
|
></FeedbackSummary>
|
||||||
<div>
|
<div>
|
||||||
<!-- progress -->
|
<!-- progress -->
|
||||||
<div v-if="cockpitStore.courseSessionUsers?.length" class="bg-white p-6">
|
<div v-if="cockpitStore.courseSessionUsers?.length" class="bg-white p-6">
|
||||||
|
|
@ -140,7 +139,7 @@ function setActiveClasses(translationKey: string) {
|
||||||
>
|
>
|
||||||
<template #center>
|
<template #center>
|
||||||
<div class="flex flex-row items-center justify-between">
|
<div class="flex flex-row items-center justify-between">
|
||||||
<div class="flex flex-row items-center gap-4">
|
<div class="flex flex-row items-center gap-4 mr-6">
|
||||||
<div class="h-12">
|
<div class="h-12">
|
||||||
<LearningPathDiagram
|
<LearningPathDiagram
|
||||||
v-if="
|
v-if="
|
||||||
|
|
@ -198,6 +197,7 @@ function setActiveClasses(translationKey: string) {
|
||||||
<template #link>
|
<template #link>
|
||||||
<router-link
|
<router-link
|
||||||
:to="`/course/${props.courseSlug}/cockpit/profile/${csu.user_id}`"
|
:to="`/course/${props.courseSlug}/cockpit/profile/${csu.user_id}`"
|
||||||
|
class="underline w-full text-right"
|
||||||
>
|
>
|
||||||
{{ $t("general.profileLink") }}
|
{{ $t("general.profileLink") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,6 @@ 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.core.middleware.auth import django_view_authentication_exempt
|
from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
|
||||||
from vbv_lernwelt.core.views import (
|
from vbv_lernwelt.core.views import (
|
||||||
|
|
@ -35,7 +32,13 @@ from vbv_lernwelt.course.views import (
|
||||||
request_course_completion,
|
request_course_completion,
|
||||||
request_course_completion_for_user,
|
request_course_completion_for_user,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.feedback.views import get_expert_feedbacks_for_course, get_feedback_for_circle
|
from vbv_lernwelt.feedback.views import (
|
||||||
|
get_expert_feedbacks_for_course,
|
||||||
|
get_feedback_for_circle,
|
||||||
|
)
|
||||||
|
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):
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ from vbv_lernwelt.course.management.commands.create_uk_course import (
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
||||||
from vbv_lernwelt.course.services import mark_course_completion
|
from vbv_lernwelt.course.services import mark_course_completion
|
||||||
|
from vbv_lernwelt.feedback.creators.create_demo_feedback import create_feedback
|
||||||
from vbv_lernwelt.learnpath.create_vv_learning_path import create_vv_learning_path
|
from vbv_lernwelt.learnpath.create_vv_learning_path import create_vv_learning_path
|
||||||
from vbv_lernwelt.learnpath.create_vv_new_learning_path import (
|
from vbv_lernwelt.learnpath.create_vv_new_learning_path import (
|
||||||
create_vv_new_learning_path,
|
create_vv_new_learning_path,
|
||||||
|
|
@ -120,6 +121,7 @@ def command():
|
||||||
for i, circle in enumerate(circles):
|
for i, circle in enumerate(circles):
|
||||||
expert = experts[i % len(experts)]
|
expert = experts[i % len(experts)]
|
||||||
expert.expert.add(circle)
|
expert.expert.add(circle)
|
||||||
|
create_feedback(circle, cs, 10)
|
||||||
|
|
||||||
# course session Versicherungsvermittler/in (neu)
|
# course session Versicherungsvermittler/in (neu)
|
||||||
# course session Versicherungsvermittler/in
|
# course session Versicherungsvermittler/in
|
||||||
|
|
@ -192,6 +194,18 @@ def command():
|
||||||
user=User.objects.get(username="evelyn.schmid@example.com"),
|
user=User.objects.get(username="evelyn.schmid@example.com"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
create_feedback(
|
||||||
|
Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-kickoff"), cs, 10
|
||||||
|
)
|
||||||
|
create_feedback(
|
||||||
|
Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-haushalt-teil-2"),
|
||||||
|
cs,
|
||||||
|
14,
|
||||||
|
)
|
||||||
|
create_feedback(
|
||||||
|
Circle.objects.get(slug="überbetriebliche-kurse-lp-circle-basis"), cs, 12
|
||||||
|
)
|
||||||
|
|
||||||
# course session Überbetriebliche Kurse Lehrjahr 1 - Region Zürich
|
# course session Überbetriebliche Kurse Lehrjahr 1 - Region Zürich
|
||||||
cs = CourseSession.objects.create(
|
cs = CourseSession.objects.create(
|
||||||
course_id=COURSE_UK1,
|
course_id=COURSE_UK1,
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,7 @@ class DocumentUploadApiTestCase(APITestCase):
|
||||||
response = self.client.delete(f"/api/core/document/{document.id}/")
|
response = self.client.delete(f"/api/core/document/{document.id}/")
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
|
||||||
# expert cannot upload in other course
|
# expert cannot upload in other course
|
||||||
# expert cannot delete other upload
|
# expert cannot delete other upload
|
||||||
# exper cannot change course
|
# exper cannot change course
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
from vbv_lernwelt.course.models import CourseSession
|
||||||
|
from vbv_lernwelt.feedback.factories import FeedbackFactory
|
||||||
|
from vbv_lernwelt.learnpath.models import Circle
|
||||||
|
|
||||||
|
|
||||||
|
def create_feedback(circle: Circle, course_session: CourseSession, amount: int):
|
||||||
|
for _i in range(amount):
|
||||||
|
FeedbackFactory(circle=circle, course_session=course_session).save()
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from factory.django import DjangoModelFactory
|
from factory.django import DjangoModelFactory
|
||||||
|
from factory.fuzzy import FuzzyChoice, FuzzyInteger
|
||||||
|
|
||||||
from vbv_lernwelt.feedback.models import FeedbackResponse
|
from vbv_lernwelt.feedback.models import FeedbackResponse
|
||||||
|
|
||||||
|
|
@ -6,3 +7,31 @@ from vbv_lernwelt.feedback.models import FeedbackResponse
|
||||||
class FeedbackFactory(DjangoModelFactory):
|
class FeedbackFactory(DjangoModelFactory):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FeedbackResponse
|
model = FeedbackResponse
|
||||||
|
|
||||||
|
satisfaction = FuzzyInteger(1, 4)
|
||||||
|
goal_attainment = FuzzyInteger(1, 4)
|
||||||
|
proficiency = FuzzyChoice([20, 40, 60, 80])
|
||||||
|
received_materials = FuzzyChoice([True, False])
|
||||||
|
materials_rating = FuzzyInteger(1, 4)
|
||||||
|
instructor_competence = FuzzyInteger(1, 4)
|
||||||
|
instructor_respect = FuzzyInteger(1, 4)
|
||||||
|
instructor_open_feedback = FuzzyChoice(
|
||||||
|
[
|
||||||
|
"Alles gut, manchmal etwas langfädig",
|
||||||
|
"Super, bin begeistert",
|
||||||
|
"Ok, enspricht den Erwartungen",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
would_recommend = FuzzyChoice([True, False])
|
||||||
|
course_positive_feedback = FuzzyChoice(
|
||||||
|
[
|
||||||
|
"Die Präsentation war super",
|
||||||
|
"Das Beispiel mit der Katze fand ich sehr veranschaulicht!",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
course_negative_feedback = FuzzyChoice(
|
||||||
|
[
|
||||||
|
"Es wäre praktisch, Zugang zu einer FAQ zu haben.",
|
||||||
|
"Es wäre schön, mehr Videos hinzuzufügen.",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ class FeedbackApiBaseTestCase(APITestCase):
|
||||||
|
|
||||||
|
|
||||||
class FeedbackSummaryApiTestCase(FeedbackApiBaseTestCase):
|
class FeedbackSummaryApiTestCase(FeedbackApiBaseTestCase):
|
||||||
|
|
||||||
def test_can_get_feedback_summary_for_circles(self):
|
def test_can_get_feedback_summary_for_circles(self):
|
||||||
number_basis_feedback = 5
|
number_basis_feedback = 5
|
||||||
number_analyse_feedback = 10
|
number_analyse_feedback = 10
|
||||||
|
|
@ -65,18 +64,24 @@ class FeedbackSummaryApiTestCase(FeedbackApiBaseTestCase):
|
||||||
csu.expert.add(basis_circle)
|
csu.expert.add(basis_circle)
|
||||||
|
|
||||||
for i in range(number_basis_feedback):
|
for i in range(number_basis_feedback):
|
||||||
FeedbackFactory(circle=basis_circle, course_session=csu.course_session).save()
|
FeedbackFactory(
|
||||||
|
circle=basis_circle, course_session=csu.course_session
|
||||||
|
).save()
|
||||||
|
|
||||||
for i in range(number_analyse_feedback):
|
for i in range(number_analyse_feedback):
|
||||||
FeedbackFactory(circle=analyse_circle, course_session=csu.course_session).save()
|
FeedbackFactory(
|
||||||
|
circle=analyse_circle, course_session=csu.course_session
|
||||||
|
).save()
|
||||||
|
|
||||||
response = self.client.get(f"/api/core/feedback/{csu.course_session.course.id}/summary/")
|
response = self.client.get(
|
||||||
|
f"/api/core/feedback/{csu.course_session.course.id}/summary/"
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
expected = {
|
expected = [
|
||||||
analyse_circle.id: {"circle_id": analyse_circle.id, "count": number_analyse_feedback},
|
{"circle_id": basis_circle.id, "count": number_basis_feedback},
|
||||||
basis_circle.id: {"circle_id": basis_circle.id, "count": number_basis_feedback},
|
{"circle_id": analyse_circle.id, "count": number_analyse_feedback},
|
||||||
}
|
]
|
||||||
self.assertEqual(response.data, expected)
|
self.assertEqual(response.data, expected)
|
||||||
|
|
||||||
def test_can_only_see_feedback_from_own_circle(self):
|
def test_can_only_see_feedback_from_own_circle(self):
|
||||||
|
|
@ -92,17 +97,23 @@ class FeedbackSummaryApiTestCase(FeedbackApiBaseTestCase):
|
||||||
basis_circle = Circle.objects.get(slug="test-lehrgang-lp-circle-basis")
|
basis_circle = Circle.objects.get(slug="test-lehrgang-lp-circle-basis")
|
||||||
|
|
||||||
for i in range(number_basis_feedback):
|
for i in range(number_basis_feedback):
|
||||||
FeedbackFactory(circle=basis_circle, course_session=csu.course_session).save()
|
FeedbackFactory(
|
||||||
|
circle=basis_circle, course_session=csu.course_session
|
||||||
|
).save()
|
||||||
|
|
||||||
for i in range(number_analyse_feedback):
|
for i in range(number_analyse_feedback):
|
||||||
FeedbackFactory(circle=analyse_circle, course_session=csu.course_session).save()
|
FeedbackFactory(
|
||||||
|
circle=analyse_circle, course_session=csu.course_session
|
||||||
|
).save()
|
||||||
|
|
||||||
response = self.client.get(f"/api/core/feedback/{csu.course_session.course.id}/summary/")
|
response = self.client.get(
|
||||||
|
f"/api/core/feedback/{csu.course_session.course.id}/summary/"
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
expected = {
|
expected = [
|
||||||
analyse_circle.id: {"circle_id": analyse_circle.id, "count": number_analyse_feedback},
|
{"circle_id": analyse_circle.id, "count": number_analyse_feedback},
|
||||||
}
|
]
|
||||||
self.assertEqual(response.data, expected)
|
self.assertEqual(response.data, expected)
|
||||||
|
|
||||||
def test_student_does_not_see_feedback(self):
|
def test_student_does_not_see_feedback(self):
|
||||||
|
|
@ -114,10 +125,12 @@ class FeedbackSummaryApiTestCase(FeedbackApiBaseTestCase):
|
||||||
analyse_circle = Circle.objects.get(slug="test-lehrgang-lp-circle-analyse")
|
analyse_circle = Circle.objects.get(slug="test-lehrgang-lp-circle-analyse")
|
||||||
FeedbackFactory(circle=analyse_circle, course_session=csu.course_session).save()
|
FeedbackFactory(circle=analyse_circle, course_session=csu.course_session).save()
|
||||||
|
|
||||||
response = self.client.get(f"/api/core/feedback/{csu.course_session.course.id}/summary/")
|
response = self.client.get(
|
||||||
|
f"/api/core/feedback/{csu.course_session.course.id}/summary/"
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.data, {})
|
self.assertEqual(response.data, [])
|
||||||
|
|
||||||
|
|
||||||
class FeedbackDetailApiTestCase(FeedbackApiBaseTestCase):
|
class FeedbackDetailApiTestCase(FeedbackApiBaseTestCase):
|
||||||
|
|
@ -157,7 +170,9 @@ class FeedbackDetailApiTestCase(FeedbackApiBaseTestCase):
|
||||||
course_negative_feedback=expected["course_negative_feedback"][i],
|
course_negative_feedback=expected["course_negative_feedback"][i],
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
response = self.client.get(f"/api/core/feedback/{csu.course_session.course.id}/{circle.id}/")
|
response = self.client.get(
|
||||||
|
f"/api/core/feedback/{csu.course_session.course.id}/{circle.id}/"
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.data, expected)
|
self.assertEqual(response.data, expected)
|
||||||
|
|
@ -172,7 +187,9 @@ class FeedbackDetailApiTestCase(FeedbackApiBaseTestCase):
|
||||||
circle = Circle.objects.get(slug="test-lehrgang-lp-circle-basis")
|
circle = Circle.objects.get(slug="test-lehrgang-lp-circle-basis")
|
||||||
FeedbackFactory(circle=circle, course_session=csu.course_session).save()
|
FeedbackFactory(circle=circle, course_session=csu.course_session).save()
|
||||||
|
|
||||||
response = self.client.get(f"/api/core/feedback/{csu.course_session.course.id}/{circle.id}/")
|
response = self.client.get(
|
||||||
|
f"/api/core/feedback/{csu.course_session.course.id}/{circle.id}/"
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.data, {})
|
self.assertEqual(response.data, {})
|
||||||
|
|
@ -186,7 +203,9 @@ class FeedbackDetailApiTestCase(FeedbackApiBaseTestCase):
|
||||||
circle = Circle.objects.get(slug="test-lehrgang-lp-circle-analyse")
|
circle = Circle.objects.get(slug="test-lehrgang-lp-circle-analyse")
|
||||||
FeedbackFactory(circle=circle, course_session=csu.course_session).save()
|
FeedbackFactory(circle=circle, course_session=csu.course_session).save()
|
||||||
|
|
||||||
response = self.client.get(f"/api/core/feedback/{csu.course_session.course.id}/{circle.id}/")
|
response = self.client.get(
|
||||||
|
f"/api/core/feedback/{csu.course_session.course.id}/{circle.id}/"
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.data, {})
|
self.assertEqual(response.data, {})
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import itertools
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
from rest_framework.decorators import api_view
|
from rest_framework.decorators import api_view
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
@ -22,26 +24,31 @@ FEEDBACK_FIELDS = [
|
||||||
|
|
||||||
@api_view(["GET"])
|
@api_view(["GET"])
|
||||||
def get_expert_feedbacks_for_course(request, course_id):
|
def get_expert_feedbacks_for_course(request, course_id):
|
||||||
feedbacks = FeedbackResponse.objects.filter(course_session__course_id=course_id, circle__expert__user=request.user)
|
feedbacks = FeedbackResponse.objects.filter(
|
||||||
circle_count = {}
|
course_session__course_id=course_id, circle__expert__user=request.user
|
||||||
|
).order_by("circle_id")
|
||||||
|
circle_count = []
|
||||||
|
|
||||||
for feedback in feedbacks:
|
grouped_feedbacks = itertools.groupby(feedbacks, lambda x: x.circle_id)
|
||||||
if feedback.circle_id not in circle_count:
|
|
||||||
circle_count[feedback.circle_id] = {
|
for key, feedbacks in grouped_feedbacks:
|
||||||
"circle_id": feedback.circle_id,
|
circle_count.append(
|
||||||
"count": 0,
|
{
|
||||||
|
"circle_id": key,
|
||||||
|
"count": len(list(feedbacks)),
|
||||||
}
|
}
|
||||||
|
)
|
||||||
circle_count[feedback.circle_id]["count"] += 1
|
|
||||||
|
|
||||||
return Response(status=200, data=circle_count)
|
return Response(status=200, data=circle_count)
|
||||||
|
|
||||||
|
|
||||||
@api_view(["GET"])
|
@api_view(["GET"])
|
||||||
def get_feedback_for_circle(request, course_id, circle_id):
|
def get_feedback_for_circle(request, course_id, circle_id):
|
||||||
feedbacks = FeedbackResponse.objects.filter(course_session__course_id=course_id,
|
feedbacks = FeedbackResponse.objects.filter(
|
||||||
circle__expert__user=request.user,
|
course_session__course_id=course_id,
|
||||||
circle_id=circle_id)
|
circle__expert__user=request.user,
|
||||||
|
circle_id=circle_id,
|
||||||
|
)
|
||||||
|
|
||||||
# I guess this is ok for the üK case
|
# I guess this is ok for the üK case
|
||||||
feedback_data = {}
|
feedback_data = {}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue