258 lines
8.0 KiB
Python
258 lines
8.0 KiB
Python
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from io import BytesIO
|
|
|
|
import structlog
|
|
from django.utils import timezone
|
|
from django.utils.translation import gettext_lazy as _
|
|
from openpyxl import Workbook
|
|
|
|
from vbv_lernwelt.assignment.models import (
|
|
Assignment,
|
|
AssignmentCompletion,
|
|
AssignmentType,
|
|
)
|
|
from vbv_lernwelt.course.models import CourseSession, CourseSessionUser
|
|
from vbv_lernwelt.course_session.models import (
|
|
CourseSessionAssignment,
|
|
CourseSessionEdoniqTest,
|
|
)
|
|
from vbv_lernwelt.course_session.services.export_attendance import (
|
|
add_user_export_data,
|
|
add_user_headers,
|
|
get_ordered_csus_by_course_session,
|
|
group_by_session_title,
|
|
make_export_filename,
|
|
sanitize_sheet_name,
|
|
)
|
|
from vbv_lernwelt.learnpath.models import LearningContent
|
|
|
|
logger = structlog.get_logger(__name__)
|
|
|
|
COMPETENCE_ELEMENT_EXPORT_FILE_NAME = _("export_kompetenznachweis_elemente")
|
|
|
|
|
|
@dataclass
|
|
class CompetenceCertificateElement:
|
|
assignment: Assignment
|
|
# date: DueDate
|
|
sort_datetime: datetime
|
|
learning_content: LearningContent
|
|
course_session: CourseSession
|
|
|
|
|
|
def export_competence_elements(
|
|
course_session_ids: list[str],
|
|
circle_ids: list[int] = None,
|
|
user_ids: list[str] = None,
|
|
save_as_file: bool = False,
|
|
):
|
|
if len(course_session_ids) == 0:
|
|
return
|
|
|
|
COMPETENCE_ASSIGNMENT_TYPES = [
|
|
AssignmentType.CASEWORK.value,
|
|
AssignmentType.EDONIQ_TEST.value,
|
|
]
|
|
|
|
wb = Workbook()
|
|
# remove the first sheet is just easier than keeping track of the active sheet
|
|
wb.remove(wb.active)
|
|
|
|
competence_certificate_elements = _get_competence_certificate_elements(
|
|
course_session_ids
|
|
)
|
|
|
|
assignment_completions = AssignmentCompletion.objects.filter(
|
|
course_session_id__in=course_session_ids,
|
|
assignment__assignment_type__in=COMPETENCE_ASSIGNMENT_TYPES,
|
|
).order_by("course_session", "assignment")
|
|
if user_ids:
|
|
assignment_completions = AssignmentCompletion.objects.filter(
|
|
assignment_user_id__in=user_ids if user_ids else [],
|
|
).order_by("course_session", "assignment")
|
|
|
|
# group all by the sessions title {session_id1: [...], session_id2: [...], ...}
|
|
grouped_cs_users = get_ordered_csus_by_course_session(
|
|
course_session_ids, user_ids=user_ids
|
|
)
|
|
grouped_cce = group_by_session_title(competence_certificate_elements)
|
|
grouped_ac = group_by_session_title(assignment_completions)
|
|
|
|
# create a sheet for each course session
|
|
for course_session_title, cs_users in grouped_cs_users.items():
|
|
logger.debug(
|
|
"export_assignment_completion",
|
|
data={
|
|
"course_session": course_session_title,
|
|
},
|
|
label="assignment_export",
|
|
)
|
|
|
|
# handle the case where there are no competence certificate elements for the course session
|
|
try:
|
|
cces = grouped_cce[course_session_title]
|
|
except KeyError:
|
|
cces = []
|
|
|
|
try:
|
|
acs = grouped_ac[course_session_title]
|
|
except KeyError:
|
|
acs = []
|
|
|
|
_create_sheet(
|
|
wb,
|
|
course_session_title,
|
|
cs_users,
|
|
cces,
|
|
acs,
|
|
circle_ids,
|
|
)
|
|
|
|
if save_as_file:
|
|
wb.save(make_export_filename(COMPETENCE_ELEMENT_EXPORT_FILE_NAME))
|
|
else:
|
|
output = BytesIO()
|
|
wb.save(output)
|
|
|
|
output.seek(0)
|
|
return output.getvalue()
|
|
|
|
|
|
def _create_sheet(
|
|
wb: Workbook,
|
|
title: str,
|
|
users: list[CourseSessionUser],
|
|
competence_certificate_element: list[CompetenceCertificateElement],
|
|
assignment_completions: list[AssignmentCompletion],
|
|
circle_ids: list[int],
|
|
):
|
|
sheet = wb.create_sheet(title=sanitize_sheet_name(title))
|
|
|
|
if len(users) == 0 or len(competence_certificate_element) == 0:
|
|
return sheet
|
|
|
|
# headers
|
|
# common user headers, Circle <title> <learningcontenttitle> bestanden, Circle <title> <learningcontenttitle> Resultat, ...
|
|
col_idx = add_user_headers(sheet)
|
|
|
|
ordered_assignement_ids = [] # keep track of the order of the columns when adding the rows
|
|
for cse in competence_certificate_element:
|
|
circle = cse.learning_content.get_circle()
|
|
|
|
if circle_ids and circle.id not in circle_ids:
|
|
continue
|
|
|
|
col_prefix = f'Circle "{circle.title}" {cse.learning_content.title}'
|
|
|
|
# add translation strings here as they are not picked up in f-strings
|
|
result_str = str(_("Resultat"))
|
|
success_str = str(_("bestanden"))
|
|
|
|
sheet.cell(
|
|
row=1,
|
|
column=col_idx,
|
|
value=f"{col_prefix} {success_str}",
|
|
)
|
|
|
|
sheet.cell(
|
|
row=1,
|
|
column=col_idx + 1,
|
|
value=f"{col_prefix} {result_str} %",
|
|
)
|
|
|
|
ordered_assignement_ids.append(cse.assignment.id)
|
|
|
|
col_idx += 2
|
|
|
|
# add rows with user results
|
|
_add_rows(sheet, users, ordered_assignement_ids, assignment_completions)
|
|
|
|
return sheet
|
|
|
|
|
|
def _add_rows(
|
|
sheet,
|
|
users: list[CourseSessionUser],
|
|
ordered_assignement_ids,
|
|
assignment_completions,
|
|
):
|
|
for row_idx, user in enumerate(users, start=2):
|
|
col_idx = add_user_export_data(sheet, user, row_idx)
|
|
|
|
for assignment_id in ordered_assignement_ids:
|
|
# get the completion for the user and the assignment
|
|
user_acs = [
|
|
ac
|
|
for ac in assignment_completions
|
|
if ac.assignment_id == assignment_id and ac.assignment_user == user.user
|
|
]
|
|
user_ac = user_acs[0] if user_acs else None
|
|
|
|
if user_ac:
|
|
status_text = (
|
|
str(_("Bestanden"))
|
|
if user_ac.evaluation_passed
|
|
else str(_("Nicht bestanden"))
|
|
)
|
|
sheet.cell(row=row_idx, column=col_idx, value=status_text)
|
|
try:
|
|
sheet.cell(
|
|
row=row_idx,
|
|
column=col_idx + 1,
|
|
value=round(
|
|
100
|
|
* user_ac.evaluation_points
|
|
/ user_ac.evaluation_max_points
|
|
),
|
|
)
|
|
except (ZeroDivisionError, TypeError):
|
|
sheet.cell(
|
|
row=row_idx, column=col_idx + 1, value=str(_("Keine Daten"))
|
|
)
|
|
|
|
else:
|
|
sheet.cell(row=row_idx, column=col_idx, value=str(_("Keine Daten")))
|
|
sheet.cell(row=row_idx, column=col_idx + 1, value=str(_("Keine Daten")))
|
|
|
|
col_idx += 2
|
|
|
|
|
|
def _get_competence_certificate_elements(
|
|
course_session_ids: list[str],
|
|
) -> list[CompetenceCertificateElement]:
|
|
course_session_assignments = CourseSessionAssignment.objects.filter(
|
|
course_session__id__in=course_session_ids,
|
|
learning_content__content_assignment__competence_certificate__isnull=False,
|
|
).order_by("course_session", "submission_deadline__start")
|
|
|
|
course_session_edoniqtests = CourseSessionEdoniqTest.objects.filter(
|
|
course_session__id__in=course_session_ids,
|
|
learning_content__content_assignment__competence_certificate__isnull=False,
|
|
).order_by("course_session", "deadline__start")
|
|
|
|
cse = [
|
|
CompetenceCertificateElement(
|
|
assignment=csa.learning_content.content_assignment,
|
|
sort_datetime=csa.submission_deadline.start or timezone.now(),
|
|
learning_content=csa.learning_content,
|
|
course_session=csa.course_session,
|
|
)
|
|
for csa in course_session_assignments
|
|
]
|
|
|
|
cse += [
|
|
CompetenceCertificateElement(
|
|
assignment=cset.learning_content.content_assignment,
|
|
sort_datetime=cset.deadline.start or timezone.now(),
|
|
learning_content=cset.learning_content,
|
|
course_session=cset.course_session,
|
|
)
|
|
for cset in course_session_edoniqtests
|
|
]
|
|
|
|
# order by course_session and submission_deadline
|
|
cse.sort(key=lambda x: (x.course_session.title, x.sort_datetime))
|
|
|
|
return cse
|