From a48ac35e629a412bf8e2da84d28b837d2f11a389 Mon Sep 17 00:00:00 2001 From: Reto Aebersold Date: Thu, 7 Dec 2023 11:19:56 +0100 Subject: [PATCH] feat: praxis assignments API --- .../learning_mentor/content/__init__.py | 0 .../content/praxis_assignment.py | 115 ++++++++++++++ .../vbv_lernwelt/learning_mentor/entities.py | 25 +++ .../learning_mentor/serializers.py | 19 +++ .../learning_mentor/tests/test_api.py | 70 ++++++++- .../learning_mentor/tests/test_assignments.py | 4 +- server/vbv_lernwelt/learning_mentor/views.py | 145 ++---------------- 7 files changed, 240 insertions(+), 138 deletions(-) create mode 100644 server/vbv_lernwelt/learning_mentor/content/__init__.py create mode 100644 server/vbv_lernwelt/learning_mentor/content/praxis_assignment.py create mode 100644 server/vbv_lernwelt/learning_mentor/entities.py create mode 100644 server/vbv_lernwelt/learning_mentor/serializers.py diff --git a/server/vbv_lernwelt/learning_mentor/content/__init__.py b/server/vbv_lernwelt/learning_mentor/content/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/vbv_lernwelt/learning_mentor/content/praxis_assignment.py b/server/vbv_lernwelt/learning_mentor/content/praxis_assignment.py new file mode 100644 index 00000000..8c2e7024 --- /dev/null +++ b/server/vbv_lernwelt/learning_mentor/content/praxis_assignment.py @@ -0,0 +1,115 @@ +from typing import List + +from vbv_lernwelt.assignment.models import ( + Assignment, + AssignmentCompletion, + AssignmentCompletionStatus, + AssignmentType, +) +from vbv_lernwelt.core.models import User +from vbv_lernwelt.course.models import CourseSession +from vbv_lernwelt.course_session.models import CourseSessionAssignment +from vbv_lernwelt.learning_mentor.entities import ( + CompletionStatus, + PraxisAssignmentCompletion, + PraxisAssignmentStatus, +) + + +def get_assignment_completions( + course_session: CourseSession, assignment: Assignment, participants: List[User] +) -> List[PraxisAssignmentCompletion]: + evaluation_results = AssignmentCompletion.objects.filter( + assignment_user__in=participants, + course_session=course_session, + assignment=assignment, + ).values("completion_status", "assignment_user__last_name", "assignment_user") + + user_status_map = {} + for result in evaluation_results: + completion_status = result["completion_status"] + + if completion_status == AssignmentCompletionStatus.EVALUATION_SUBMITTED.value: + status = CompletionStatus.EVALUATED + elif completion_status in [ + AssignmentCompletionStatus.SUBMITTED.value, + AssignmentCompletionStatus.EVALUATION_IN_PROGRESS.value, + ]: + status = CompletionStatus.SUBMITTED + else: + status = CompletionStatus.UNKNOWN + + user_status_map[result["assignment_user"]] = ( + status, + result["assignment_user__last_name"], + ) + + status_priority = { + CompletionStatus.SUBMITTED: 1, + CompletionStatus.EVALUATED: 2, + CompletionStatus.UNKNOWN: 3, + } + + sorted_participants = sorted( + participants, + key=lambda u: ( + status_priority.get( + user_status_map.get(u.id, (CompletionStatus.UNKNOWN, ""))[0] + ), + user_status_map.get(u.id, ("", u.last_name))[1], + ), + ) + + return [ + PraxisAssignmentCompletion( + status=user_status_map.get( + user.id, (CompletionStatus.UNKNOWN, user.last_name) + )[0], + user_id=user.id, + last_name=user.last_name, + ) + for user in sorted_participants + ] + + +def get_praxis_assignments( + course_session: CourseSession, participants: List[User] +) -> List[PraxisAssignmentStatus]: + records = [] + + if not participants: + return records + + for course_session_assignment in CourseSessionAssignment.objects.filter( + course_session=course_session, + learning_content__content_assignment__assignment_type__in=[ + AssignmentType.PRAXIS_ASSIGNMENT.value, + ], + ): + learning_content = course_session_assignment.learning_content + + completions = get_assignment_completions( + course_session=course_session, + assignment=learning_content.content_assignment, + participants=participants, + ) + + submitted_count = len( + [ + completion + for completion in completions + if completion.status == CompletionStatus.SUBMITTED + ] + ) + + records.append( + PraxisAssignmentStatus( + id=course_session_assignment.id, + title=learning_content.content_assignment.title, + circle_id=learning_content.get_circle().id, + pending_evaluations=submitted_count, + completions=completions, + ) + ) + + return records diff --git a/server/vbv_lernwelt/learning_mentor/entities.py b/server/vbv_lernwelt/learning_mentor/entities.py new file mode 100644 index 00000000..6dabcc26 --- /dev/null +++ b/server/vbv_lernwelt/learning_mentor/entities.py @@ -0,0 +1,25 @@ +from dataclasses import dataclass +from enum import Enum +from typing import List + + +class CompletionStatus(str, Enum): + UNKNOWN = "UNKNOWN" + SUBMITTED = "SUBMITTED" + EVALUATED = "EVALUATED" + + +@dataclass +class PraxisAssignmentCompletion: + status: CompletionStatus + user_id: str + last_name: str + + +@dataclass +class PraxisAssignmentStatus: + id: str + title: str + circle_id: str + pending_evaluations: int + completions: List[PraxisAssignmentCompletion] diff --git a/server/vbv_lernwelt/learning_mentor/serializers.py b/server/vbv_lernwelt/learning_mentor/serializers.py new file mode 100644 index 00000000..6904d5b2 --- /dev/null +++ b/server/vbv_lernwelt/learning_mentor/serializers.py @@ -0,0 +1,19 @@ +from rest_framework import serializers + + +class PraxisAssignmentCompletionSerializer(serializers.Serializer): + status = serializers.SerializerMethodField() + user_id = serializers.CharField() + last_name = serializers.CharField() + + @staticmethod + def get_status(obj): + return obj.status.value + + +class PraxisAssignmentStatusSerializer(serializers.Serializer): + id = serializers.CharField() + title = serializers.CharField() + circle_id = serializers.CharField() + pending_evaluations = serializers.IntegerField() + completions = PraxisAssignmentCompletionSerializer(many=True) diff --git a/server/vbv_lernwelt/learning_mentor/tests/test_api.py b/server/vbv_lernwelt/learning_mentor/tests/test_api.py index fba08cee..7f536912 100644 --- a/server/vbv_lernwelt/learning_mentor/tests/test_api.py +++ b/server/vbv_lernwelt/learning_mentor/tests/test_api.py @@ -2,10 +2,19 @@ from django.urls import reverse from rest_framework import status from rest_framework.test import APITestCase +from vbv_lernwelt.assignment.models import ( + AssignmentCompletion, + AssignmentCompletionStatus, + AssignmentType, +) from vbv_lernwelt.course.creators.test_utils import ( add_course_session_user, + create_assignment, + create_assignment_learning_content, + create_circle, create_course, create_course_session, + create_course_session_assignment, create_user, ) from vbv_lernwelt.course.models import CourseSessionUser @@ -14,8 +23,20 @@ from vbv_lernwelt.learning_mentor.models import LearningMentor class LearningMentorAPITest(APITestCase): def setUp(self) -> None: - course, course_page = create_course("Test Course") - self.course_session = create_course_session(course=course, title="Test VV") + self.course, self.course_page = create_course("Test Course") + self.course_session = create_course_session(course=self.course, title="Test VV") + + circle, _ = create_circle(title="Circle", course_page=self.course_page) + + self.assignment = create_assignment( + course=self.course, assignment_type=AssignmentType.PRAXIS_ASSIGNMENT + ) + + lca = create_assignment_learning_content(circle, self.assignment) + create_course_session_assignment( + course_session=self.course_session, learning_content_assignment=lca + ) + self.mentor = create_user("mentor") self.participant_1 = add_course_session_user( self.course_session, @@ -87,3 +108,48 @@ class LearningMentorAPITest(APITestCase): self.assertEqual(participant_1["email"], "participant_1@example.com") self.assertEqual(participant_1["first_name"], "Test") self.assertEqual(participant_1["last_name"], "Participant_1") + + def test_api_praxis_assignments(self) -> None: + # GIVEN + participants = [self.participant_1, self.participant_2, self.participant_3] + self.client.force_login(self.mentor) + + mentor = LearningMentor.objects.create( + mentor=self.mentor, + course=self.course_session.course, + ) + mentor.participants.set(participants) + + AssignmentCompletion.objects.create( + assignment_user=self.participant_1.user, + course_session=self.course_session, + assignment=self.assignment, + completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value, + ) + + AssignmentCompletion.objects.create( + assignment_user=self.participant_3.user, + course_session=self.course_session, + assignment=self.assignment, + completion_status=AssignmentCompletionStatus.SUBMITTED.value, + ) + + # WHEN + response = self.client.get(self.url) + + # THEN + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data["praxis_assignments"]), 1) + + assignment = response.data["praxis_assignments"][0] + self.assertEqual(assignment["pending_evaluations"], 1) + + self.assertEqual( + assignment["completions"][0]["last_name"], self.participant_3.user.last_name + ) + self.assertEqual( + assignment["completions"][1]["last_name"], self.participant_1.user.last_name + ) + self.assertEqual( + assignment["completions"][2]["last_name"], self.participant_2.user.last_name + ) diff --git a/server/vbv_lernwelt/learning_mentor/tests/test_assignments.py b/server/vbv_lernwelt/learning_mentor/tests/test_assignments.py index 0e99d9bd..4e2f7fa4 100644 --- a/server/vbv_lernwelt/learning_mentor/tests/test_assignments.py +++ b/server/vbv_lernwelt/learning_mentor/tests/test_assignments.py @@ -14,11 +14,11 @@ from vbv_lernwelt.course.creators.test_utils import ( create_course_session_assignment, create_user, ) -from vbv_lernwelt.learning_mentor.views import ( - CompletionStatus, +from vbv_lernwelt.learning_mentor.content.praxis_assignment import ( get_assignment_completions, get_praxis_assignments, ) +from vbv_lernwelt.learning_mentor.entities import CompletionStatus class AttendanceServicesTestCase(TestCase): diff --git a/server/vbv_lernwelt/learning_mentor/views.py b/server/vbv_lernwelt/learning_mentor/views.py index 98c7ee08..98d71414 100644 --- a/server/vbv_lernwelt/learning_mentor/views.py +++ b/server/vbv_lernwelt/learning_mentor/views.py @@ -1,140 +1,14 @@ -from dataclasses import dataclass -from enum import Enum -from typing import List - from rest_framework.decorators import api_view from rest_framework.generics import get_object_or_404 from rest_framework.response import Response -from vbv_lernwelt.assignment.models import ( - Assignment, - AssignmentCompletion, - AssignmentCompletionStatus, - AssignmentType, -) -from vbv_lernwelt.core.models import User from vbv_lernwelt.core.serializers import UserSerializer from vbv_lernwelt.course.models import CourseSession -from vbv_lernwelt.course_session.models import CourseSessionAssignment +from vbv_lernwelt.learning_mentor.content.praxis_assignment import ( + get_praxis_assignments, +) from vbv_lernwelt.learning_mentor.models import LearningMentor - - -class CompletionStatus(str, Enum): - UNKNOWN = "UNKNOWN" - SUBMITTED = "SUBMITTED" - EVALUATED = "EVALUATED" - - -@dataclass -class PraxisAssignmentCompletion: - status: CompletionStatus - user_id: str - last_name: str - - -@dataclass -class PraxisAssignmentStatus: - id: str - title: str - circle_id: str - pending_evaluations: int - completions: List[PraxisAssignmentCompletion] - - -def get_assignment_completions( - course_session: CourseSession, assignment: Assignment, participants: List[User] -) -> List[PraxisAssignmentCompletion]: - evaluation_results = AssignmentCompletion.objects.filter( - assignment_user__in=participants, - course_session=course_session, - assignment=assignment, - ).values("completion_status", "assignment_user__last_name", "assignment_user") - - user_status_map = {} - for result in evaluation_results: - completion_status = result["completion_status"] - - if completion_status == AssignmentCompletionStatus.EVALUATION_SUBMITTED.value: - status = CompletionStatus.EVALUATED - elif completion_status in [ - AssignmentCompletionStatus.SUBMITTED.value, - AssignmentCompletionStatus.EVALUATION_IN_PROGRESS.value, - ]: - status = CompletionStatus.SUBMITTED - else: - status = CompletionStatus.UNKNOWN - - user_status_map[result["assignment_user"]] = ( - status, - result["assignment_user__last_name"], - ) - - status_priority = { - CompletionStatus.SUBMITTED: 1, - CompletionStatus.EVALUATED: 2, - CompletionStatus.UNKNOWN: 3, - } - - sorted_participants = sorted( - participants, - key=lambda u: ( - status_priority.get( - user_status_map.get(u.id, (CompletionStatus.UNKNOWN, ""))[0] - ), - user_status_map.get(u.id, ("", u.last_name))[1], - ), - ) - - return [ - PraxisAssignmentCompletion( - status=user_status_map.get( - user.id, (CompletionStatus.UNKNOWN, user.last_name) - )[0], - user_id=user.id, - last_name=user.last_name, - ) - for user in sorted_participants - ] - - -def get_praxis_assignments( - course_session: CourseSession, participants: List[User] -) -> List[PraxisAssignmentStatus]: - records = [] - - for course_session_assignment in CourseSessionAssignment.objects.filter( - course_session=course_session, - learning_content__content_assignment__assignment_type__in=[ - AssignmentType.PRAXIS_ASSIGNMENT.value, - ], - ): - learning_content = course_session_assignment.learning_content - - completions = get_assignment_completions( - course_session=course_session, - assignment=learning_content.content_assignment, - participants=participants, - ) - - submitted_count = len( - [ - completion - for completion in completions - if completion.status == CompletionStatus.SUBMITTED - ] - ) - - records.append( - PraxisAssignmentStatus( - id=course_session_assignment.id, - title=learning_content.content_assignment.title, - circle_id=learning_content.get_circle().id, - pending_evaluations=submitted_count, - completions=completions, - ) - ) - - return records +from vbv_lernwelt.learning_mentor.serializers import PraxisAssignmentStatusSerializer @api_view(["GET"]) @@ -150,12 +24,15 @@ def mentor_summary(request, course_session_id: int): ) participants = mentor.participants.filter(course_session=course_session) + users = [p.user for p in participants] + + praxis_assignments = get_praxis_assignments(course_session, users) return Response( { - "participants": [UserSerializer(p.user).data for p in participants], - "praxis_assignments": get_praxis_assignments( - course_session, participants - ), + "participants": [UserSerializer(user).data for user in users], + "praxis_assignments": PraxisAssignmentStatusSerializer( + praxis_assignments, many=True + ).data, } )