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)