from dataclasses import asdict, dataclass from enum import Enum from typing import List, Set from rest_framework.decorators import api_view from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response from vbv_lernwelt.assignment.models import ( AssignmentCompletion, AssignmentCompletionStatus, ) from vbv_lernwelt.core.models import User from vbv_lernwelt.course.models import ( CourseConfiguration, CourseSession, CourseSessionUser, ) from vbv_lernwelt.course.views import logger from vbv_lernwelt.course_session_group.models import CourseSessionGroup from vbv_lernwelt.learning_mentor.models import LearningMentor from vbv_lernwelt.self_evaluation_feedback.models import SelfEvaluationFeedback class WidgetType(Enum): PROGRESS_WIDGET = "ProgressWidget" COMPETENCE_WIDGET = "CompetenceWidget" MENTOR_TASKS_WIDGET = "MentorTasksWidget" MENTOR_PERSON_WIDGET = "MentorPersonWidget" MENTOR_COMPETENCE_WIDGET = "MentorCompetenceWidget" COMPETENCE_CERTIFICATE_WIDGET = "CompetenceCertificateWidget" UK_STATISTICS_WIDGET = "UKStatisticsWidget" class RoleKeyType(Enum): MEMBER = "Member" MENTOR_VV = "MentorVV" MENTOR_UK = "MentorUK" SUPERVISOR = "Supervisor" TRAINER = "Trainer" @dataclass(frozen=True) class CourseSessionWithRoles: _original: CourseSession roles: Set[str] def __getattr__(self, name: str): # Delegate attribute access to the _original CourseSession object return getattr(self._original, name) def save(self, *args, **kwargs): raise NotImplementedError("This proxy object cannot be saved.") @dataclass(frozen=True) class CourseConfig: course_id: str course_slug: str course_title: str role_key: str is_uk: bool is_vv: bool is_mentor: bool widgets: List[str] has_preview: bool session_to_continue_id: str | None def get_course_sessions_with_roles_for_user(user: User) -> List[CourseSessionWithRoles]: result_course_sessions = {} # participant/member/expert course sessions csu_qs = CourseSessionUser.objects.filter(user=user).prefetch_related( "course_session", "course_session__course" ) for csu in csu_qs: cs = csu.course_session # member/expert is mutually exclusive... cs.roles = {csu.role} result_course_sessions[cs.id] = cs # enrich with supervisor course sessions csg_qs = CourseSessionGroup.objects.filter(supervisor=user).prefetch_related( "course_session", "course_session__course" ) for csg in csg_qs: for cs in csg.course_session.all(): cs.roles = set() cs = result_course_sessions.get(cs.id, cs) cs.roles.add("SUPERVISOR") result_course_sessions[cs.id] = cs # enrich with mentor course sessions lm_qs = LearningMentor.objects.filter(mentor=user).prefetch_related( "course_session", "course_session__course" ) for lm in lm_qs: cs = lm.course_session cs.roles = set() cs = result_course_sessions.get(cs.id, cs) cs.roles.add("LEARNING_MENTOR") result_course_sessions[cs.id] = cs return [ CourseSessionWithRoles(cs, cs.roles) for cs in result_course_sessions.values() ] def has_cs_role(roles: Set[str]) -> bool: return bool(roles & {"SUPERVISOR", "EXPERT", "MEMBER"}) def user_role(roles: Set[str]) -> str: return ( "SUPERVISOR" if "SUPERVISOR" in roles else ("EXPERT" if "EXPERT" in roles else "MEMBER") ) @api_view(["GET"]) def get_dashboard_persons(request): try: course_sessions = get_course_sessions_with_roles_for_user(request.user) result_persons = {} for cs in course_sessions: if has_cs_role(cs.roles) and cs.course.configuration.is_uk: course_session_users = CourseSessionUser.objects.filter( course_session=cs.id ) my_role = user_role(cs.roles) for csu in course_session_users: result_persons[csu.user.id] = { "user_id": csu.user.id, "first_name": csu.user.first_name, "last_name": csu.user.last_name, "email": csu.user.email, "course_sessions": [ { "id": cs.id, "session_title": cs.title, "course_id": cs.course.id, "course_title": cs.course.title, "course_slug": cs.course.slug, "user_role": csu.role, "my_role": my_role, "is_uk": cs.course.configuration.is_uk, "is_vv": cs.course.configuration.is_vv, } ], } # add persons where request.user is mentor for cs in course_sessions: if "LEARNING_MENTOR" in cs.roles: lm = LearningMentor.objects.filter( mentor=request.user, course_session=cs.id ).first() for participant in lm.participants.all(): course_session_entry = { "id": cs.id, "session_title": cs.title, "course_id": cs.course.id, "course_title": cs.course.title, "course_slug": cs.course.slug, "user_role": "LEARNING_MENTEE", "my_role": "LEARNING_MENTOR", "is_uk": cs.course.configuration.is_uk, "is_vv": cs.course.configuration.is_vv, } if participant.user.id not in result_persons: result_persons[participant.user.id] = { "user_id": participant.user.id, "first_name": participant.user.first_name, "last_name": participant.user.last_name, "email": participant.user.email, "course_sessions": [course_session_entry], } else: # user is already in result_persons result_persons[participant.user.id]["course_sessions"].append( course_session_entry ) # add persons where request.user is mentee mentor_relation_qs = LearningMentor.objects.filter( participants__user=request.user ).prefetch_related("mentor", "course_session") for mentor_relation in mentor_relation_qs: cs = mentor_relation.course_session course_session_entry = { "id": cs.id, "session_title": cs.title, "course_id": cs.course.id, "course_title": cs.course.title, "course_slug": cs.course.slug, "user_role": "LEARNING_MENTOR", "my_role": "LEARNING_MENTEE", "is_uk": cs.course.configuration.is_uk, "is_vv": cs.course.configuration.is_vv, } if mentor_relation.mentor.id not in result_persons: result_persons[mentor_relation.mentor.id] = { "user_id": mentor_relation.mentor.id, "first_name": mentor_relation.mentor.first_name, "last_name": mentor_relation.mentor.last_name, "email": mentor_relation.mentor.email, "course_sessions": [course_session_entry], } else: # user is already in result_persons result_persons[mentor_relation.mentor.id]["course_sessions"].append( course_session_entry ) return Response( status=200, data=list(result_persons.values()), ) except PermissionDenied as e: raise e except Exception as e: logger.error(e, exc_info=True) return Response({"error": str(e)}, status=404) def get_widgets_for_course( role_key: RoleKeyType, is_uk: bool, is_vv: bool, is_mentor: bool ) -> List[str]: widgets = [] if role_key == RoleKeyType.MEMBER: widgets.append(WidgetType.PROGRESS_WIDGET.value) widgets.append(WidgetType.COMPETENCE_WIDGET.value) if is_uk: widgets.append(WidgetType.COMPETENCE_CERTIFICATE_WIDGET.value) if role_key in [RoleKeyType.SUPERVISOR, RoleKeyType.TRAINER] and is_uk: widgets.append(WidgetType.UK_STATISTICS_WIDGET.value) if is_mentor: widgets.append(WidgetType.MENTOR_PERSON_WIDGET.value) if is_vv: widgets.append(WidgetType.MENTOR_TASKS_WIDGET.value) if is_uk: widgets.append(WidgetType.MENTOR_COMPETENCE_WIDGET.value) return widgets def get_role_key_and_mentor( course_sessions: List[CourseSessionWithRoles], is_uk: bool, is_vv: bool ) -> tuple[RoleKeyType, bool]: roles = set() role = None for cs in course_sessions: roles.update(cs.roles) if "SUPERVISOR" in roles: role = RoleKeyType.SUPERVISOR elif "EXPERT" in roles: role = RoleKeyType.TRAINER elif "MEMBER" in roles: role = RoleKeyType.MEMBER elif "LEARNING_MENTOR" in roles: if is_uk: role = RoleKeyType.MENTOR_UK elif is_vv: role = RoleKeyType.MENTOR_VV is_mentor = "LEARNING_MENTOR" in roles return role, is_mentor def collect_course_sessions_by_course( course_sessions: List[CourseSessionWithRoles], ) -> dict: course_sessions_by_course = {} for cs in course_sessions: if cs.course.id not in course_sessions_by_course: course_sessions_by_course[cs.course.id] = [] course_sessions_by_course[cs.course.id].append(cs) return course_sessions_by_course def has_preview(role_key: RoleKeyType) -> bool: return ( role_key in [RoleKeyType.MENTOR_VV, RoleKeyType.MENTOR_UK] and not role_key == RoleKeyType.MEMBER ) def get_newest_cs( course_sessions: List[CourseSessionWithRoles], ) -> CourseSessionWithRoles | None: newest: CourseSessionWithRoles | None = None for cs in course_sessions: generation_newest = newest.generation if newest else None if generation_newest is None or cs.generation > generation_newest: newest = cs return newest def get_course_config( course_sessions: List[CourseSessionWithRoles], ) -> List[CourseConfig]: course_configs = [] cs_by_course = collect_course_sessions_by_course(course_sessions) for _id, cs_in_course in cs_by_course.items(): is_uk = cs_in_course[0].course.configuration.is_uk is_vv = cs_in_course[0].course.configuration.is_vv role_key, is_mentor = get_role_key_and_mentor(cs_in_course, is_uk, is_vv) session_to_continue = get_newest_cs(cs_in_course) course_configs.append( CourseConfig( course_id=str(cs_in_course[0].course.id), course_slug=cs_in_course[0].course.slug, course_title=cs_in_course[0].course.title, role_key=role_key.value, is_uk=is_uk, is_vv=is_vv, is_mentor=is_mentor, widgets=get_widgets_for_course(role_key, is_uk, is_vv, is_mentor), has_preview=has_preview(role_key), session_to_continue_id=str(session_to_continue.id) if session_to_continue else None, ) ) return course_configs @api_view(["GET"]) def get_dashboard_config(request): try: course_sessions = get_course_sessions_with_roles_for_user(request.user) # noqa course_configs = get_course_config(course_sessions) return Response( status=200, data=[asdict(cc) for cc in course_configs], ) except PermissionDenied as e: raise e except Exception as e: logger.error(e, exc_info=True) return Response({"error": str(e)}, status=404) @api_view(["GET"]) def get_mentee_count(request, course_id: str): try: return Response( status=200, data={"mentee_count": _get_mentee_count(course_id, request.user)}, # noqa ) except PermissionDenied as e: raise e except Exception as e: logger.error(e, exc_info=True) return Response({"error": str(e)}, status=404) def _get_mentee_count(course_id: str, mentor: User) -> int: return CourseSessionUser.objects.filter( participants__mentor=mentor, course_session__course__id=course_id ).count() @api_view(["GET"]) def get_mentor_open_tasks_count(request, course_id: str): try: return Response( status=200, data={ "open_task_count": _get_mentor_open_tasks_count( course_id, request.user ) # noqa }, ) except PermissionDenied as e: raise e except Exception as e: logger.error(e, exc_info=True) return Response({"error": str(e)}, status=404) def _get_mentor_open_tasks_count(course_id: str, mentor: User) -> int: open_assigment_count = 0 open_feedback_count = 0 course_configuration = CourseConfiguration.objects.get(course_id=course_id) if course_configuration.is_vv: open_assigment_count = AssignmentCompletion.objects.filter( course_session__course__id=course_id, completion_status=AssignmentCompletionStatus.SUBMITTED.value, evaluation_user=mentor, # noqa assignment_user__coursesessionuser__participants__mentor=mentor, ).count() open_feedback_count = SelfEvaluationFeedback.objects.filter( feedback_provider_user=mentor, # noqa feedback_requester_user__coursesessionuser__participants__mentor=mentor, feedback_submitted=False, ).count() return open_assigment_count + open_feedback_count