feat: adds graphql assignment dashboard

This commit is contained in:
Livio Bieri 2023-10-20 19:40:00 +02:00
parent d16bb59392
commit dc706e7ece
5 changed files with 585 additions and 228 deletions

View File

@ -0,0 +1,156 @@
import math
from typing import List
import graphene
import vbv_lernwelt.assignment.models
from vbv_lernwelt.assignment.models import (
AssignmentCompletion,
AssignmentCompletionStatus,
AssignmentType,
)
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
from vbv_lernwelt.course_session.models import (
CourseSessionAssignment,
CourseSessionEdoniqTest,
)
class AssignmentCompletionMetrics(graphene.ObjectType):
passed_count = graphene.Int(required=True)
failed_count = graphene.Int(required=True)
unranked_count = graphene.Int(required=True)
ranking_completed = graphene.Boolean(required=True)
average_passed = graphene.Float(required=True)
class AssignmentRecord(graphene.ObjectType):
course_session_id = graphene.ID(required=True)
course_session_assignment_id = graphene.ID(required=True)
circle_id = graphene.ID(required=True)
generation = graphene.String(required=True)
assignment_type_translation_key = graphene.String(required=True)
assignment_title = graphene.String(required=True)
deadline = graphene.DateTime(required=True)
metrics = graphene.Field(AssignmentCompletionMetrics, required=True)
details_url = graphene.String(required=True)
class AssignmentSummary(graphene.ObjectType):
completed_count = graphene.Int(required=True)
average_passed = graphene.Float(required=True)
class Assignments(graphene.ObjectType):
records = graphene.List(AssignmentRecord, required=True)
summary = graphene.Field(AssignmentSummary, required=True)
def create_assignment_summary(metrics) -> AssignmentSummary:
completed_metrics = [m for m in metrics if m.ranking_completed]
if not completed_metrics:
return AssignmentSummary(completed_count=0, average_passed=0) # noqa
completed_count = len(completed_metrics)
average_passed_completed = (
sum([m.average_passed for m in completed_metrics]) / completed_count
)
return AssignmentSummary(
completed_count=completed_count, average_passed=average_passed_completed # noqa
)
def get_assignment_completion_metrics(
course_session: CourseSession, assignment: vbv_lernwelt.assignment.models.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.value,
assignment_user__in=course_session_users,
course_session=course_session,
assignment=assignment,
).values_list("evaluation_passed", flat=True)
passed_count = len([passed for passed in evaluation_results if passed])
failed_count = len(evaluation_results) - passed_count
participants_count = len(course_session_users)
unranked_count = participants_count - passed_count - failed_count
if participants_count == 0:
average_passed = 0
else:
average_passed = math.ceil(passed_count / participants_count * 100)
return AssignmentCompletionMetrics(
passed_count=passed_count, # noqa
failed_count=failed_count, # noqa
unranked_count=unranked_count, # noqa
ranking_completed=unranked_count == 0, # noqa
average_passed=average_passed, # noqa
)
def create_record(
course_session_assignment: CourseSessionAssignment | CourseSessionEdoniqTest,
) -> AssignmentRecord:
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 AssignmentRecord(
course_session_id=str(course_session_assignment.course_session.id), # noqa
circle_id=learning_content.get_circle().id, # noqa
course_session_assignment_id=str(course_session_assignment.id), # noqa
generation=course_session_assignment.course_session.generation, # noqa
assignment_type_translation_key=due_date.assignment_type_translation_key, # noqa
assignment_title=learning_content.content_assignment.title, # noqa
metrics=get_assignment_completion_metrics( # noqa
course_session=course_session_assignment.course_session, # noqa
assignment=learning_content.content_assignment, # noqa
),
details_url=due_date.url_expert, # noqa
deadline=due_date.start, # noqa
)
def assignments(course_id, user) -> Assignments:
course_sessions = CourseSession.objects.filter(
coursesessionuser__user=user,
coursesessionuser__role=CourseSessionUser.Role.SESSION_SUPERVISOR,
course_id=course_id,
)
records: List[AssignmentRecord] = []
for course_session in course_sessions:
for csa in CourseSessionAssignment.objects.filter(
course_session=course_session,
learning_content__assignment_type__in=[
AssignmentType.CASEWORK.value,
AssignmentType.PREP_ASSIGNMENT.value,
],
):
record = create_record(csa)
records.append(record)
for cset in CourseSessionEdoniqTest.objects.filter(
course_session=course_session
):
record = create_record(cset)
records.append(record)
return Assignments(
records=sorted(records, key=lambda r: r.deadline), # noqa
summary=create_assignment_summary([r.metrics for r in records]), # noqa
)

View File

@ -1,8 +1,8 @@
import datetime
import math import math
from typing import List from typing import List
import graphene import graphene
from django.utils import timezone
from vbv_lernwelt.core.models import User from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import CourseSessionUser from vbv_lernwelt.course.models import CourseSessionUser
@ -38,7 +38,7 @@ def attendance_day_presences(
course_session__course_id=course_id, course_session__course_id=course_id,
course_session__coursesessionuser__user=user, course_session__coursesessionuser__user=user,
course_session__coursesessionuser__role=CourseSessionUser.Role.SESSION_SUPERVISOR, course_session__coursesessionuser__role=CourseSessionUser.Role.SESSION_SUPERVISOR,
due_date__end__lt=datetime.datetime.now(), due_date__end__lt=timezone.now(),
).order_by("-due_date__end") ).order_by("-due_date__end")
records = [] records = []

View File

@ -1,6 +1,10 @@
import graphene import graphene
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
from vbv_lernwelt.dashboard.graphql.types.assignment import (
Assignments,
assignments,
)
from vbv_lernwelt.dashboard.graphql.types.attendance import ( from vbv_lernwelt.dashboard.graphql.types.attendance import (
attendance_day_presences, attendance_day_presences,
AttendanceDayPresences, AttendanceDayPresences,
@ -36,6 +40,7 @@ class CourseDashboardType(graphene.ObjectType):
course_session_properties = graphene.Field(CourseSessionProperties) course_session_properties = graphene.Field(CourseSessionProperties)
attendance_day_presences = graphene.Field(AttendanceDayPresences) attendance_day_presences = graphene.Field(AttendanceDayPresences)
feedback_responses = graphene.Field(FeedbackResponses) feedback_responses = graphene.Field(FeedbackResponses)
assignments = graphene.Field(Assignments)
competences = graphene.Field(Competences) competences = graphene.Field(Competences)
def resolve_attendance_day_presences(root, info) -> AttendanceDayPresences: def resolve_attendance_day_presences(root, info) -> AttendanceDayPresences:
@ -47,6 +52,9 @@ class CourseDashboardType(graphene.ObjectType):
def resolve_competences(root, info) -> Competences: def resolve_competences(root, info) -> Competences:
return competences(root.course_id, info.context.user) return competences(root.course_id, info.context.user)
def resolve_assignments(root, info):
return assignments(root.course_id, info.context.user)
def resolve_course_session_properties(root, info): def resolve_course_session_properties(root, info):
course_session_data = [] course_session_data = []
circle_data = [] circle_data = []

View File

@ -1,17 +1,11 @@
from datetime import datetime from datetime import datetime
from typing import NamedTuple, List from typing import Tuple
from graphene_django.utils import GraphQLTestCase from graphene_django.utils import GraphQLTestCase
from vbv_lernwelt.assignment.models import ( from vbv_lernwelt.assignment.models import (
AssignmentType, AssignmentType,
AssignmentCompletion,
Assignment, Assignment,
AssignmentCompletionStatus,
)
from vbv_lernwelt.assignment.tests.assignment_factories import (
AssignmentFactory,
AssignmentListPageFactory,
) )
from vbv_lernwelt.course.models import CourseSessionUser, CourseSession from vbv_lernwelt.course.models import CourseSessionUser, CourseSession
from vbv_lernwelt.course_session.models import ( from vbv_lernwelt.course_session.models import (
@ -24,257 +18,349 @@ from vbv_lernwelt.dashboard.tests.graphql.utils import (
create_course, create_course,
create_course_session, create_course_session,
create_user, create_user,
create_assignment,
create_assignment_completion,
create_assignment_learning_content,
create_course_session_edoniq_test,
create_course_session_assignment,
) )
from vbv_lernwelt.learnpath.tests.learning_path_factories import ( from vbv_lernwelt.learnpath.models import Circle
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): class AssignmentTestCase(GraphQLTestCase):
GRAPHQL_URL = "/server/graphql/" GRAPHQL_URL = "/server/graphql/"
GRAPHQL_QUERY = f"""query($course_id: ID) {{
course_dashboard(course_id: $course_id) {{
assignments{{
summary{{
completed_count
average_passed
}}
records{{
course_session_id
course_session_assignment_id
circle_id
generation
assignment_title
assignment_type_translation_key
details_url
deadline
metrics {{
passed_count
failed_count
unranked_count
ranking_completed
average_passed
}}
}}
}}
}}
}}"""
def test_assignment_something_not_yet_sure_what_exactly_who_knows(self): def setUp(self):
# GIVEN self.course, self.course_page = create_course("Test Course")
course, course_page = create_course("Test Course") self.course_session = create_course_session(course=self.course, title=":)")
course_session = create_course_session(course=course, title="Test Bern 2022 a") self.circle, _ = create_circle(title="Circle", course_page=self.course_page)
circle, _ = create_circle(title="Test Circle", course_page=course_page)
supervisor = create_user("supervisor") self.supervisor = create_user("supervisor")
add_course_session_user( add_course_session_user(
course_session=course_session, course_session=self.course_session,
user=supervisor, user=self.supervisor,
role=CourseSessionUser.Role.SESSION_SUPERVISOR, role=CourseSessionUser.Role.SESSION_SUPERVISOR,
) )
m1 = create_user("member_1") self.m1 = create_user("member_1")
add_course_session_user( add_course_session_user(
course_session=course_session, course_session=self.course_session,
user=m1, user=self.m1,
role=CourseSessionUser.Role.MEMBER, role=CourseSessionUser.Role.MEMBER,
) )
m2 = create_user("member_2") self.m2 = create_user("member_2")
add_course_session_user( add_course_session_user(
course_session=course_session, course_session=self.course_session,
user=m2, user=self.m2,
role=CourseSessionUser.Role.MEMBER, role=CourseSessionUser.Role.MEMBER,
) )
m3 = create_user("member_3") self.m3 = create_user("member_3")
add_course_session_user( add_course_session_user(
course_session=course_session, course_session=self.course_session,
user=m3, user=self.m3,
role=CourseSessionUser.Role.MEMBER, role=CourseSessionUser.Role.MEMBER,
) )
e1 = create_user("expert_1") self.e1 = create_user("expert_1")
add_course_session_user( add_course_session_user(
course_session=course_session, course_session=self.course_session,
user=e1, user=self.e1,
role=CourseSessionUser.Role.EXPERT, role=CourseSessionUser.Role.EXPERT,
) )
assignment = AssignmentFactory( self.client.force_login(self.supervisor)
parent=AssignmentListPageFactory(
parent=course.coursepage, def test_dashboard_contains_casework(self):
), self._test_assignment_type_dashboard_details(
assignment_type=AssignmentType.CASEWORK.name, assignment_type=AssignmentType.CASEWORK
title="Test Assignment",
effort_required="However long it takes",
intro_text="Assignment Intro Text",
performance_objectives=[],
) )
edoniq_test = AssignmentFactory( def test_dashboard_contains_prep_assignments(self):
parent=AssignmentListPageFactory( self._test_assignment_type_dashboard_details(
parent=course.coursepage, assignment_type=AssignmentType.PREP_ASSIGNMENT
),
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( def test_dashboard_contains_edoniq_tests(self):
assignment_user=m1, self._test_assignment_type_dashboard_details(
assignment=edoniq_test, assignment_type=AssignmentType.EDONIQ_TEST
evaluation_passed=True,
course_session=course_session,
completion_data={},
) )
AssignmentCompletion.objects.create( def test_dashboard_not_contains_unsupported_types(self):
assignment_user=m1, """
Since everything is mixed in the same table, we need to make sure
that the dashboard only contains the supported types does not
get confused by the unsupported ones.
"""
irrelevant_types_for_dashboard = set(AssignmentType) - {
AssignmentType.CASEWORK,
AssignmentType.PREP_ASSIGNMENT,
AssignmentType.EDONIQ_TEST,
}
for assignment_type in irrelevant_types_for_dashboard:
self._test_assignment_type_not_in_dashboard(assignment_type=assignment_type)
def _test_assignment_type_dashboard_details(self, assignment_type: AssignmentType):
# GIVEN
assignment, csa = mix_assignment_cocktail(
assignment_type=assignment_type,
deadline_at=datetime(2000, 4, 1),
course_session=self.course_session,
circle=self.circle,
)
create_assignment_completion(
user=self.m1,
assignment=assignment, assignment=assignment,
evaluation_passed=True, course_session=self.course_session,
)
# WHEN
response = self.query(
self.GRAPHQL_QUERY, variables={"course_id": str(self.course.id)}
)
# THEN
self.assertResponseNoErrors(response)
dashboard = response.json()["data"]["course_dashboard"]
records = dashboard[0]["assignments"]["records"]
self.assertEqual(len(records), 1)
record = records[0]
if isinstance(csa, CourseSessionAssignment):
due_date = csa.submission_deadline
else:
due_date = csa.deadline
self.assertEqual(record["course_session_id"], str(self.course_session.id))
self.assertEqual(record["course_session_assignment_id"], str(csa.id))
self.assertEqual(record["generation"], str(self.course_session.generation))
self.assertEqual(record["circle_id"], str(self.circle.id))
self.assertEqual(record["details_url"], due_date.url_expert)
self.assertEqual(datetime.fromisoformat(record["deadline"]), due_date.start)
self.assertEqual(
record["assignment_title"],
csa.learning_content.content_assignment.title,
)
self.assertEqual(
record["assignment_type_translation_key"],
due_date.assignment_type_translation_key,
)
def _test_assignment_type_not_in_dashboard(self, assignment_type: AssignmentType):
_, csa = mix_assignment_cocktail(
assignment_type=assignment_type,
course_session=self.course_session,
circle=self.circle,
)
# WHEN
response = self.query(
self.GRAPHQL_QUERY, variables={"course_id": str(self.course.id)}
)
# THEN
self.assertResponseNoErrors(response)
dashboard = response.json()["data"]["course_dashboard"]
records = dashboard[0]["assignments"]["records"]
self.assertEqual(len(records), 0)
def test_metrics_summary(self):
# GIVEN
assignment_1, _ = mix_assignment_cocktail(
deadline_at=datetime(1990, 4, 1),
assignment_type=AssignmentType.CASEWORK,
course_session=self.course_session,
circle=self.circle,
)
assignment_2, _ = mix_assignment_cocktail(
deadline_at=datetime(2000, 4, 1),
assignment_type=AssignmentType.EDONIQ_TEST,
course_session=self.course_session,
circle=self.circle,
)
assignment_3, _ = mix_assignment_cocktail(
deadline_at=datetime(2010, 4, 1),
assignment_type=AssignmentType.PREP_ASSIGNMENT,
course_session=self.course_session,
circle=self.circle,
)
# no completions for this assignment yet
assignment_4, _ = mix_assignment_cocktail(
deadline_at=datetime(2020, 4, 1),
assignment_type=AssignmentType.EDONIQ_TEST,
course_session=self.course_session,
circle=self.circle,
)
# assignment 1
assigment_1_results = [
(self.m1, True), # passed
(self.m2, False), # failed
(self.m3, None), # unranked
]
for user, has_passed in assigment_1_results:
if has_passed is None:
continue
create_assignment_completion(
user=user,
assignment=assignment_1,
course_session=self.course_session,
has_passed=has_passed,
)
# assignment 2
assignment_2_results = [
(self.m1, True), # passed
(self.m2, True), # passed
(self.m3, False), # failed
]
for user, has_passed in assignment_2_results:
create_assignment_completion(
user=user,
assignment=assignment_2,
course_session=self.course_session,
has_passed=has_passed,
)
# assignment 3
assignment_3_results = [
(self.m1, True), # passed
(self.m2, True), # passed
(self.m3, True), # passed
]
for user, has_passed in assignment_3_results:
create_assignment_completion(
user=user,
assignment=assignment_3,
course_session=self.course_session,
has_passed=has_passed,
)
# WHEN
response = self.query(
self.GRAPHQL_QUERY, variables={"course_id": str(self.course.id)}
)
# THEN
self.assertResponseNoErrors(response)
dashboard = response.json()["data"]["course_dashboard"]
# 1 -> incomplete (not counted for average)
# 2 -> complete 66% passed ...
# 3 -> complete 100% passed --> 83.5%
# 4 -> incomplete (not counted for average)
summary = dashboard[0]["assignments"]["summary"]
self.assertEqual(summary["completed_count"], 2)
self.assertEqual(summary["average_passed"], 83.5)
records = dashboard[0]["assignments"]["records"]
self.assertEqual(len(records), 4)
# 1 -> assigment_1_results (oldest)
assignment_1_metrics = records[0]["metrics"]
self.assertEqual(assignment_1_metrics["passed_count"], 1)
self.assertEqual(assignment_1_metrics["failed_count"], 1)
self.assertEqual(assignment_1_metrics["unranked_count"], 1)
self.assertEqual(assignment_1_metrics["ranking_completed"], False)
self.assertEqual(assignment_1_metrics["average_passed"], 34)
# 2 -> assignment_2_results
assignment_2_metrics = records[1]["metrics"]
self.assertEqual(assignment_2_metrics["passed_count"], 2)
self.assertEqual(assignment_2_metrics["failed_count"], 1)
self.assertEqual(assignment_2_metrics["unranked_count"], 0)
self.assertEqual(assignment_2_metrics["ranking_completed"], True)
self.assertEqual(assignment_2_metrics["average_passed"], 67)
# 3 -> assignment_3_results
assignment_3_metrics = records[2]["metrics"]
self.assertEqual(assignment_3_metrics["passed_count"], 3)
self.assertEqual(assignment_3_metrics["failed_count"], 0)
self.assertEqual(assignment_3_metrics["unranked_count"], 0)
self.assertEqual(assignment_3_metrics["ranking_completed"], True)
self.assertEqual(assignment_3_metrics["average_passed"], 100)
# 4 -> no completions (newest)
assignment_4_metrics = records[3]["metrics"]
self.assertEqual(assignment_4_metrics["passed_count"], 0)
self.assertEqual(assignment_4_metrics["failed_count"], 0)
self.assertEqual(assignment_4_metrics["unranked_count"], 3)
self.assertEqual(assignment_4_metrics["ranking_completed"], False)
self.assertEqual(assignment_4_metrics["average_passed"], 0)
def mix_assignment_cocktail(
assignment_type: AssignmentType,
course_session: CourseSession,
circle: Circle,
deadline_at: datetime | None = None,
) -> Tuple[Assignment, CourseSessionAssignment | CourseSessionEdoniqTest]:
"""
Little test helper to create a course session assignment or edoniq test based
on the given assignment type.
"""
assignment = create_assignment(
course=course_session.course, assignment_type=assignment_type
)
if assignment_type == AssignmentType.EDONIQ_TEST:
cset = create_course_session_edoniq_test(
deadline_at=deadline_at,
course_session=course_session, course_session=course_session,
completion_data={}, learning_content_edoniq_test=create_assignment_learning_content(
circle=circle,
assignment=assignment,
),
) )
return assignment, cset
learning_content_assignment = LearningContentAssignmentFactory( else:
title="Learning Content Assignment Title", csa = create_course_session_assignment(
parent=circle, deadline_at=deadline_at,
content_assignment=assignment, course_session=course_session,
learning_content_assignment=create_assignment_learning_content(
circle=circle,
assignment=assignment,
),
) )
return assignment, csa
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)

View File

@ -4,6 +4,16 @@ from typing import List, Tuple
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from django.utils import timezone from django.utils import timezone
from vbv_lernwelt.assignment.models import (
AssignmentType,
AssignmentCompletion,
Assignment,
AssignmentCompletionStatus,
)
from vbv_lernwelt.assignment.tests.assignment_factories import (
AssignmentFactory,
AssignmentListPageFactory,
)
from vbv_lernwelt.competence.factories import ( from vbv_lernwelt.competence.factories import (
ActionCompetenceFactory, ActionCompetenceFactory,
ActionCompetenceListPageFactory, ActionCompetenceListPageFactory,
@ -21,15 +31,26 @@ from vbv_lernwelt.course.models import (
CourseSessionUser, CourseSessionUser,
) )
from vbv_lernwelt.course.utils import get_wagtail_default_site from vbv_lernwelt.course.utils import get_wagtail_default_site
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse from vbv_lernwelt.course_session.models import (
CourseSessionAttendanceCourse,
CourseSessionAssignment,
CourseSessionEdoniqTest,
)
from vbv_lernwelt.duedate.models import DueDate from vbv_lernwelt.duedate.models import DueDate
from vbv_lernwelt.learnpath.models import Circle, LearningPath from vbv_lernwelt.learnpath.models import (
Circle,
LearningPath,
LearningContentAssignment,
LearningContentEdoniqTest,
)
from vbv_lernwelt.learnpath.tests.learning_path_factories import ( from vbv_lernwelt.learnpath.tests.learning_path_factories import (
CircleFactory, CircleFactory,
LearningContentAttendanceCourseFactory, LearningContentAttendanceCourseFactory,
LearningPathFactory, LearningPathFactory,
LearningUnitFactory, LearningUnitFactory,
TopicFactory, TopicFactory,
LearningContentAssignmentFactory,
LearningContentEdoniqTestFactory,
) )
@ -116,6 +137,92 @@ def create_attendance_course(
) )
def create_assignment(
course: Course,
assignment_type: AssignmentType,
) -> Assignment:
return AssignmentFactory(
parent=AssignmentListPageFactory(
parent=course.coursepage,
),
assignment_type=assignment_type.name,
title=f"Dummy Assignment ({assignment_type.name})",
effort_required=":)",
intro_text=":)",
performance_objectives=[],
)
def create_assignment_completion(
user: User,
assignment: Assignment,
course_session: CourseSession,
has_passed: bool | None = None,
) -> AssignmentCompletion:
return AssignmentCompletion.objects.create(
completion_status=AssignmentCompletionStatus.EVALUATION_SUBMITTED.value,
assignment_user=user,
assignment=assignment,
evaluation_passed=has_passed,
course_session=course_session,
completion_data={},
)
def create_assignment_learning_content(
circle: Circle,
assignment: Assignment,
) -> LearningContentAssignment | LearningContentEdoniqTest:
if AssignmentType(assignment.assignment_type) == AssignmentType.EDONIQ_TEST:
return LearningContentEdoniqTestFactory(
title="Learning Content (EDONIQ_TEST)",
parent=circle,
content_assignment=assignment,
)
return LearningContentAssignmentFactory(
title=f"Learning Content ({assignment.assignment_type})",
parent=circle,
content_assignment=assignment,
)
def create_course_session_assignment(
course_session: CourseSession,
learning_content_assignment: LearningContentAssignment,
deadline_at: datetime | None = None,
) -> CourseSessionAssignment:
cas = CourseSessionAssignment.objects.create(
course_session=course_session,
learning_content=learning_content_assignment,
)
if deadline_at:
# the save on the course_session_assignment already sets a lot
# of due date fields, so it's easier to just overwrite the this
cas.submission_deadline.start = timezone.make_aware(deadline_at)
cas.submission_deadline.save()
return cas
def create_course_session_edoniq_test(
course_session: CourseSession,
learning_content_edoniq_test: LearningContentEdoniqTest,
deadline_at: datetime,
) -> CourseSessionEdoniqTest:
cset = CourseSessionEdoniqTest.objects.create(
course_session=course_session,
learning_content=learning_content_edoniq_test,
)
# same as above (see create_course_session_assignment)
cset.deadline.start = timezone.make_aware(deadline_at)
cset.deadline.save()
return cset
def create_performance_criteria_page( def create_performance_criteria_page(
course: Course, course_page: CoursePage, circle: Circle course: Course, course_page: CoursePage, circle: Circle
) -> PerformanceCriteria: ) -> PerformanceCriteria: