Files
catops/techdb/flightslot/actions/exportweek.py
2025-11-18 14:52:05 +01:00

198 lines
8.6 KiB
Python

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,HourBuildingLeg
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, "Cell.", "Mail", "Notes"]
# 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_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, student_phone, student_email, mission_notes])
# Fill HourBuilding rows
hb_name: str
hb_days: List[str]
hb_notes: 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 = f"{h.notes}\n----\n" if h.notes else ""
hb_legs = HourBuildingLeg.objects.filter(hb_id = h.id)
for hh in hb_legs:
hb_notes += f"{hh.departure} -> {hh.destination} [{hh.time}]\n" if not hh.stop else f"STOP at {hh.departure} [{hh.time}]\n"
hb_notes.strip('\n')
hb_data.append([str(q.week), *student_data, *hb_days, str(q.student.phone), q.student.email, hb_notes])
# 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
# 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)
# 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
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