vbv/server/vbv_lernwelt/dashboard/graphql/queries.py

343 lines
12 KiB
Python

from typing import Dict, List, Set, Tuple
import graphene
from vbv_lernwelt.assignment.models import (
AssignmentCompletion,
AssignmentCompletionStatus,
)
from vbv_lernwelt.core.admin import User
from vbv_lernwelt.course.consts import UK_COURSE_IDS
from vbv_lernwelt.course.models import Course, CourseSession, CourseSessionUser
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
from vbv_lernwelt.dashboard.graphql.types.competence import competences
from vbv_lernwelt.dashboard.graphql.types.dashboard import (
BaseStatisticsType,
CourseProgressType,
CourseStatisticsType,
DashboardConfigType,
DashboardType,
ProgressDashboardAssignmentType,
ProgressDashboardCompetenceType,
)
from vbv_lernwelt.iam.permissions import (
can_view_course_session,
can_view_course_session_group_statistics,
can_view_course_session_progress,
)
from vbv_lernwelt.learning_mentor.models import (
AgentParticipantRelation,
AgentParticipantRoleType,
)
from vbv_lernwelt.learnpath.models import Circle
def _agent_course_statistics(user, course_id: str, role: str):
course = Course.objects.get(id=course_id)
participant_ids = set()
course_session_ids = set()
relations_qs = AgentParticipantRelation.objects.filter(
agent=user,
role=role,
participant__course_session__course=course,
)
for relation in relations_qs:
participant_ids.add(relation.participant.user_id)
course_session_ids.add(relation.participant.course_session_id)
return CourseStatisticsType(
_id=f"{role}:{course.id}", # noqa
course_id=course.id, # noqa
course_title=course.title, # noqa
course_slug=course.slug, # noqa
course_session_selection_ids=list(course_session_ids), # noqa
user_selection_ids=list(participant_ids), # noqa
)
class DashboardQuery(graphene.ObjectType):
course_statistics = graphene.Field(
CourseStatisticsType, course_id=graphene.ID(required=True)
)
mentor_course_statistics = graphene.Field(
BaseStatisticsType,
course_id=graphene.ID(required=True),
agent_role=graphene.String(required=True),
)
course_progress = graphene.Field(
CourseProgressType, course_id=graphene.ID(required=True)
)
dashboard_config = graphene.List(
graphene.NonNull(DashboardConfigType), required=True
)
def resolve_course_statistics(root, info, course_id: str): # noqa
user = info.context.user
course = Course.objects.get(id=course_id)
course_session_ids = set()
# supervisors
for group in CourseSessionGroup.objects.filter(course=course):
if can_view_course_session_group_statistics(user=user, group=group):
course_session_ids.update(
group.course_session.all().values_list("id", flat=True)
)
# let's assume that users are not supervisors in one region/group and trainer in another
if not course_session_ids:
circle_ids = set()
circle_ids.update(
Circle.objects.filter(
expert__user=user, expert__role=CourseSessionUser.Role.EXPERT
).values_list("id", flat=True)
)
if not circle_ids:
return None
course_session_ids = CourseSession.objects.filter(
course=course,
coursesessionuser__user=user,
coursesessionuser__role=CourseSessionUser.Role.EXPERT,
).values_list("id", flat=True)
setattr(info.context, "circle_ids", list(circle_ids)) # noqa: B010
# todo: if course_session_ids and circles are empty return none or 404 or 401
return CourseStatisticsType(
_id=course.id, # noqa
course_id=course.id, # noqa
course_title=course.title, # noqa
course_slug=course.slug, # noqa
course_session_selection_ids=list(course_session_ids), # noqa
)
def resolve_mentor_course_statistics(root, info, course_id: str, agent_role: str): # noqa
user = info.context.user
return _agent_course_statistics(user, course_id, role=agent_role)
def resolve_dashboard_config(root, info): # noqa
user = info.context.user
if user.is_superuser:
return [
{
"id": c.id,
"course_id": c.id,
"name": c.title,
"slug": c.slug,
"dashboard_type": DashboardType.SIMPLE_DASHBOARD,
"course_configuration": c.configuration,
}
for c in Course.objects.all()
]
(
statistic_dashboards,
statistics_dashboard_course_ids,
) = get_user_statistics_dashboards(user=user)
(
course_session_dashboards,
course_session_dashboard_course_ids,
) = get_user_course_session_dashboards(
user=user, exclude_course_ids=statistics_dashboard_course_ids
)
learning_mentor_dashboards, _ = get_learning_mentor_dashboards(
user=user, exclude_course_ids=course_session_dashboard_course_ids
)
return (
statistic_dashboards
+ course_session_dashboards
+ learning_mentor_dashboards
)
def resolve_course_progress(root, info, course_id: str): # noqa
"""
Slightly fragile but could be good enough: most only have one
course session per course anyway but if there are multiple, we
just pick the newest one (by generation) as best guess.
"""
user = info.context.user
course = Course.objects.get(id=course_id)
setattr(info.context, "course", course) # noqa: B010
newest: CourseSession | None = None
course_session_for_user: List[str] = []
# generation
for course_session in CourseSession.objects.filter(course_id=course_id):
if can_view_course_session_progress(
user=user, course_session=course_session
):
course_session_for_user.append(course_session)
generation_newest = newest.generation if newest else None
if (
generation_newest is None
or course_session.generation > generation_newest
):
newest = course_session
# competence
_, success_total, fail_total = competences(
course_slug=str(course.slug),
course_session_selection_ids=course_session_for_user,
user_selection_ids=[str(user.id)],
)
# assignment
evaluation_results = AssignmentCompletion.objects.filter(
completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value,
assignment_user=user,
course_session__course=course,
).values(
"evaluation_max_points", "evaluation_points", "evaluation_points_deducted"
)
evaluation_results = list(evaluation_results)
points_max_count = sum(
[result.get("evaluation_max_points", 0) for result in evaluation_results]
)
points_achieved_count = sum(
[
(
result.get("evaluation_points", 0)
- result.get("evaluation_points_deducted", 0)
)
for result in evaluation_results
]
)
return CourseProgressType(
_id=course_id, # noqa
course_id=course_id, # noqa
session_to_continue_id=newest.id if newest else None, # noqa
competence=ProgressDashboardCompetenceType( # noqa
_id=course_id, # noqa
total_count=success_total + fail_total, # noqa
success_count=success_total, # noqa
fail_count=fail_total, # noqa
),
assignment=ProgressDashboardAssignmentType( # noqa
_id=course_id, # noqa
total_count=len(evaluation_results), # noqa
points_max_count=int(points_max_count), # noqa
points_achieved_count=int(points_achieved_count), # noqa
),
)
def get_user_statistics_dashboards(user: User) -> Tuple[List[Dict[str, str]], Set[int]]:
course_ids = set()
dashboards = []
for group in CourseSessionGroup.objects.all():
if can_view_course_session_group_statistics(user=user, group=group):
course = group.course
course_ids.add(course)
dashboards.append(
{
"id": str(course.id),
"name": course.title,
"slug": course.slug,
"dashboard_type": DashboardType.STATISTICS_DASHBOARD,
"course_configuration": course.configuration,
}
)
return dashboards, course_ids
def get_learning_mentor_dashboards(
user: User, exclude_course_ids: Set[int]
) -> Tuple[List[Dict[str, str]], Set[int]]:
learning_mentor_relation_qs = AgentParticipantRelation.objects.filter(
agent=user, role=AgentParticipantRoleType.LEARNING_MENTOR.value
).exclude(participant__course_session__course__id__in=exclude_course_ids)
dashboards = []
course_ids = set()
for rel in learning_mentor_relation_qs:
course = rel.participant.course_session.course
if course.id in UK_COURSE_IDS:
dashboard_type = DashboardType.PRAXISBILDNER_DASHBOARD
else:
dashboard_type = DashboardType.MENTOR_DASHBOARD
if course.id not in course_ids:
course_ids.add(course.id)
dashboards.append(
{
"id": str(course.id),
"name": course.title,
"slug": course.slug,
"dashboard_type": dashboard_type,
"course_configuration": course.configuration,
}
)
return dashboards, course_ids
def get_user_course_session_dashboards(
user: User, exclude_course_ids: Set[int]
) -> Tuple[List[Dict[str, str]], Set[int]]:
"""
Edge case: what do we show to users with access to multiple
sessions of a course, but with varying permissions?
-> We just show the simple list dashboard for now.
"""
dashboards = []
course_ids = set()
course_sessions = CourseSession.objects.exclude(course__in=exclude_course_ids)
roles_by_course: Dict[Course, Set[DashboardType]] = {}
for course_session in course_sessions:
if can_view_course_session(user=user, course_session=course_session):
role = CourseSessionUser.objects.get(
course_session=course_session, user=user
).role
roles_by_course.setdefault(course_session.course, set())
roles_by_course[course_session.course].add(role)
for course, roles in roles_by_course.items():
resolved_dashboard_type = None
if len(roles) == 1:
course_role = roles.pop()
if course_role == CourseSessionUser.Role.EXPERT:
resolved_dashboard_type = DashboardType.SIMPLE_DASHBOARD
elif course_role == CourseSessionUser.Role.MEMBER:
resolved_dashboard_type = DashboardType.PROGRESS_DASHBOARD
else:
# fallback: just go with simple list dashboard
resolved_dashboard_type = DashboardType.SIMPLE_DASHBOARD
course_ids.add(course.id)
dashboards.append(
{
"id": str(course.id),
"name": course.title,
"slug": course.slug,
"dashboard_type": resolved_dashboard_type,
"course_configuration": course.configuration,
}
)
return dashboards, course_ids