from django.http import HttpRequest, HttpResponse from django.db.models.query import QuerySet from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment, Border, Side from openpyxl.utils import get_column_letter from datetime import date, datetime from typing import List from ..models.weekpref import WeekPreference from ..models.missions import Training from ..models.hourbuildings import HourBuilding, HourBuildingLegFlight, HourBuildingLegStop, HourBuildingLegBase def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) -> HttpResponse: if not queryset.first(): raise Exception("Empty queryset") # Init Variables year = date.today().year week = queryset.first().week if queryset.first() else date.today().isocalendar().week weeks = queryset.order_by("week").distinct("week").all() # Prepare export filename and http content filename = f"{year}_week{'+'.join([str(w.week) for w in weeks])}_export.xlsx" response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') response['Content-Disposition'] = f'attachment; filename="{filename}"' # Create workbook and sheet wb = Workbook() ws = wb.active if not ws: raise Exception("Export: cannot select active workbook") ws.title = f"Week Preferences" ws.page_setup.orientation = ws.ORIENTATION_LANDSCAPE ws.page_setup.paperSize = ws.PAPERSIZE_A3 ws.page_setup.fitToHeight = 0 ws.page_setup.fitToWidth = 1 # Header titles days = [f"{datetime.strptime(f"{year} {week} {x}", "%G %V %u").strftime("%A")} {datetime.strptime(f"{year} {week} {x}", "%G %V %u").day}" for x in range(1,8)] headers = ["Week", "Student", "Course", *days, "Notes", "Cell.", "Mail"] # Header fields positions week_index: int = headers.index("Week") + 1 student_index: int = headers.index("Student") + 1 course_index: int = headers.index("Course") + 1 cell_index: int = headers.index("Cell.") + 1 mail_index: int = headers.index("Mail") + 1 note_index: int = headers.index("Notes") + 1 # Stile header header_fill = PatternFill("solid", fgColor="0e005c") bold_white = Font(color="FFFFFF", bold=True) bold_black = Font(color="000000", bold=True) center = Alignment(horizontal="center", vertical="center", wrapText=True) # Cell styles border_thick: Side = Side(style='thick', color='000000') border_bottom: Border = Border(bottom=border_thick) border_left: Border = Border(left=border_thick) border_right: Border = Border(right=border_thick) border_all: Border = Border(bottom=border_thick, top=border_thick, left=border_thick, right=None) # Scrittura header for col, h in enumerate(headers, start=1): cell = ws.cell(row=1, column=col, value=h) cell.fill = header_fill cell.font = bold_white cell.alignment = center ### Start of Student Loop ### # Fill worksheet with EVERY training and hb for every student # Each of this iterations fills the table for a student row: int = 2 row_offset: int = 0 for i, q in enumerate(queryset.order_by("student__surname", "student__name", "student__course"), start=1): student_data: List[str] student_phone: str = q.student.phone if q.student.phone else "" student_email: str = q.student.email if q.student.course: student_data = [f"{q.student.surname} {q.student.name}", f"{q.student.course.ctype}-{q.student.course.cnumber}"] else: student_data = [f"{q.student.surname} {q.student.name}", f"No Course Assigned"] # Fill Training mission rows mission_name: str mission_days: List[str] mission_notes: str mission_data: List[List[str]] = [] for t in Training.objects.filter(weekpref = q.id): if not t.mission: raise Exception("No Training Mission Assigned") mission_name = f"{t.mission.mtype}-{t.mission.mnum}" mission_days = [ mission_name if t.monday else "", mission_name if t.tuesday else "", mission_name if t.wednesday else "", mission_name if t.thursday else "", mission_name if t.friday else "", mission_name if t.saturday else "", mission_name if t.sunday else "" ] mission_notes = t.notes if t.notes else "--" mission_data.append([str(q.week), *student_data, *mission_days, mission_notes, student_phone, student_email, ]) # Fill HourBuilding rows hb_name: str hb_days: List[str] hb_data: List[List[str]] = [] for h in HourBuilding.objects.filter(weekpref = q.id): hb_name = f"HB-{h.aircraft}\nVedi Note ->" hb_days = [ hb_name if h.monday else "", hb_name if h.tuesday else "", hb_name if h.wednesday else "", hb_name if h.thursday else "", hb_name if h.friday else "", hb_name if h.saturday else "", hb_name if h.sunday else "" ] hb_notes: List[str] = [f"{h.notes}", "---"] if h.notes else [] hb_legs_all = HourBuildingLegBase.objects.filter(hb_id = h.id) for hh in hb_legs_all: if isinstance(hh, HourBuildingLegFlight): hb_notes.append(f"{hh.departure} -> {hh.destination} [{hh.time}]") elif isinstance(hh, HourBuildingLegStop): hb_notes.append(f"STOP [{hh.time}] {"Refuel" if hh.refuel else ""}" ) hb_data.append([str(q.week), *student_data, *hb_days, "\n".join(hb_notes), str(q.student.phone), q.student.email]) # Build rows for table all_data: List[List[str]] = mission_data + hb_data student_start: int = row + row_offset for r in all_data: for j, c in enumerate(r, start=1): cell = ws.cell(row = row + row_offset, column = j, value = c) cell.alignment = center # Format Student Name if j == student_index: cell.font = bold_black # Format Course Column if j == course_index and q.student.course: cell.font = bold_black cell.fill = PatternFill("solid", fgColor=str(q.student.course.color).lstrip('#').lower()) prev_cell_val: str = r[0] merge_start: bool = False merge_col_start: int = 1 for c, v in enumerate(r, start=1): # Merge cells in the row if v == prev_cell_val and not merge_start: merge_start = True merge_col_start = c-1 # start merge from previous column elif v != prev_cell_val and merge_start: merge_start = False ws.merge_cells(start_row=row+row_offset, end_row=row+row_offset, start_column=max(merge_col_start,1), end_column=max(c-1,1)) # end merge to previous column prev_cell_val = v # Incement row counter row_offset += 1 # End week preferences for this student student_end: int = row + row_offset -1 # Add thick border to the last cell row of this student for i in range(course_index, len(all_data[0])+1): ws.cell(row=student_end, column=i).border = border_bottom if i == len(all_data[0]): for j in range(student_start, student_end + 1): ws.cell(row=j, column=i).border += border_right # Merge Week, thick border #ws.cell(row=student_start, column=week_index).border = border_bottom #ws.merge_cells(start_row=student_start, end_row=student_end, start_column=week_index, end_column=week_index) # Merge Name, thick border ws.cell(row=student_start, column=student_index).border = border_all ws.merge_cells(start_row=student_start, end_row=student_end, start_column=student_index, end_column=student_index) # Merge Course ws.merge_cells(start_row=student_start, end_row=student_end, start_column=course_index, end_column=course_index) # Merge Cell ws.merge_cells(start_row=student_start, end_row=student_end, start_column=cell_index, end_column=cell_index) # Merge Mail ws.merge_cells(start_row=student_start, end_row=student_end, start_column=mail_index, end_column=mail_index) # Keep the largest column for column_cells in ws.columns: length: int = max(len(str(cell.value)) for cell in column_cells) col_letter: str = "A" if column_cells[0].column: col_letter = get_column_letter(column_cells[0].column) ws.column_dimensions[col_letter].width = length + 2 ### End of Student Loop ### # Save document in HttpResponse wb.save(response) return response