544 lines
18 KiB
Python
544 lines
18 KiB
Python
import base64
|
|
from dataclasses import asdict, dataclass
|
|
from datetime import date
|
|
from enum import Enum
|
|
from typing import List, Tuple
|
|
|
|
from django.db.models import Q
|
|
from django.http import HttpResponse
|
|
from rest_framework import status
|
|
from rest_framework.decorators import api_view
|
|
from rest_framework.exceptions import PermissionDenied
|
|
from rest_framework.response import Response
|
|
|
|
from vbv_lernwelt.assignment.export import (
|
|
COMPETENCE_ELEMENT_EXPORT_FILE_NAME,
|
|
export_competence_elements,
|
|
)
|
|
from vbv_lernwelt.assignment.models import (
|
|
AssignmentCompletion,
|
|
AssignmentCompletionStatus,
|
|
)
|
|
from vbv_lernwelt.competence.services import (
|
|
query_competence_course_session_assignments,
|
|
query_competence_course_session_edoniq_tests,
|
|
)
|
|
from vbv_lernwelt.core.models import User
|
|
from vbv_lernwelt.course.models import CourseConfiguration, CourseSessionUser
|
|
from vbv_lernwelt.course.views import logger
|
|
from vbv_lernwelt.course_session.services.export_attendance import (
|
|
ATTENDANCE_EXPORT_FILENAME,
|
|
export_attendance,
|
|
make_export_filename,
|
|
)
|
|
from vbv_lernwelt.dashboard.person_export import PERSONS_EXPORT_FILENAME, export_persons
|
|
from vbv_lernwelt.dashboard.utils import (
|
|
CourseSessionWithRoles,
|
|
create_course_session_dict,
|
|
create_person_list_with_roles,
|
|
get_course_sessions_with_roles_for_user,
|
|
user_role,
|
|
)
|
|
from vbv_lernwelt.duedate.models import DueDate
|
|
from vbv_lernwelt.duedate.serializers import DueDateSerializer
|
|
from vbv_lernwelt.feedback.export import (
|
|
FEEDBACK_EXPORT_FILE_NAME,
|
|
export_feedback_with_circle_restriction,
|
|
)
|
|
from vbv_lernwelt.learning_mentor.models import (
|
|
AgentParticipantRelation,
|
|
AgentParticipantRoleType,
|
|
)
|
|
from vbv_lernwelt.learnpath.models import Circle
|
|
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"
|
|
UK_BERUFSBILDNER_STATISTICS_WIDGET = "UKBerufsbildnerStatisticsWidget"
|
|
|
|
|
|
class RoleKeyType(Enum):
|
|
MEMBER = "Member"
|
|
MENTOR_VV = "MentorVV"
|
|
MENTOR_UK = "MentorUK"
|
|
SUPERVISOR = "Supervisor"
|
|
TRAINER = "Trainer"
|
|
BERUFSBILDNER = "Berufsbildner"
|
|
UNKNOWN_ROLE_KEY = "UnknownRoleKey"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CourseConfig:
|
|
course_id: str
|
|
course_slug: str
|
|
course_title: str
|
|
role_key: str
|
|
is_uk: bool
|
|
is_vv: bool
|
|
widgets: List[str]
|
|
has_preview: bool
|
|
session_to_continue_id: str | None
|
|
|
|
|
|
def _persons_list_add_competence_metrics(persons):
|
|
course_session_ids = {cs["id"] for p in persons for cs in p["course_sessions"]}
|
|
competence_assignments = query_competence_course_session_assignments(
|
|
course_session_ids
|
|
) + query_competence_course_session_edoniq_tests(course_session_ids)
|
|
assignment_ids = {
|
|
a.learning_content.content_assignment.id for a in competence_assignments
|
|
}
|
|
|
|
for p in persons:
|
|
passed_count = 0
|
|
failed_count = 0
|
|
for cs in p["course_sessions"]:
|
|
evaluation_results = AssignmentCompletion.objects.filter(
|
|
completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value,
|
|
assignment_user_id=p.get("user_id"),
|
|
course_session=cs.get("id"),
|
|
assignment_id__in=assignment_ids,
|
|
)
|
|
cs_passed_count = len(
|
|
[ac for ac in evaluation_results if ac.evaluation_passed]
|
|
)
|
|
cs_failed_count = len(evaluation_results) - cs_passed_count
|
|
cs["competence_metrics"] = {
|
|
"passed_count": cs_passed_count,
|
|
"failed_count": cs_failed_count,
|
|
}
|
|
passed_count += len(
|
|
[ac for ac in evaluation_results if ac.evaluation_passed]
|
|
)
|
|
failed_count += len(evaluation_results) - passed_count
|
|
p["competence_metrics"] = {
|
|
"passed_count": passed_count,
|
|
"failed_count": failed_count,
|
|
}
|
|
|
|
return persons
|
|
|
|
|
|
@api_view(["GET"])
|
|
def get_dashboard_persons(request):
|
|
try:
|
|
persons = list(create_person_list_with_roles(request.user))
|
|
|
|
if request.GET.get("with_competence_metrics", "") == "true":
|
|
persons = _persons_list_add_competence_metrics(persons)
|
|
|
|
return Response(
|
|
status=200,
|
|
data=list(persons),
|
|
)
|
|
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_dashboard_due_dates(request):
|
|
try:
|
|
course_sessions = get_course_sessions_with_roles_for_user(request.user)
|
|
course_session_ids = [cs.id for cs in course_sessions]
|
|
|
|
today = date.today()
|
|
|
|
# Fetch future due dates in a single query using Q objects for complex filtering
|
|
future_due_dates = DueDate.objects.filter(
|
|
Q(course_session_id__in=course_session_ids),
|
|
Q(end__gte=today) | Q(start__gte=today),
|
|
).select_related("course_session")
|
|
|
|
result_due_dates = []
|
|
course_session_map = {cs.id: cs for cs in course_sessions}
|
|
|
|
for due_date in sorted(future_due_dates, key=lambda x: x.start):
|
|
data = DueDateSerializer(due_date).data
|
|
cs = course_session_map.get(due_date.course_session_id)
|
|
|
|
if cs:
|
|
data["course_session"] = create_course_session_dict(
|
|
cs, my_role=user_role(cs.roles), user_role=""
|
|
)
|
|
result_due_dates.append(data)
|
|
|
|
return Response(status=200, data=result_due_dates)
|
|
|
|
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(
|
|
course_sessions: List[CourseSessionWithRoles], is_uk: bool, is_vv: bool
|
|
) -> List[str]:
|
|
relation_roles = set()
|
|
for cs in course_sessions:
|
|
relation_roles.update(cs.roles)
|
|
|
|
widgets = []
|
|
|
|
if "MEMBER" in relation_roles:
|
|
widgets.append(WidgetType.PROGRESS_WIDGET.value)
|
|
widgets.append(WidgetType.COMPETENCE_WIDGET.value)
|
|
if is_uk:
|
|
widgets.append(WidgetType.COMPETENCE_CERTIFICATE_WIDGET.value)
|
|
|
|
if "EXPERT" in relation_roles or "SUPERVISOR" in relation_roles:
|
|
if is_uk:
|
|
widgets.append(WidgetType.UK_STATISTICS_WIDGET.value)
|
|
|
|
if "LEARNING_MENTOR" in relation_roles:
|
|
widgets.append(WidgetType.MENTOR_PERSON_WIDGET.value)
|
|
if is_uk:
|
|
widgets.append(WidgetType.MENTOR_COMPETENCE_WIDGET.value)
|
|
elif is_vv:
|
|
widgets.append(WidgetType.MENTOR_TASKS_WIDGET.value)
|
|
|
|
if "BERUFSBILDNER" in relation_roles:
|
|
if is_uk:
|
|
widgets.append(WidgetType.UK_BERUFSBILDNER_STATISTICS_WIDGET.value)
|
|
|
|
return widgets
|
|
|
|
|
|
def get_relevant_role_key(
|
|
course_sessions: List[CourseSessionWithRoles], is_uk: bool, is_vv: bool
|
|
) -> RoleKeyType:
|
|
relation_roles = set()
|
|
|
|
for cs in course_sessions:
|
|
relation_roles.update(cs.roles)
|
|
|
|
if "SUPERVISOR" in relation_roles:
|
|
return RoleKeyType.SUPERVISOR
|
|
elif "EXPERT" in relation_roles:
|
|
return RoleKeyType.TRAINER
|
|
elif "MEMBER" in relation_roles:
|
|
return RoleKeyType.MEMBER
|
|
|
|
elif "LEARNING_MENTOR" in relation_roles:
|
|
if is_uk:
|
|
return RoleKeyType.MENTOR_UK
|
|
elif is_vv:
|
|
return RoleKeyType.MENTOR_VV
|
|
elif "BERUFSBILDNER" in relation_roles:
|
|
if is_uk:
|
|
return RoleKeyType.BERUFSBILDNER
|
|
|
|
return RoleKeyType.UNKNOWN_ROLE_KEY
|
|
|
|
|
|
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 = get_relevant_role_key(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,
|
|
widgets=get_widgets_for_course(cs_in_course, is_uk, is_vv),
|
|
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 AgentParticipantRelation.objects.filter(
|
|
agent=mentor,
|
|
participant__course_session__course_id=course_id,
|
|
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
|
|
).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=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
|
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)
|
|
|
|
learning_meentee_ids = [
|
|
str(relation.participant.user_id)
|
|
for relation in AgentParticipantRelation.objects.filter(
|
|
agent=mentor,
|
|
participant__course_session__course_id=course_id,
|
|
role=AgentParticipantRoleType.LEARNING_MENTOR.value,
|
|
).prefetch_related("participant")
|
|
]
|
|
|
|
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_id__in=learning_meentee_ids,
|
|
).count()
|
|
|
|
open_feedback_qs = SelfEvaluationFeedback.objects.filter(
|
|
feedback_provider_user=mentor, # noqa
|
|
feedback_requester_user_id__in=learning_meentee_ids,
|
|
feedback_submitted=False,
|
|
)
|
|
# filter open feedbacks for course_id (-> not possible with queryset)
|
|
open_feedback_count = len(
|
|
[
|
|
feedback_entry
|
|
for feedback_entry in open_feedback_qs
|
|
if str(feedback_entry.learning_unit.get_course().id) == course_id
|
|
]
|
|
)
|
|
|
|
return open_assigment_count + open_feedback_count
|
|
|
|
|
|
@api_view(["POST"])
|
|
def export_attendance_as_xsl(request):
|
|
circle_ids = request.data.get("circleIds", None)
|
|
requested_course_session_ids = request.data.get("courseSessionIds", [])
|
|
course_sessions_with_roles = _get_permitted_courses_sessions_for_user(
|
|
request.user, requested_course_session_ids
|
|
)
|
|
data = export_attendance(
|
|
[cs.id for cs in course_sessions_with_roles],
|
|
circle_ids=circle_ids,
|
|
)
|
|
return _make_excel_response(data, file_name=ATTENDANCE_EXPORT_FILENAME)
|
|
|
|
|
|
@api_view(["POST"])
|
|
def export_competence_elements_as_xsl(request):
|
|
circle_ids = request.data.get("circleIds", None)
|
|
requested_course_session_ids = request.data.get("courseSessionIds", [])
|
|
|
|
(
|
|
course_session_ids,
|
|
participant_user_ids,
|
|
) = _filter_permitted_course_session_and_user_ids(
|
|
request.user, requested_course_session_ids
|
|
)
|
|
|
|
data = export_competence_elements(
|
|
course_session_ids=list(course_session_ids),
|
|
circle_ids=circle_ids,
|
|
user_ids=list(participant_user_ids),
|
|
)
|
|
return _make_excel_response(data, COMPETENCE_ELEMENT_EXPORT_FILE_NAME)
|
|
|
|
|
|
@api_view(["POST"])
|
|
def export_feedback_as_xsl(request):
|
|
circle_ids = request.data.get("circleIds", None)
|
|
requested_course_session_ids = request.data.get("courseSessionIds", [])
|
|
course_sessions_with_roles = _get_permitted_courses_sessions_for_user(
|
|
request.user, requested_course_session_ids
|
|
) # noqa
|
|
|
|
allowed_circles = _get_permitted_circles_ids_for_user_and_course_session(
|
|
request.user,
|
|
course_sessions_with_roles,
|
|
circle_ids,
|
|
) # noqa
|
|
|
|
data = export_feedback_with_circle_restriction(allowed_circles, False)
|
|
return _make_excel_response(data, FEEDBACK_EXPORT_FILE_NAME)
|
|
|
|
|
|
@api_view(["POST"])
|
|
def export_persons_as_xsl(request):
|
|
requested_course_session_ids = request.data.get("courseSessionIds", [])
|
|
course_sessions_with_roles = _get_permitted_courses_sessions_for_user(
|
|
request.user, requested_course_session_ids
|
|
) # noqa
|
|
|
|
data = export_persons(
|
|
request.user,
|
|
[cswr.id for cswr in course_sessions_with_roles],
|
|
)
|
|
return _make_excel_response(data, PERSONS_EXPORT_FILENAME)
|
|
|
|
|
|
def _get_permitted_courses_sessions_for_user(
|
|
user: User, requested_coursesession_ids: List[str]
|
|
) -> List[CourseSessionWithRoles]:
|
|
ALLOWED_ROLES = ["EXPERT", "SUPERVISOR"]
|
|
|
|
user_course_sessions_with_roles = _get_course_sessions_with_roles_for_user(
|
|
user, ALLOWED_ROLES, requested_coursesession_ids
|
|
) # noqa
|
|
|
|
return user_course_sessions_with_roles
|
|
|
|
|
|
def _filter_permitted_course_session_and_user_ids(
|
|
user: User, requested_course_session_ids: List[str]
|
|
):
|
|
course_session_ids = set([])
|
|
participant_user_ids = set([])
|
|
|
|
for person in create_person_list_with_roles(
|
|
user, course_session_ids=requested_course_session_ids
|
|
):
|
|
for cs in person["course_sessions"]:
|
|
if str(cs["id"]) in [str(i) for i in requested_course_session_ids]:
|
|
if cs["my_role"] in ["SUPERVISOR", "EXPERT", "BERUFSBILDNER"]:
|
|
course_session_ids.add(cs["id"])
|
|
participant_user_ids.add(person["user_id"])
|
|
|
|
return course_session_ids, participant_user_ids
|
|
|
|
|
|
def _make_excel_response(data: bytes, file_name: str) -> HttpResponse:
|
|
encoded_data = base64.b64encode(data).decode("utf-8")
|
|
|
|
# Create the JSON response
|
|
response_data = {
|
|
"encoded_data": encoded_data,
|
|
"file_name": make_export_filename(file_name),
|
|
}
|
|
|
|
return Response(response_data, status=200)
|
|
|
|
|
|
def _get_course_sessions_with_roles_for_user(
|
|
user: User, allowed_roles: List[str], requested_cs_ids: List[str]
|
|
) -> List[CourseSessionWithRoles]:
|
|
all_cs_roles_for_user = [
|
|
csr
|
|
for csr in get_course_sessions_with_roles_for_user(user)
|
|
if any(role in allowed_roles for role in csr.roles)
|
|
and csr.id in requested_cs_ids
|
|
] # noqa
|
|
|
|
return all_cs_roles_for_user
|
|
|
|
|
|
def _get_permitted_circles_ids_for_user_and_course_session(
|
|
user: User,
|
|
user_course_sessions_with_roles: List[CourseSessionWithRoles],
|
|
requested_circle_ids: Tuple[int, int],
|
|
):
|
|
allowed_circles_for_sessions = []
|
|
for cswr in user_course_sessions_with_roles:
|
|
if "SUPERVISOR" in cswr.roles:
|
|
allowed_circles_for_sessions.append((cswr.id, requested_circle_ids))
|
|
else:
|
|
course_session_users = CourseSessionUser.objects.filter(
|
|
course_session=cswr.id,
|
|
user=user,
|
|
)
|
|
allowed_circles = (
|
|
Circle.objects.filter(
|
|
Q(expert__in=course_session_users) & Q(id__in=requested_circle_ids)
|
|
)
|
|
.distinct()
|
|
.values_list("id", flat=True)
|
|
)
|
|
allowed_circles_for_sessions.append((cswr.id, list(allowed_circles)))
|
|
|
|
return allowed_circles_for_sessions
|