2 Commits

Author SHA1 Message Date
95370ed0dc Improved excel formatting 2025-11-24 12:20:42 +01:00
bb634d28ed improved excel formatting 2025-11-23 12:32:12 +01:00
2 changed files with 59 additions and 36 deletions

View File

@@ -10,7 +10,7 @@ from typing import List
from ..models.weekpref import WeekPreference from ..models.weekpref import WeekPreference
from ..models.missions import Training from ..models.missions import Training
from ..models.hourbuildings import HourBuilding, HourBuildingLegFlight, HourBuildingLegStop from ..models.hourbuildings import HourBuilding, HourBuildingLegFlight, HourBuildingLegStop, HourBuildingLegBase
def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) -> HttpResponse: def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) -> HttpResponse:
@@ -41,7 +41,7 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
# Header titles # 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)] 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, "Cell.", "Mail", "Notes"] headers = ["Week", "Student", "Course", *days, "Notes", "Cell.", "Mail"]
# Header fields positions # Header fields positions
week_index: int = headers.index("Week") + 1 week_index: int = headers.index("Week") + 1
@@ -59,7 +59,12 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
# Cell styles # Cell styles
border_thick: Side = Side(style='thick', color='000000') 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: 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) border_all: Border = Border(bottom=border_thick, top=border_thick, left=border_thick, right=None)
# Scrittura header # Scrittura header
@@ -102,15 +107,14 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
mission_name if t.sunday else "" mission_name if t.sunday else ""
] ]
mission_notes = t.notes if t.notes else "--" mission_notes = t.notes if t.notes else "--"
mission_data.append([str(q.week), *student_data, *mission_days, student_phone, student_email, mission_notes]) 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: str
hb_days: List[str] hb_days: List[str]
hb_notes: 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 = f"HB - {h.aircraft}\nVedi Note ->"
hb_days = [ hb_days = [
hb_name if h.monday else "", hb_name if h.monday else "",
hb_name if h.tuesday else "", hb_name if h.tuesday else "",
@@ -120,53 +124,71 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
hb_name if h.saturday else "", hb_name if h.saturday else "",
hb_name if h.sunday else "" hb_name if h.sunday else ""
] ]
hb_notes = f"{h.notes}\n----\n" if h.notes else "" hb_notes: List[str] = [f"{h.notes}", "---"] if h.notes else []
hb_legs = HourBuildingLegFlight.objects.filter(hb_id = h.id) hb_legs_all = HourBuildingLegBase.objects.filter(hb_id = h.id)
#for hh in hb_legs: for hh in hb_legs_all:
# hb_notes += f"{hh.departure} -> {hh.destination} [{hh.time}]\n" if not hh.stop else f"STOP at {hh.departure} [{hh.time}]\n" time_str: str = ':'.join(str(hh.time).split(':')[:2]) # keep only hours and minutes
hb_notes.strip('\n') if isinstance(hh, HourBuildingLegFlight):
hb_data.append([str(q.week), *student_data, *hb_days, str(q.student.phone), q.student.email, hb_notes]) 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 # 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 r in all_data: for row_content in all_data:
for j, c in enumerate(r, start=1): for c, cell_content in enumerate(row_content, start=1):
cell = ws.cell(row = row + row_offset, column = j, value = c) cell = ws.cell(row = row + row_offset, column = c, value = cell_content)
cell.alignment = center cell.alignment = center
# Format Student Name # Format Student Name
if j == student_index: if c == student_index:
cell.font = bold_black cell.font = bold_black
# Format Course Column # Format Course Column with color
if j == course_index and q.student.course: elif c == course_index and q.student.course:
cell.font = bold_black cell.font = bold_black
cell.fill = PatternFill("solid", fgColor=str(q.student.course.color).lstrip('#').lower()) 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 = r[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, v in enumerate(r, start=1): for c, cell_content in enumerate(row_content, start=1):
# Merge cells in the row # Merge cells in the row
if v == 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 v != 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 = v prev_cell_val = cell_content
# Incement row counter # Incement row counter
row_offset += 1 row_offset += 1
# End week preferences for this student # End week preferences for this student
student_end: int = row + row_offset -1 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 # Merge Week, thick border
# ws.cell(row=student_start, column=week_index).border = border_all 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) ws.merge_cells(start_row=student_start, end_row=student_end, start_column=week_index, end_column=week_index)
# Merge Name, thick border # Merge Name, thick border
ws.cell(row=student_start, column=student_index).border = border_all 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) ws.merge_cells(start_row=student_start, end_row=student_end, start_column=student_index, end_column=student_index)
@@ -177,13 +199,15 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
# Merge Mail # Merge Mail
ws.merge_cells(start_row=student_start, end_row=student_end, start_column=mail_index, end_column=mail_index) ws.merge_cells(start_row=student_start, end_row=student_end, start_column=mail_index, end_column=mail_index)
# 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
# Keep the largest column # Keep the largest column
max_len: List[int] = []
for column_cells in ws.columns: for column_cells in ws.columns:
length: int = max(len(str(cell.value)) for cell in column_cells) 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" 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)
@@ -191,7 +215,6 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
### End of Student Loop ### ### End of Student Loop ###
# Save document in HttpResponse # Save document in HttpResponse
wb.save(response) wb.save(response)

View File

@@ -114,14 +114,14 @@ class HourBuildingLegFlight(HourBuildingLegBase):
verbose_name = "Flight leg" verbose_name = "Flight leg"
verbose_name_plural = "Flight legs" verbose_name_plural = "Flight legs"
def __str__(self):
return f"{self.departure} -> {self.destination}"
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.departure = self.departure.upper().strip() self.departure = self.departure.upper().strip()
self.destination = self.destination.upper().strip() self.destination = self.destination.upper().strip()
super().save(*args, **kwargs) super().save(*args, **kwargs)
def __str__(self):
return f"{self.departure} -> {self.destination}"
class HourBuildingLegStop(HourBuildingLegBase): class HourBuildingLegStop(HourBuildingLegBase):
refuel = models.BooleanField( refuel = models.BooleanField(