vbv/server/vbv_lernwelt/feedback/export.py

169 lines
5.6 KiB
Python

from io import BytesIO
from itertools import groupby
from operator import attrgetter
from typing import List, Tuple
import structlog
from django.db.models import QuerySet
from django.utils.translation import gettext_lazy as _
from openpyxl import Workbook
from vbv_lernwelt.course_session.services.export_attendance import (
make_export_filename,
sanitize_sheet_name,
)
from vbv_lernwelt.feedback.models import FeedbackResponse
logger = structlog.get_logger(__name__)
FEEDBACK_EXPORT_FILE_NAME = _("export_feedback")
VV_FEEDBACK_QUESTIONS = [
("satisfaction", "Zufriedenheit insgesamt"),
("goal_attainment", "Zielerreichung insgesamt"),
(
"proficiency",
"Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Circle?",
),
("preparation_task_clarity", "Waren die Praxisaufträge klar und verständlich?"),
("would_recommend", "Würdest du den Circle weiterempfehlen?"),
("course_positive_feedback", "Was hat dir besonders gut gefallen?"),
("course_negative_feedback", "Wo siehst du Verbesserungspotential?"),
]
UK_FEEDBACK_QUESTIONS = [
("satisfaction", _("Zufriedenheit insgesamt")),
("goal_attainment", _("Zielerreichung insgesamt")),
(
"proficiency",
_("Wie beurteilst du deine Sicherheit bezüglichen den Themen nach dem Kurs?"),
),
(
"preparation_task_clarity",
_("Waren die Vorbereitungsaufträge klar und verständlich?"),
),
(
"instructor_competence",
_(
"Wie beurteilst du die Themensicherheit und Fachkompetenz des Kursleiters/der Kursleiterin?"
),
),
(
"instructor_respect",
_(
"Wurden Fragen und Anregungen der Kursteilnehmenden ernst genommen und aufgegriffen?"
),
),
(
"instructor_open_feedback",
_("Was möchtest du dem Kursleiter/der Kursleiterin sonst noch sagen?"),
),
("would_recommend", _("Würdest du den Kurs weiterempfehlen?")),
("course_positive_feedback", _("Was hat dir besonders gut gefallen?")),
("course_negative_feedback", _("Wo siehst du Verbesserungspotential?")),
]
def export_feedback(course_session_ids: list[str], save_as_file: bool, circles=None):
"""
Export for django view, all circles are allowed
"""
if circles:
feedback_unordered = FeedbackResponse.objects.filter(
course_session_id__in=course_session_ids,
circle__in=circles,
submitted=True,
)
else:
feedback_unordered = FeedbackResponse.objects.filter(
course_session_id__in=course_session_ids,
submitted=True,
)
return _generate_feedback_export(feedback_unordered, save_as_file)
def export_feedback_with_circle_restriction(
course_sessions_with_circles: List[Tuple[int, List[int]]], save_as_file: bool
):
"""
Export for user export, only circles in specified course sessions are allowed
"""
feedback_unordered = FeedbackResponse.objects.none()
for course_session_with_circles in course_sessions_with_circles:
feedback_unordered = feedback_unordered | FeedbackResponse.objects.filter(
course_session_id=course_session_with_circles[0],
circle_id__in=course_session_with_circles[1],
submitted=True,
)
return _generate_feedback_export(feedback_unordered, save_as_file)
def _generate_feedback_export(feedback_unordered: QuerySet, save_as_file: bool):
wb = Workbook()
# remove the first sheet is just easier than keeping track of the active sheet
wb.remove(wb.active)
feedbacks = feedback_unordered.order_by("circle", "course_session", "updated_at")
grouped_feedbacks = groupby(feedbacks, key=attrgetter("circle"))
for circle, group_feedbacks in grouped_feedbacks:
group_feedbacks = list(group_feedbacks)
logger.debug(
"export_feedback_for_circle",
data={
"circle": circle.id,
"count": len(group_feedbacks),
},
label="feedback_export",
)
_create_sheet(wb, circle.title, group_feedbacks)
if save_as_file:
wb.save(make_export_filename(FEEDBACK_EXPORT_FILE_NAME))
else:
# todo handle IndexError
output = BytesIO()
wb.save(output)
output.seek(0)
return output.getvalue()
def _create_sheet(wb: Workbook, title: str, data: list[FeedbackResponse]):
sheet = wb.create_sheet(title=sanitize_sheet_name(title))
if len(data) == 0:
return sheet
# we instruct the users not to mix exports of different courses, so we can assume the questions are the same and of the first type
question_data = (
UK_FEEDBACK_QUESTIONS
if data[0].data["feedback_type"] == "uk"
else VV_FEEDBACK_QUESTIONS
)
# add header
sheet.cell(row=1, column=1, value=str(_("Durchführung")))
sheet.cell(row=1, column=2, value=str(_("Datum")))
questions = [q[1] for q in question_data]
for col_idx, title in enumerate(questions, start=3):
sheet.cell(row=1, column=col_idx, value=str(title))
_add_rows(sheet, data, question_data)
return sheet
def _add_rows(sheet, data, question_data):
for row_idx, feedback in enumerate(data, start=2):
sheet.cell(row=row_idx, column=1, value=feedback.course_session.title)
sheet.cell(
row=row_idx, column=2, value=feedback.updated_at.date().strftime("%d.%m.%Y")
)
for col_idx, question in enumerate(question_data, start=3):
response = feedback.data.get(question[0], "")
sheet.cell(row=row_idx, column=col_idx, value=response)