From 278d65990513c7899b4dff9fe1bbcfdeb9deca62 Mon Sep 17 00:00:00 2001 From: Reto Aebersold Date: Mon, 4 Dec 2023 10:06:19 +0100 Subject: [PATCH] wip: learning mentor --- .../pages/cockpit/vv/VVCockpitParentPage.vue | 11 +++++ server/config/settings/base.py | 1 + server/config/urls.py | 5 ++ .../assignment/graphql/mutations.py | 4 +- .../vbv_lernwelt/assignment/graphql/types.py | 4 +- server/vbv_lernwelt/assignment/views.py | 4 +- server/vbv_lernwelt/iam/permissions.py | 17 +++++++ .../vbv_lernwelt/learning_mentor/__init__.py | 0 server/vbv_lernwelt/learning_mentor/admin.py | 10 ++++ server/vbv_lernwelt/learning_mentor/apps.py | 6 +++ .../migrations/0001_initial.py | 42 ++++++++++++++++ .../learning_mentor/migrations/__init__.py | 0 server/vbv_lernwelt/learning_mentor/models.py | 13 +++++ .../learning_mentor/tests/__init__.py | 0 .../learning_mentor/tests/test_api.py | 26 ++++++++++ server/vbv_lernwelt/learning_mentor/views.py | 48 +++++++++++++++++++ 16 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 client/src/pages/cockpit/vv/VVCockpitParentPage.vue create mode 100644 server/vbv_lernwelt/learning_mentor/__init__.py create mode 100644 server/vbv_lernwelt/learning_mentor/admin.py create mode 100644 server/vbv_lernwelt/learning_mentor/apps.py create mode 100644 server/vbv_lernwelt/learning_mentor/migrations/0001_initial.py create mode 100644 server/vbv_lernwelt/learning_mentor/migrations/__init__.py create mode 100644 server/vbv_lernwelt/learning_mentor/models.py create mode 100644 server/vbv_lernwelt/learning_mentor/tests/__init__.py create mode 100644 server/vbv_lernwelt/learning_mentor/tests/test_api.py create mode 100644 server/vbv_lernwelt/learning_mentor/views.py diff --git a/client/src/pages/cockpit/vv/VVCockpitParentPage.vue b/client/src/pages/cockpit/vv/VVCockpitParentPage.vue new file mode 100644 index 00000000..ed872e95 --- /dev/null +++ b/client/src/pages/cockpit/vv/VVCockpitParentPage.vue @@ -0,0 +1,11 @@ + + + diff --git a/server/config/settings/base.py b/server/config/settings/base.py index 7e80cd01..8ead2757 100644 --- a/server/config/settings/base.py +++ b/server/config/settings/base.py @@ -130,6 +130,7 @@ LOCAL_APPS = [ "vbv_lernwelt.importer", "vbv_lernwelt.edoniq_test", "vbv_lernwelt.course_session_group", + "vbv_lernwelt.learning_mentor", ] # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS diff --git a/server/config/urls.py b/server/config/urls.py index 59544756..87376ca5 100644 --- a/server/config/urls.py +++ b/server/config/urls.py @@ -53,6 +53,7 @@ from vbv_lernwelt.importer.views import ( coursesessions_trainers_import, t2l_sync, ) +from vbv_lernwelt.learning_mentor.views import mentor_summary from vbv_lernwelt.notify.views import email_notification_settings from wagtail import urls as wagtail_urls from wagtail.admin import urls as wagtailadmin_urls @@ -125,6 +126,10 @@ urlpatterns = [ request_course_completion_for_user, name="request_course_completion_for_user"), + path(r"api/mentor//", + mentor_summary, + name="mentor_summary"), + # assignment path( r"api/assignment///status/", diff --git a/server/vbv_lernwelt/assignment/graphql/mutations.py b/server/vbv_lernwelt/assignment/graphql/mutations.py index 2d9394ca..c7540861 100644 --- a/server/vbv_lernwelt/assignment/graphql/mutations.py +++ b/server/vbv_lernwelt/assignment/graphql/mutations.py @@ -10,7 +10,7 @@ from vbv_lernwelt.assignment.models import Assignment, AssignmentCompletionStatu from vbv_lernwelt.assignment.services import update_assignment_completion from vbv_lernwelt.core.models import User from vbv_lernwelt.course.models import CourseSession, CourseSessionUser -from vbv_lernwelt.iam.permissions import has_course_access, is_course_session_expert +from vbv_lernwelt.iam.permissions import can_evaluate_assignments, has_course_access logger = structlog.get_logger(__name__) @@ -92,7 +92,7 @@ class AssignmentCompletionMutation(graphene.Mutation): AssignmentCompletionStatus.EVALUATION_SUBMITTED, AssignmentCompletionStatus.EVALUATION_IN_PROGRESS, ): - if not is_course_session_expert(info.context.user, course_session_id): + if not can_evaluate_assignments(info.context.user, course_session_id): raise PermissionDenied() evaluation_data = { diff --git a/server/vbv_lernwelt/assignment/graphql/types.py b/server/vbv_lernwelt/assignment/graphql/types.py index 021a2226..c3c933e7 100644 --- a/server/vbv_lernwelt/assignment/graphql/types.py +++ b/server/vbv_lernwelt/assignment/graphql/types.py @@ -7,7 +7,7 @@ from vbv_lernwelt.assignment.models import Assignment, AssignmentCompletion from vbv_lernwelt.core.graphql.types import JSONStreamField from vbv_lernwelt.course.graphql.interfaces import CoursePageInterface from vbv_lernwelt.course.models import CourseSession -from vbv_lernwelt.iam.permissions import has_course_access, is_course_session_expert +from vbv_lernwelt.iam.permissions import can_evaluate_assignments, has_course_access from vbv_lernwelt.learnpath.graphql.types import LearningContentInterface @@ -103,7 +103,7 @@ def resolve_assignment_completion( if assignment_user_id is None: assignment_user_id = info.context.user.id - if str(assignment_user_id) == str(info.context.user.id) or is_course_session_expert( + if str(assignment_user_id) == str(info.context.user.id) or can_evaluate_assignments( info.context.user, course_session_id ): course_id = CourseSession.objects.get(id=course_session_id).course_id diff --git a/server/vbv_lernwelt/assignment/views.py b/server/vbv_lernwelt/assignment/views.py index 640a64ab..7b55be7d 100644 --- a/server/vbv_lernwelt/assignment/views.py +++ b/server/vbv_lernwelt/assignment/views.py @@ -4,7 +4,7 @@ from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response from vbv_lernwelt.assignment.models import AssignmentCompletion -from vbv_lernwelt.iam.permissions import is_course_session_expert +from vbv_lernwelt.iam.permissions import can_evaluate_assignments logger = structlog.get_logger(__name__) @@ -12,7 +12,7 @@ logger = structlog.get_logger(__name__) @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): + if can_evaluate_assignments(request.user, course_session_id): qs = AssignmentCompletion.objects.filter( course_session_id=course_session_id, assignment_id=assignment_id, diff --git a/server/vbv_lernwelt/iam/permissions.py b/server/vbv_lernwelt/iam/permissions.py index a572f21d..3541cfe6 100644 --- a/server/vbv_lernwelt/iam/permissions.py +++ b/server/vbv_lernwelt/iam/permissions.py @@ -48,6 +48,23 @@ def is_course_session_expert(user, course_session_id: int): return is_supervisor or is_expert +def can_evaluate_assignments(user, course_session_id: int): + if user.is_superuser: + return True + + is_supervisor = CourseSessionGroup.objects.filter( + supervisor=user, course_session__id=course_session_id + ).exists() + + is_expert = CourseSessionUser.objects.filter( + course_session_id=course_session_id, + user=user, + role=CourseSessionUser.Role.EXPERT, + ).exists() + + return is_supervisor or is_expert + + def course_sessions_for_user_qs(user): if user.is_superuser: return CourseSession.objects.all() diff --git a/server/vbv_lernwelt/learning_mentor/__init__.py b/server/vbv_lernwelt/learning_mentor/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/learning_mentor/admin.py b/server/vbv_lernwelt/learning_mentor/admin.py new file mode 100644 index 00000000..6fb7474e --- /dev/null +++ b/server/vbv_lernwelt/learning_mentor/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin + +from vbv_lernwelt.learning_mentor.models import LearningMentor + + +@admin.register(LearningMentor) +class LearningMentorAdmin(admin.ModelAdmin): + list_display = [ + "mentor_email", + ] diff --git a/server/vbv_lernwelt/learning_mentor/apps.py b/server/vbv_lernwelt/learning_mentor/apps.py new file mode 100644 index 00000000..eba5c3d6 --- /dev/null +++ b/server/vbv_lernwelt/learning_mentor/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class LearningMentorConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "vbv_lernwelt.learning_mentor" diff --git a/server/vbv_lernwelt/learning_mentor/migrations/0001_initial.py b/server/vbv_lernwelt/learning_mentor/migrations/0001_initial.py new file mode 100644 index 00000000..ef477003 --- /dev/null +++ b/server/vbv_lernwelt/learning_mentor/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# Generated by Django 3.2.20 on 2023-12-01 10:11 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="LearningMentor", + fields=[ + ( + "mentor_email", + models.EmailField( + max_length=254, primary_key=True, serialize=False + ), + ), + ( + "mentor", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "students", + models.ManyToManyField( + related_name="students", to=settings.AUTH_USER_MODEL + ), + ), + ], + ), + ] diff --git a/server/vbv_lernwelt/learning_mentor/migrations/__init__.py b/server/vbv_lernwelt/learning_mentor/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/learning_mentor/models.py b/server/vbv_lernwelt/learning_mentor/models.py new file mode 100644 index 00000000..5b6c51ed --- /dev/null +++ b/server/vbv_lernwelt/learning_mentor/models.py @@ -0,0 +1,13 @@ +from django.db import models + +from vbv_lernwelt.core.models import User + + +class LearningMentor(models.Model): + mentor_email = models.EmailField(primary_key=True) + mentor = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) + + students = models.ManyToManyField(User, related_name="students") + + def __str__(self): + return self.mentor_email diff --git a/server/vbv_lernwelt/learning_mentor/tests/__init__.py b/server/vbv_lernwelt/learning_mentor/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/learning_mentor/tests/test_api.py b/server/vbv_lernwelt/learning_mentor/tests/test_api.py new file mode 100644 index 00000000..b4b7245b --- /dev/null +++ b/server/vbv_lernwelt/learning_mentor/tests/test_api.py @@ -0,0 +1,26 @@ +from django.urls import reverse +from rest_framework.test import APITestCase + +from vbv_lernwelt.course.creators.test_utils import create_course, create_course_session + + +class LearningMentorAPITest(APITestCase): + # def setUp(self) -> None: + # create_default_users() + # create_test_course(with_sessions=True) + # + # self.mentor = User.objects.get(username="expert-vv.expert1@eiger-versicherungen.ch") + + def test_api(self) -> None: + # GIVEN + course, course_page = create_course("Test Course") + course_session = create_course_session(course=course, title="Test VV") + + # self.client.force_login(self.mentor) + url = reverse("mentor_summary", kwargs={"course_session_id": course_session.id}) + + # WHEN + response = self.client.get(url) + + # THEN + # self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/server/vbv_lernwelt/learning_mentor/views.py b/server/vbv_lernwelt/learning_mentor/views.py new file mode 100644 index 00000000..9ca77589 --- /dev/null +++ b/server/vbv_lernwelt/learning_mentor/views.py @@ -0,0 +1,48 @@ +from rest_framework.decorators import api_view +from rest_framework.response import Response + +from vbv_lernwelt.course.models import CourseSession +from vbv_lernwelt.feedback.models import FeedbackResponse +from vbv_lernwelt.feedback.utils import feedback_users +from vbv_lernwelt.learnpath.models import Circle + + +def get_practical_assignment(): + pass + + +def get_feedbacks(course_session: CourseSession): + circle_feedbacks = [] + + fbs = FeedbackResponse.objects.filter( + submitted=True, + course_session=course_session, + feedback_user__in=feedback_users(course_session.id), + ) + + +def get_all_feedbacks(course_session: CourseSession): + circle_feedbacks = [] + + circles = ( + course_session.course.get_learning_path() + .get_descendants() + .live() + .specific() + .exact_type(Circle) + ) + + print(circles) + + +@api_view(["GET"]) +def mentor_summary(request, course_session_id: int): + if not request.user.is_authenticated: + return Response(status=403) + + if request.method == "GET": + course_session = CourseSession.objects.get(id=course_session_id) + + get_all_feedbacks(course_session) + + return Response({})