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_thin: Side = Side(style='thin', color='000000', border_style='dashed') border_bottom: Border = Border(bottom=border_thick) border_bottom_thin: Border = Border(bottom=border_thin) border_left: Border = Border(left=border_thick) border_right: Border = Border(right=border_thick) border_right_thin: Border = Border(right=border_thin) 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: time_str: str = ':'.join(str(hh.time).split(':')[:2]) # keep only hours and minutes if isinstance(hh, HourBuildingLegFlight): hb_notes.append(f"{hh.departure} -> {hh.destination} [{time_str}]{f' / PAX: {hh.pax.capitalize()}' if hh.pax else ''}") elif isinstance(hh, HourBuildingLegStop): hb_notes.append(f"STOP [{time_str}] {"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 row_content in all_data: for c, cell_content in enumerate(row_content, start=1): cell = ws.cell(row = row + row_offset, column = c, value = cell_content) cell.alignment = center # Format Student Name if c == student_index: cell.font = bold_black # Format Course Column with color elif c == course_index and q.student.course: cell.font = bold_black cell.fill = PatternFill("solid", fgColor=str(q.student.course.color).lstrip('#').lower()) # Add internal borders between mix cells and notes elif c > course_index and c <= note_index: cell.border = border_bottom_thin + border_right_thin # Fill mix cells if the cell is not empty if c > course_index and c < note_index: if len(cell_content): cell.fill = PatternFill('solid', fgColor="f0f0f0") prev_cell_val: str = row_content[0] merge_start: bool = False merge_col_start: int = 1 for c, cell_content in enumerate(row_content, start=1): # Merge cells in the row if cell_content == prev_cell_val and not merge_start: merge_start = True merge_col_start = c-1 # start merge from previous column elif cell_content != 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 = cell_content # 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 c in range(course_index, mail_index + 1): ws.cell(row=student_end, column=c).border = Border(bottom=border_thick, right=border_thin) # And for last column also a vertical border all student high if c == mail_index: for row_content in range(student_start, student_end + 1): ws.cell(row=row_content, column=c).border += border_right # Merge Week, thick border ws.cell(row=student_start, column=week_index).border = border_all 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 max_len: List[int] = [] for column_cells in ws.columns: for cell in column_cells: cell_lines = str(cell.value).splitlines() if len(cell_lines) == 0: continue max_len.append(max([len(ll) for ll in cell_lines])) length: int = max(max_len) 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