From 6a985ce60797b46a83559af95059c943cd3d8f41 Mon Sep 17 00:00:00 2001 From: Reto Aebersold Date: Mon, 15 Jan 2024 11:33:45 +0100 Subject: [PATCH] feat: cockpit user profile --- .../cockpit/profile/CockpitProfileContent.vue | 10 ++ .../pages/cockpit/CockpitUserProfilePage.vue | 116 ------------------ .../profilePage/CockpitUserProfilePage.vue | 74 +++++++++++ .../profilePage/LearningPathProfilePage.vue | 72 +++++++++++ client/src/router/index.ts | 12 +- server/config/urls.py | 3 +- server/vbv_lernwelt/api/user.py | 15 ++- server/vbv_lernwelt/iam/permissions.py | 26 +++- server/vbv_lernwelt/iam/tests/test_experts.py | 82 +++++++++++++ 9 files changed, 288 insertions(+), 122 deletions(-) create mode 100644 client/src/components/cockpit/profile/CockpitProfileContent.vue delete mode 100644 client/src/pages/cockpit/CockpitUserProfilePage.vue create mode 100644 client/src/pages/cockpit/profilePage/CockpitUserProfilePage.vue create mode 100644 client/src/pages/cockpit/profilePage/LearningPathProfilePage.vue create mode 100644 server/vbv_lernwelt/iam/tests/test_experts.py diff --git a/client/src/components/cockpit/profile/CockpitProfileContent.vue b/client/src/components/cockpit/profile/CockpitProfileContent.vue new file mode 100644 index 00000000..77eb78d1 --- /dev/null +++ b/client/src/components/cockpit/profile/CockpitProfileContent.vue @@ -0,0 +1,10 @@ + diff --git a/client/src/pages/cockpit/CockpitUserProfilePage.vue b/client/src/pages/cockpit/CockpitUserProfilePage.vue deleted file mode 100644 index 88df5bd4..00000000 --- a/client/src/pages/cockpit/CockpitUserProfilePage.vue +++ /dev/null @@ -1,116 +0,0 @@ - - - - - diff --git a/client/src/pages/cockpit/profilePage/CockpitUserProfilePage.vue b/client/src/pages/cockpit/profilePage/CockpitUserProfilePage.vue new file mode 100644 index 00000000..e06f7f96 --- /dev/null +++ b/client/src/pages/cockpit/profilePage/CockpitUserProfilePage.vue @@ -0,0 +1,74 @@ + + + diff --git a/client/src/pages/cockpit/profilePage/LearningPathProfilePage.vue b/client/src/pages/cockpit/profilePage/LearningPathProfilePage.vue new file mode 100644 index 00000000..c4bbfb9b --- /dev/null +++ b/client/src/pages/cockpit/profilePage/LearningPathProfilePage.vue @@ -0,0 +1,72 @@ + + + diff --git a/client/src/router/index.ts b/client/src/router/index.ts index 2e0deef5..44eeb299 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -221,9 +221,19 @@ const router = createRouter({ }, { path: "profile/:userId", - component: () => import("@/pages/cockpit/CockpitUserProfilePage.vue"), + component: () => + import("@/pages/cockpit/profilePage/CockpitUserProfilePage.vue"), props: true, name: "cockpitUserProfile", + children: [ + { + path: "learning-path", + component: () => + import("@/pages/cockpit/profilePage/LearningPathProfilePage.vue"), + props: true, + name: "cockpitProfileLearningPath", + }, + ], }, { path: "profile/:userId/:circleSlug", diff --git a/server/config/urls.py b/server/config/urls.py index f7d27476..0634c233 100644 --- a/server/config/urls.py +++ b/server/config/urls.py @@ -12,7 +12,7 @@ from django_ratelimit.exceptions import Ratelimited from graphene_django.views import GraphQLView from vbv_lernwelt.api.directory import list_entities -from vbv_lernwelt.api.user import get_cockpit_type, me_user_view +from vbv_lernwelt.api.user import get_cockpit_type, get_profile, me_user_view from vbv_lernwelt.assignment.views import request_assignment_completion_status from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt from vbv_lernwelt.core.schema import schema @@ -100,6 +100,7 @@ urlpatterns = [ path("sso/", include("vbv_lernwelt.sso.urls")), re_path(r'api/core/me/$', me_user_view, name='me_user_view'), re_path(r'api/core/entities/$', list_entities, name='list_entities'), + path(r'api/core/profile//', get_profile, name='get_profile_view'), re_path(r'api/core/login/$', django_view_authentication_exempt(vue_login), name='vue_login'), diff --git a/server/vbv_lernwelt/api/user.py b/server/vbv_lernwelt/api/user.py index 2ff2b3d3..9aeecaf3 100644 --- a/server/vbv_lernwelt/api/user.py +++ b/server/vbv_lernwelt/api/user.py @@ -5,9 +5,9 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from vbv_lernwelt.core.serializers import UserSerializer - from vbv_lernwelt.course.models import Course, CourseSessionUser from vbv_lernwelt.course_session_group.models import CourseSessionGroup +from vbv_lernwelt.iam.permissions import can_view_profile from vbv_lernwelt.learning_mentor.models import LearningMentor @@ -59,3 +59,16 @@ def get_cockpit_type(request, course_id: int): cockpit_type = None return Response({"type": cockpit_type}) + + +@api_view(["GET"]) +@permission_classes([IsAuthenticated]) +def get_profile(request, course_session_id: int, user_id: str): + course_session_user = get_object_or_404( + CourseSessionUser, course_session_id=course_session_id, user_id=user_id + ) + + if not can_view_profile(request.user, course_session_user): + return Response(status=403) + + return Response(UserSerializer(course_session_user.user).data) diff --git a/server/vbv_lernwelt/iam/permissions.py b/server/vbv_lernwelt/iam/permissions.py index e0b0fd92..c243b62c 100644 --- a/server/vbv_lernwelt/iam/permissions.py +++ b/server/vbv_lernwelt/iam/permissions.py @@ -36,17 +36,24 @@ def is_course_session_expert(user, course_session_id: int): if user.is_superuser: return True + course_session = CourseSession.objects.get(id=course_session_id) + is_supervisor = CourseSessionGroup.objects.filter( - supervisor=user, course_session__id=course_session_id + supervisor=user, course_session=course_session ).exists() is_expert = CourseSessionUser.objects.filter( - course_session_id=course_session_id, + course_session=course_session, user=user, role=CourseSessionUser.Role.EXPERT, ).exists() - return is_supervisor or is_expert + is_learning_mentor = LearningMentor.objects.filter( + mentor=user, + course=course_session.course, + ).exists() + + return is_supervisor or is_expert or is_learning_mentor def is_course_session_member(user, course_session_id: int | None = None): @@ -150,3 +157,16 @@ def has_role_in_course(user: User, course: Course) -> bool: return True return False + + +def can_view_profile(user: User, profile_user: CourseSessionUser) -> bool: + if user.is_superuser: + return True + + if user == profile_user.user: + return True + + if is_course_session_expert(user, profile_user.course_session_id): + return True + + return False diff --git a/server/vbv_lernwelt/iam/tests/test_experts.py b/server/vbv_lernwelt/iam/tests/test_experts.py new file mode 100644 index 00000000..f0032656 --- /dev/null +++ b/server/vbv_lernwelt/iam/tests/test_experts.py @@ -0,0 +1,82 @@ +from django.test import TestCase + +from vbv_lernwelt.course.creators.test_utils import ( + create_course, + create_course_session, + create_user, +) +from vbv_lernwelt.course.models import CourseSessionUser +from vbv_lernwelt.course_session_group.models import CourseSessionGroup +from vbv_lernwelt.iam.permissions import is_course_session_expert +from vbv_lernwelt.learning_mentor.models import LearningMentor + + +class ExpertTestCase(TestCase): + def setUp(self): + self.course, _ = create_course("Test Course") + self.course_session = create_course_session( + course=self.course, title="Test Session" + ) + + self.user = create_user("user") + + def test_member(self): + # GIVEN + csu = CourseSessionUser.objects.create( + course_session=self.course_session, + user=self.user, + role=CourseSessionUser.Role.MEMBER, + ) + + # WHEN + is_expert = is_course_session_expert( + user=csu.user, course_session_id=self.course_session.id + ) + + # THEN + self.assertFalse(is_expert) + + def test_supervisor(self): + # GIVEN + csg = CourseSessionGroup.objects.create(name="Test Group", course=self.course) + csg.course_session.add(self.course_session) + csg.supervisor.add(self.user) + + # WHEN + is_expert = is_course_session_expert( + user=self.user, course_session_id=self.course_session.id + ) + + # THEN + self.assertTrue(is_expert) + + def test_learning_mentor(self): + # GIVEN + lm = LearningMentor.objects.create( + mentor=self.user, + course=self.course, + ) + + # WHEN + is_expert = is_course_session_expert( + user=lm.mentor, course_session_id=self.course_session.id + ) + + # THEN + self.assertTrue(is_expert) + + def test_expert(self): + # GIVEN + csu = CourseSessionUser.objects.create( + course_session=self.course_session, + user=self.user, + role=CourseSessionUser.Role.EXPERT, + ) + + # WHEN + is_expert = is_course_session_expert( + user=csu.user, course_session_id=self.course_session.id + ) + + # THEN + self.assertTrue(is_expert)