281 lines
9.0 KiB
Python
281 lines
9.0 KiB
Python
from datetime import datetime
|
|
from typing import NamedTuple, List
|
|
|
|
from graphene_django.utils import GraphQLTestCase
|
|
|
|
from vbv_lernwelt.assignment.models import (
|
|
AssignmentType,
|
|
AssignmentCompletion,
|
|
Assignment,
|
|
AssignmentCompletionStatus,
|
|
)
|
|
from vbv_lernwelt.assignment.tests.assignment_factories import (
|
|
AssignmentFactory,
|
|
AssignmentListPageFactory,
|
|
)
|
|
from vbv_lernwelt.course.models import CourseSessionUser, CourseSession
|
|
from vbv_lernwelt.course_session.models import (
|
|
CourseSessionAssignment,
|
|
CourseSessionEdoniqTest,
|
|
)
|
|
from vbv_lernwelt.dashboard.tests.graphql.utils import (
|
|
add_course_session_user,
|
|
create_circle,
|
|
create_course,
|
|
create_course_session,
|
|
create_user,
|
|
)
|
|
from vbv_lernwelt.learnpath.tests.learning_path_factories import (
|
|
LearningContentAssignmentFactory,
|
|
LearningContentEdoniqTestFactory,
|
|
)
|
|
from vbv_lernwelt.notify.email.email_services import format_swiss_datetime
|
|
|
|
|
|
class AssignmentCompletionMetrics(NamedTuple):
|
|
passed: int
|
|
failed: int
|
|
unranked: int
|
|
|
|
@property
|
|
def ranking_completed(self) -> bool:
|
|
"""
|
|
Assumption: Completed means all users have been ranked
|
|
(passed or failed) -> intermediate states are irrelevant
|
|
for the course session supervisor.
|
|
"""
|
|
return self.unranked == 0
|
|
|
|
@property
|
|
def average_passed(self) -> float:
|
|
total = self.passed + self.failed + self.unranked
|
|
|
|
if total == 0:
|
|
return 0
|
|
|
|
return self.passed / total
|
|
|
|
|
|
class AssignmentCompletionsStatistics(NamedTuple):
|
|
count_completed: int
|
|
average_passed: float
|
|
|
|
|
|
def calculate_assignment_completions_statistics(
|
|
metrics: List[AssignmentCompletionMetrics],
|
|
) -> AssignmentCompletionsStatistics:
|
|
completed_metrics = [m for m in metrics if m.ranking_completed]
|
|
|
|
if not completed_metrics:
|
|
return AssignmentCompletionsStatistics(count_completed=0, average_passed=0)
|
|
|
|
count_completed = len(completed_metrics)
|
|
|
|
average_passed_completed = (
|
|
sum([m.average_passed for m in completed_metrics]) / count_completed
|
|
)
|
|
|
|
return AssignmentCompletionsStatistics(
|
|
count_completed=count_completed, average_passed=average_passed_completed
|
|
)
|
|
|
|
|
|
def get_assignment_completion_metrics(
|
|
course_session: CourseSession, assignment: Assignment
|
|
) -> AssignmentCompletionMetrics:
|
|
course_session_users = CourseSessionUser.objects.filter(
|
|
course_session=course_session,
|
|
role=CourseSessionUser.Role.MEMBER,
|
|
).values_list("user", flat=True)
|
|
|
|
evaluation_results = AssignmentCompletion.objects.filter(
|
|
completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED,
|
|
assignment_user__in=course_session_users,
|
|
course_session=course_session,
|
|
assignment=assignment,
|
|
).values_list("evaluation_passed", flat=True)
|
|
|
|
count_passed = len([passed for passed in evaluation_results if passed])
|
|
count_failed = len(evaluation_results) - count_passed
|
|
count_unranked = len(course_session_users) - count_passed - count_failed
|
|
|
|
return AssignmentCompletionMetrics(
|
|
passed=count_passed, failed=count_failed, unranked=count_unranked
|
|
)
|
|
|
|
|
|
class DashboardAssignmentRecord(NamedTuple):
|
|
course_session_id: str
|
|
circle_id: str
|
|
|
|
# type of assignment (translated client-side)
|
|
assignment_type_translation_key: str
|
|
assignment_title: str
|
|
|
|
deadline: datetime
|
|
metrics: AssignmentCompletionMetrics
|
|
|
|
details_url: str
|
|
|
|
@property
|
|
def deadline_formatted(self) -> str:
|
|
return format_swiss_datetime(self.deadline)
|
|
|
|
|
|
def create_dashboard_record(
|
|
course_session_assignment: CourseSessionAssignment | CourseSessionEdoniqTest,
|
|
) -> DashboardAssignmentRecord:
|
|
if isinstance(course_session_assignment, CourseSessionAssignment):
|
|
due_date = course_session_assignment.submission_deadline
|
|
else:
|
|
due_date = course_session_assignment.deadline
|
|
|
|
learning_content = course_session_assignment.learning_content
|
|
|
|
return DashboardAssignmentRecord(
|
|
course_session_id=str(course_session_assignment.course_session.id),
|
|
circle_id=learning_content.get_circle().id,
|
|
assignment_type_translation_key=due_date.assignment_type_translation_key,
|
|
assignment_title=learning_content.content_assignment.title,
|
|
metrics=get_assignment_completion_metrics(
|
|
course_session=course_session_assignment.course_session,
|
|
assignment=learning_content.content_assignment,
|
|
),
|
|
details_url=due_date.url_expert,
|
|
deadline=due_date.start,
|
|
)
|
|
|
|
|
|
def trybetter(course_session):
|
|
dashboard_records: List[DashboardAssignmentRecord] = []
|
|
|
|
for course_session_assignment in CourseSessionAssignment.objects.filter(
|
|
course_session=course_session,
|
|
learning_content__assignment_type__in=[
|
|
AssignmentType.CASEWORK.value,
|
|
AssignmentType.PREP_ASSIGNMENT.value,
|
|
],
|
|
):
|
|
dashboard_record = create_dashboard_record(course_session_assignment)
|
|
dashboard_records.append(dashboard_record)
|
|
|
|
for course_session_edoniq_test in CourseSessionEdoniqTest.objects.filter(
|
|
course_session=course_session
|
|
):
|
|
dashboard_record = create_dashboard_record(course_session_edoniq_test)
|
|
dashboard_records.append(dashboard_record)
|
|
|
|
return sorted(dashboard_records, key=lambda r: r.deadline)
|
|
|
|
|
|
class AssignmentTestCase(GraphQLTestCase):
|
|
GRAPHQL_URL = "/server/graphql/"
|
|
|
|
def test_assignment_something_not_yet_sure_what_exactly_who_knows(self):
|
|
# GIVEN
|
|
course, course_page = create_course("Test Course")
|
|
course_session = create_course_session(course=course, title="Test Bern 2022 a")
|
|
circle, _ = create_circle(title="Test Circle", course_page=course_page)
|
|
|
|
supervisor = create_user("supervisor")
|
|
add_course_session_user(
|
|
course_session=course_session,
|
|
user=supervisor,
|
|
role=CourseSessionUser.Role.SESSION_SUPERVISOR,
|
|
)
|
|
|
|
m1 = create_user("member_1")
|
|
add_course_session_user(
|
|
course_session=course_session,
|
|
user=m1,
|
|
role=CourseSessionUser.Role.MEMBER,
|
|
)
|
|
|
|
m2 = create_user("member_2")
|
|
add_course_session_user(
|
|
course_session=course_session,
|
|
user=m2,
|
|
role=CourseSessionUser.Role.MEMBER,
|
|
)
|
|
|
|
m3 = create_user("member_3")
|
|
add_course_session_user(
|
|
course_session=course_session,
|
|
user=m3,
|
|
role=CourseSessionUser.Role.MEMBER,
|
|
)
|
|
|
|
e1 = create_user("expert_1")
|
|
add_course_session_user(
|
|
course_session=course_session,
|
|
user=e1,
|
|
role=CourseSessionUser.Role.EXPERT,
|
|
)
|
|
|
|
assignment = AssignmentFactory(
|
|
parent=AssignmentListPageFactory(
|
|
parent=course.coursepage,
|
|
),
|
|
assignment_type=AssignmentType.CASEWORK.name,
|
|
title="Test Assignment",
|
|
effort_required="However long it takes",
|
|
intro_text="Assignment Intro Text",
|
|
performance_objectives=[],
|
|
)
|
|
|
|
edoniq_test = AssignmentFactory(
|
|
parent=AssignmentListPageFactory(
|
|
parent=course.coursepage,
|
|
),
|
|
assignment_type=AssignmentType.EDONIQ_TEST.name,
|
|
title="Edoniq Test Assignment",
|
|
effort_required="However long it takes",
|
|
intro_text="Edoniq Test Assigment Intro Text",
|
|
performance_objectives=[],
|
|
)
|
|
|
|
AssignmentCompletion.objects.create(
|
|
assignment_user=m1,
|
|
assignment=edoniq_test,
|
|
evaluation_passed=True,
|
|
course_session=course_session,
|
|
completion_data={},
|
|
)
|
|
|
|
AssignmentCompletion.objects.create(
|
|
assignment_user=m1,
|
|
assignment=assignment,
|
|
evaluation_passed=True,
|
|
course_session=course_session,
|
|
completion_data={},
|
|
)
|
|
|
|
learning_content_assignment = LearningContentAssignmentFactory(
|
|
title="Learning Content Assignment Title",
|
|
parent=circle,
|
|
content_assignment=assignment,
|
|
)
|
|
|
|
learning_content_edoniq_test = LearningContentEdoniqTestFactory(
|
|
title="Learning Content Edoniq Test Title",
|
|
parent=circle,
|
|
content_assignment=edoniq_test,
|
|
)
|
|
|
|
CourseSessionAssignment.objects.create(
|
|
course_session=course_session, learning_content=learning_content_assignment
|
|
)
|
|
|
|
CourseSessionEdoniqTest.objects.create(
|
|
course_session=course_session, learning_content=learning_content_edoniq_test
|
|
)
|
|
|
|
# ---------------------------------------------------------------------
|
|
# ---------------------------------------------------------------------
|
|
# ---------------------------------------------------------------------
|
|
# ---------------------------------------------------------------------
|
|
|
|
trybetter(course_session)
|
|
|
|
self.client.force_login(supervisor)
|