diff --git a/client/src/main.ts b/client/src/main.ts index df9292f4..f4692211 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -29,7 +29,7 @@ const app = createApp(App); Sentry.init({ app, - environment: import.meta.env.VITE_SENTRY_ENV, + environment: import.meta.env.VITE_SENTRY_ENV || "http://localhost:8000/server/graphql/", dsn: "https://2df6096a4fd94bd6b4802124d10e4b8d@o8544.ingest.sentry.io/4504157846372352", tracesSampleRate: 0.0, enabled: diff --git a/server/config/urls.py b/server/config/urls.py index 8c779fe7..12017daf 100644 --- a/server/config/urls.py +++ b/server/config/urls.py @@ -7,6 +7,9 @@ from django.urls import include, path, re_path from django.views import defaults as default_views from grapple import urls as grapple_urls 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.views import ( @@ -32,10 +35,7 @@ from vbv_lernwelt.course.views import ( request_course_completion, request_course_completion_for_user, ) -from vbv_lernwelt.feedback.views import get_name -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.feedback.views import get_expert_feedbacks_for_course def raise_example_error(request): @@ -81,7 +81,7 @@ urlpatterns = [ request_course_completion_for_user, name="request_course_completion_for_user"), - # test + # documents path(r'api/core/document/start/', document_upload_start, name='file_upload_start'), path(r'api/core/document//', document_delete, @@ -91,13 +91,16 @@ urlpatterns = [ path(r"api/core/document/local//", document_direct_upload, name='file_upload_local'), + # feedback + path(r'api/core/feedback/summary//', get_expert_feedbacks_for_course, + name='feedback_summary'), + # testing and debug path('server/raise_error/', user_passes_test(lambda u: u.is_superuser, login_url='/login/')( raise_example_error), ), path("server/checkratelimit/", check_rate_limit), path("server/", include(grapple_urls)), - path(r"your-name/", get_name) ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/server/vbv_lernwelt/course/tests/test_document_uploads.py b/server/vbv_lernwelt/course/tests/test_document_uploads.py index 0f105647..8b21b95c 100644 --- a/server/vbv_lernwelt/course/tests/test_document_uploads.py +++ b/server/vbv_lernwelt/course/tests/test_document_uploads.py @@ -32,6 +32,12 @@ class DocumentUploadApiTestCase(APITestCase): ) csu.expert.add(Circle.objects.get(slug="test-lehrgang-lp-circle-analyse")) + _csu = CourseSessionUser.objects.create( + course_session=self.course_session, + user=self.user, + role=CourseSessionUser.Role.MEMBER, + ) + self.test_data = { "file_name": "test.pdf", "file_type": "application/pdf", @@ -185,7 +191,6 @@ class DocumentUploadApiTestCase(APITestCase): response = self.client.delete(f"/api/core/document/{document.id}/") self.assertEqual(response.status_code, 403) - # expert cannot upload in other course # expert cannot delete other upload # exper cannot change course diff --git a/server/vbv_lernwelt/feedback/factories.py b/server/vbv_lernwelt/feedback/factories.py new file mode 100644 index 00000000..11a364a7 --- /dev/null +++ b/server/vbv_lernwelt/feedback/factories.py @@ -0,0 +1,8 @@ +from factory.django import DjangoModelFactory + +from vbv_lernwelt.feedback.models import FeedbackResponse + + +class FeedbackFactory(DjangoModelFactory): + class Meta: + model = FeedbackResponse diff --git a/server/vbv_lernwelt/feedback/tests/__init__.py b/server/vbv_lernwelt/feedback/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/feedback/tests/test_feedback_api.py b/server/vbv_lernwelt/feedback/tests/test_feedback_api.py new file mode 100644 index 00000000..81f15b24 --- /dev/null +++ b/server/vbv_lernwelt/feedback/tests/test_feedback_api.py @@ -0,0 +1,117 @@ +from rest_framework.test import APITestCase + +from vbv_lernwelt.core.create_default_users import create_default_users +from vbv_lernwelt.core.models import User +from vbv_lernwelt.course.consts import COURSE_TEST_ID +from vbv_lernwelt.course.creators.test_course import create_test_course +from vbv_lernwelt.course.models import CourseSession, CourseSessionUser +from vbv_lernwelt.feedback.factories import FeedbackFactory +from vbv_lernwelt.learnpath.models import Circle + + +class FeedbackApiTestCase(APITestCase): + def setUp(self) -> None: + create_default_users() + create_test_course() + + self.user = User.objects.get(username="student") + self.expert = User.objects.get( + username="patrizia.huggel@eiger-versicherungen.ch" + ) + + self.course_session = CourseSession.objects.create( + course_id=COURSE_TEST_ID, + title="Test Lehrgang Session", + ) + + csu = CourseSessionUser.objects.create( + course_session=self.course_session, + user=User.objects.get(username="patrizia.huggel@eiger-versicherungen.ch"), + role=CourseSessionUser.Role.EXPERT, + ) + csu.expert.add(Circle.objects.get(slug="test-lehrgang-lp-circle-analyse")) + + _csu = CourseSessionUser.objects.create( + course_session=self.course_session, + user=self.user, + role=CourseSessionUser.Role.MEMBER, + ) + + self.test_data = { + "file_name": "test.pdf", + "file_type": "application/pdf", + "name": "Test", + "course_session": self.course_session.id, + } + + self.client.login( + username="patrizia.huggel@eiger-versicherungen.ch", password="myvbv1234" + ) + + def test_can_get_feedback_summary_for_circles(self): + number_basis_feedback = 5 + number_analyse_feedback = 10 + + csu = CourseSessionUser.objects.get( + course_session=self.course_session, + user=User.objects.get(username="patrizia.huggel@eiger-versicherungen.ch"), + role=CourseSessionUser.Role.EXPERT, + ) + analyse_circle = Circle.objects.get(slug="test-lehrgang-lp-circle-analyse") + basis_circle = Circle.objects.get(slug="test-lehrgang-lp-circle-basis") + csu.expert.add(basis_circle) + + for i in range(number_basis_feedback): + FeedbackFactory(circle=basis_circle, course_session=csu.course_session).save() + + for i in range(number_analyse_feedback): + FeedbackFactory(circle=analyse_circle, course_session=csu.course_session).save() + + response = self.client.get(f"/api/core/feedback/summary/{csu.course_session.course.id}/") + + self.assertEqual(response.status_code, 200) + expected = { + analyse_circle.id: {"circle_id": analyse_circle.id, "count": number_analyse_feedback}, + basis_circle.id: {"circle_id": basis_circle.id, "count": number_basis_feedback}, + } + self.assertEqual(response.data, expected) + + def test_can_only_see_feedback_from_own_circle(self): + number_basis_feedback = 5 + number_analyse_feedback = 10 + + csu = CourseSessionUser.objects.get( + course_session=self.course_session, + user=User.objects.get(username="patrizia.huggel@eiger-versicherungen.ch"), + role=CourseSessionUser.Role.EXPERT, + ) + analyse_circle = Circle.objects.get(slug="test-lehrgang-lp-circle-analyse") + basis_circle = Circle.objects.get(slug="test-lehrgang-lp-circle-basis") + + for i in range(number_basis_feedback): + FeedbackFactory(circle=basis_circle, course_session=csu.course_session).save() + + for i in range(number_analyse_feedback): + FeedbackFactory(circle=analyse_circle, course_session=csu.course_session).save() + + response = self.client.get(f"/api/core/feedback/summary/{csu.course_session.course.id}/") + + self.assertEqual(response.status_code, 200) + expected = { + analyse_circle.id: {"circle_id": analyse_circle.id, "count": number_analyse_feedback}, + } + self.assertEqual(response.data, expected) + + def test_student_does_not_see_feedback(self): + self.client.login(username="student", password="test") + csu = CourseSessionUser.objects.get( + course_session=self.course_session, + user=self.user, + ) + analyse_circle = Circle.objects.get(slug="test-lehrgang-lp-circle-analyse") + FeedbackFactory(circle=analyse_circle, course_session=csu.course_session).save() + + response = self.client.get(f"/api/core/feedback/summary/{csu.course_session.course.id}/") + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data, {}) diff --git a/server/vbv_lernwelt/feedback/views.py b/server/vbv_lernwelt/feedback/views.py index d140c76d..37e7d84c 100644 --- a/server/vbv_lernwelt/feedback/views.py +++ b/server/vbv_lernwelt/feedback/views.py @@ -1,15 +1,25 @@ -from django.http import HttpResponseRedirect -from django.shortcuts import render +import structlog +from rest_framework.decorators import api_view +from rest_framework.response import Response -from .forms import FeedbackForm +from vbv_lernwelt.feedback.models import FeedbackResponse + +logger = structlog.get_logger(__name__) -def get_name(request): - if request.method == "POST": - form = FeedbackForm(request.POST) - if form.is_valid(): - return HttpResponseRedirect("/thanks/") - else: - form = FeedbackForm() +@api_view(["GET"]) +def get_expert_feedbacks_for_course(request, course_id): + feedbacks = FeedbackResponse.objects.filter(course_session__course_id=course_id, circle__expert__user=request.user) + other = list(FeedbackResponse.objects.all()) + circle_count = {} - return render(request, "feedback/name.html", {"form": form}) + for feedback in feedbacks: + if feedback.circle_id not in circle_count: + circle_count[feedback.circle_id] = { + "circle_id": feedback.circle_id, + "count": 0, + } + + circle_count[feedback.circle_id]["count"] += 1 + + return Response(status=200, data=circle_count)