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.utils import get_column_letter
from ..models.courses import CourseTypes
from ..models.missions import Training
from ..models.weekpref import WeekPreference
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 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:
if not queryset.first():
@@ -84,68 +98,72 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
student_email: str = q.student.email
student_course_type: 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:
student_course_type = q.student.course.ctype
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 = [
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}"
]
else:
student_data = [f"{q.student.surname} {q.student.name}", f"No Course Assigned"]
# Fill Training mission rows
mission_name: str
mission_name: List[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_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_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_name_joined if t.monday else "",
mission_name_joined if t.tuesday else "",
mission_name_joined if t.wednesday else "",
mission_name_joined if t.thursday else "",
mission_name_joined if t.friday else "",
mission_name_joined if t.saturday 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, ])
# Fill HourBuilding rows
hb_name: str
hb_name: List[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.strip()}", "---"] if h.notes else []
hb_name = ["HB", f"({h.aircraft})"]
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 ''}")
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):
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])
hb_name.append(f"STOP [{time_str}] {"Refuel" if hh.refuel else ""}" )
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
all_data: List[List[str]] = mission_data + hb_data
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):
cell = ws.cell(row = row + row_offset, column = c, value = cell_content)
cell.alignment = center
@@ -160,25 +178,25 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
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 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
cell.fill = PatternFill('solid', fgColor=PALETTE[ri % len(PALETTE)].lstrip("#").lower())
if MERGE:
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
@@ -208,6 +226,7 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
# Keep the largest column
max_len: List[int] = []
col_letter: str = "A"
for column_cells in ws.columns:
for cell in column_cells:
cell_lines = str(cell.value).splitlines()
@@ -215,11 +234,9 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
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

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