Improved xlsx formatting

This commit is contained in:
2025-12-04 13:05:34 +01:00
parent 303359c921
commit b8f4331d3b
2 changed files with 72 additions and 49 deletions

View File

@@ -5,6 +5,7 @@ from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter from openpyxl.utils import get_column_letter
from ..models.courses import CourseTypes
from ..models.missions import Training from ..models.missions import Training
from ..models.weekpref import WeekPreference from ..models.weekpref import WeekPreference
from ..models.hourbuildings import HourBuilding, HourBuildingLegFlight, HourBuildingLegStop, HourBuildingLegBase from ..models.hourbuildings import HourBuilding, HourBuildingLegFlight, HourBuildingLegStop, HourBuildingLegBase
@@ -12,6 +13,19 @@ from ..models.hourbuildings import HourBuilding, HourBuildingLegFlight, HourBuil
from datetime import date, datetime from datetime import date, datetime
from typing import List from typing import List
# Enable cell merging for equal mission
MERGE: bool = False
PALETTE : List[str] = [
"#E6F2FF", # azzurro chiarissimo
"#E5FBF8", # verde acqua molto chiaro
"#ECFBE1", # verde chiarissimo
"#FFFBD1", # giallo molto chiaro
"#FFF1D6", # giallo-arancio molto chiaro
"#FFE3DD", # rosa pesca molto chiaro
"#F3E6FA", # lilla chiarissimo
]
def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) -> HttpResponse: def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) -> HttpResponse:
if not queryset.first(): if not queryset.first():
@@ -84,68 +98,72 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
student_email: str = q.student.email student_email: str = q.student.email
student_course_type: str student_course_type: str
student_course_number: str student_course_number: str
student_course_ac: str student_course_ac: str = f"({'/'.join(t.type for t in q.student.aircrafts.distinct("type").all())})"
if q.student.course: if q.student.course:
student_course_type = q.student.course.ctype student_course_type = q.student.course.ctype
student_course_number = str(q.student.course.cnumber) student_course_number = str(q.student.course.cnumber)
student_course_ac = " / ".join(t.type for t in q.student.aircrafts.distinct("type").all())
student_data = [ student_data = [
f"{q.student.surname} {q.student.name}\n{student_course_ac}", "\n".join([f"{q.student.surname} {q.student.name}", student_course_ac]),
f"{student_course_type}-{student_course_number}" f"{student_course_type}-{student_course_number}"
] ]
else: else:
student_data = [f"{q.student.surname} {q.student.name}", f"No Course Assigned"] student_data = [f"{q.student.surname} {q.student.name}", f"No Course Assigned"]
# Fill Training mission rows # Fill Training mission rows
mission_name: str mission_name: List[str]
mission_days: List[str] mission_days: List[str]
mission_notes: str mission_notes: str
mission_data: List[List[str]] = [] mission_data: List[List[str]] = []
for t in Training.objects.filter(weekpref = q.id): for t in Training.objects.filter(weekpref = q.id):
if not t.mission: if not t.mission:
raise Exception("No Training Mission Assigned") raise Exception("No Training Mission Assigned")
mission_name = f"{t.mission.mtype}-{t.mission.mnum}" mission_notes = t.notes.strip().capitalize() if t.notes else "---"
mission_name = [f"{t.mission.mtype}-{t.mission.mnum}"]
if q.student.course and q.student.course.ctype == CourseTypes.PPL: # add course aircraft only for PPL students
mission_name.append(student_course_ac)
mission_name_joined: str = "\n".join(mission_name)
mission_days = [ mission_days = [
mission_name if t.monday else "", mission_name_joined if t.monday else "",
mission_name if t.tuesday else "", mission_name_joined if t.tuesday else "",
mission_name if t.wednesday else "", mission_name_joined if t.wednesday else "",
mission_name if t.thursday else "", mission_name_joined if t.thursday else "",
mission_name if t.friday else "", mission_name_joined if t.friday else "",
mission_name if t.saturday else "", mission_name_joined if t.saturday else "",
mission_name if t.sunday else "" mission_name_joined if t.sunday else ""
] ]
mission_notes = t.notes.strip() if t.notes else "--"
mission_data.append([str(q.week), *student_data, *mission_days, mission_notes, student_phone, student_email, ]) mission_data.append([str(q.week), *student_data, *mission_days, mission_notes, student_phone, student_email, ])
# Fill HourBuilding rows # Fill HourBuilding rows
hb_name: str hb_name: List[str]
hb_days: List[str] hb_days: List[str]
hb_data: List[List[str]] = [] hb_data: List[List[str]] = []
for h in HourBuilding.objects.filter(weekpref = q.id): for h in HourBuilding.objects.filter(weekpref = q.id):
hb_name = f"HB - {h.aircraft}\nVedi Note ->" hb_name = ["HB", f"({h.aircraft})"]
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.strip()}", "---"] if h.notes else []
hb_legs_all = HourBuildingLegBase.objects.filter(hb_id = h.id) hb_legs_all = HourBuildingLegBase.objects.filter(hb_id = h.id)
for hh in hb_legs_all: for hh in hb_legs_all:
time_str: str = ':'.join(str(hh.time).split(':')[:2]) # keep only hours and minutes time_str: str = ':'.join(str(hh.time).split(':')[:2]) # keep only hours and minutes
if isinstance(hh, HourBuildingLegFlight): if isinstance(hh, HourBuildingLegFlight):
hb_notes.append(f"{hh.departure} -> {hh.destination} [{time_str}]{f' / PAX: {hh.pax.capitalize()}' if hh.pax else ''}") hb_pax: str | None = " ".join(x.capitalize() for x in hh.pax.split()) if hh.pax else None
hb_name.append(f"{hh.departure} -> {hh.destination} [{time_str}]{f' / Pax: {hb_pax}' if hb_pax else ''}")
elif isinstance(hh, HourBuildingLegStop): elif isinstance(hh, HourBuildingLegStop):
hb_notes.append(f"STOP [{time_str}] {"Refuel" if hh.refuel else ""}" ) hb_name.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]) hb_name_joined: str = "\n".join(hb_name)
hb_days = [
hb_name_joined if h.monday else "",
hb_name_joined if h.tuesday else "",
hb_name_joined if h.wednesday else "",
hb_name_joined if h.thursday else "",
hb_name_joined if h.friday else "",
hb_name_joined if h.saturday else "",
hb_name_joined if h.sunday else ""
]
hb_notes: str = h.notes.strip().capitalize() if h.notes else ""
hb_data.append([str(q.week), *student_data, *hb_days, hb_notes, str(q.student.phone), q.student.email])
# Build rows for table # Build rows for table
all_data: List[List[str]] = mission_data + hb_data all_data: List[List[str]] = mission_data + hb_data
student_start: int = row + row_offset student_start: int = row + row_offset
for row_content in all_data: for ri, row_content in enumerate(all_data):
for c, cell_content in enumerate(row_content, start=1): for c, cell_content in enumerate(row_content, start=1):
cell = ws.cell(row = row + row_offset, column = c, value = cell_content) cell = ws.cell(row = row + row_offset, column = c, value = cell_content)
cell.alignment = center cell.alignment = center
@@ -160,25 +178,25 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
elif c > course_index and c <= note_index: elif c > course_index and c <= note_index:
cell.border = border_bottom_thin + border_right_thin cell.border = border_bottom_thin + border_right_thin
# Fill mix cells if the cell is not empty # Fill mix cells if the cell is not empty
if c > course_index and c < note_index: if c > course_index and c <= note_index:
if len(cell_content): if len(cell_content):
cell.fill = PatternFill('solid', fgColor="f0f0f0") cell.fill = PatternFill('solid', fgColor=PALETTE[ri % len(PALETTE)].lstrip("#").lower())
if MERGE:
prev_cell_val: str = row_content[0] prev_cell_val: str = row_content[0]
merge_start: bool = False merge_start: bool = False
merge_col_start: int = 1 merge_col_start: int = 1
for c, cell_content in enumerate(row_content, start=1): for c, cell_content in enumerate(row_content, start=1):
# Merge cells in the row # Merge cells in the row
if cell_content == prev_cell_val and not merge_start: if cell_content == prev_cell_val and not merge_start:
merge_start = True merge_start = True
merge_col_start = c-1 # start merge from previous column merge_col_start = c-1 # start merge from previous column
elif cell_content != prev_cell_val and merge_start: elif cell_content != prev_cell_val and merge_start:
merge_start = False merge_start = False
ws.merge_cells(start_row=row+row_offset, ws.merge_cells(start_row=row+row_offset,
end_row=row+row_offset, end_row=row+row_offset,
start_column=max(merge_col_start,1), start_column=max(merge_col_start,1),
end_column=max(c-1,1)) # end merge to previous column end_column=max(c-1,1)) # end merge to previous column
prev_cell_val = cell_content prev_cell_val = cell_content
# Incement row counter # Incement row counter
row_offset += 1 row_offset += 1
@@ -208,6 +226,7 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
# Keep the largest column # Keep the largest column
max_len: List[int] = [] max_len: List[int] = []
col_letter: str = "A"
for column_cells in ws.columns: for column_cells in ws.columns:
for cell in column_cells: for cell in column_cells:
cell_lines = str(cell.value).splitlines() cell_lines = str(cell.value).splitlines()
@@ -215,11 +234,9 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
continue continue
max_len.append(max([len(ll) for ll in cell_lines])) max_len.append(max([len(ll) for ll in cell_lines]))
length: int = max(max_len) length: int = max(max_len)
col_letter: str = "A"
if column_cells[0].column: if column_cells[0].column:
col_letter = get_column_letter(column_cells[0].column) col_letter = get_column_letter(column_cells[0].column)
ws.column_dimensions[col_letter].width = length + 2 ws.column_dimensions[col_letter].width = length + 2
### End of Student Loop ### ### End of Student Loop ###
# Save document in HttpResponse # Save document in HttpResponse

6
note.txt Normal file
View File

@@ -0,0 +1,6 @@
OK leg delle hb all'interno del riquadro per i giorni
OK aereo assegnato allo studente di fianco al numero della missione per PPL, invece per CPL e IR e HB il tipo va di fiaco al nome dello studente
OK le missioni ripetute su piu' giorni hanno una cella per giorno (non unite)
lo studente vede solo le missioni della sua fase PPL->PPL ATPL-> tutto
OK ogni richiesta ha un colore diverso che cicla con delle tinte pastello