Files
timetable/app/xlsx_grid_helper.py
Talhadeveloperr a7bb2f3dae initial project
2026-09-09 17:00:54 +05:00

123 lines
4.2 KiB
Python

"""
Shared openpyxl helpers for rendering a day x time-slot timetable grid.
Used by every templates/*-wise/*_template.py module so styling stays
consistent across views.
"""
import os
from openpyxl import Workbook
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from openpyxl.utils import get_column_letter
DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
HEADER_FILL = PatternFill(start_color="1F4E78", end_color="1F4E78", fill_type="solid")
HEADER_FONT = Font(color="FFFFFF", bold=True)
LUNCH_FILL = PatternFill(start_color="D9D9D9", end_color="D9D9D9", fill_type="solid")
GENERAL_FILL = PatternFill(start_color="FFF2CC", end_color="FFF2CC", fill_type="solid")
LAB_FILL = PatternFill(start_color="D9EAD3", end_color="D9EAD3", fill_type="solid")
THEORY_FILL = PatternFill(start_color="DDEBF7", end_color="DDEBF7", fill_type="solid")
THIN_BORDER = Border(
left=Side(style="thin"), right=Side(style="thin"),
top=Side(style="thin"), bottom=Side(style="thin"),
)
CENTER = Alignment(horizontal="center", vertical="center", wrap_text=True)
def cell_fill_for(session):
if session is None:
return None
if session.get("is_general"):
return GENERAL_FILL
if session.get("course_type") == "lab":
return LAB_FILL
return THEORY_FILL
def build_grid_sheet(ws, title, subtitle, time_slots, sessions_by_day_slot, cell_text_fn):
"""
Renders one timetable grid onto worksheet `ws`.
time_slots: ordered list of dicts: {"label": str, "start": str, "end": str,
"is_lunch": bool}
sessions_by_day_slot: dict[(day, slot_index)] -> session dict (or list of
session dicts for slots spanning a double/lab period), used
only to decide fill color via cell_fill_for().
cell_text_fn: function(day, slot_index) -> str to render into the cell.
"""
invalid_chars = ':\\/?*[]'
sheet_name = "".join(c for c in title if c not in invalid_chars)
ws.title = sheet_name[:31] # Excel sheet name limit
ws.merge_cells("A1:F1")
ws["A1"] = title
ws["A1"].font = Font(bold=True, size=14)
ws["A1"].alignment = CENTER
if subtitle:
ws.merge_cells("A2:F2")
ws["A2"] = subtitle
ws["A2"].font = Font(italic=True, size=10, color="555555")
ws["A2"].alignment = CENTER
header_row = 4
ws.cell(row=header_row, column=1, value="Time").font = HEADER_FONT
ws.cell(row=header_row, column=1).fill = HEADER_FILL
ws.cell(row=header_row, column=1).alignment = CENTER
ws.cell(row=header_row, column=1).border = THIN_BORDER
for col, day in enumerate(DAYS, start=2):
c = ws.cell(row=header_row, column=col, value=day)
c.font = HEADER_FONT
c.fill = HEADER_FILL
c.alignment = CENTER
c.border = THIN_BORDER
for row_offset, slot in enumerate(time_slots):
row = header_row + 1 + row_offset
time_cell = ws.cell(row=row, column=1, value=f'{slot["start"]} - {slot["end"]}')
time_cell.font = Font(bold=True, size=9)
time_cell.alignment = CENTER
time_cell.border = THIN_BORDER
if slot.get("is_lunch"):
time_cell.fill = LUNCH_FILL
for col, day in enumerate(DAYS, start=2):
cell = ws.cell(row=row, column=col)
cell.border = THIN_BORDER
cell.alignment = CENTER
cell.font = Font(size=9)
if slot.get("is_lunch"):
cell.fill = LUNCH_FILL
cell.value = "LUNCH BREAK"
continue
text = cell_text_fn(day, row_offset)
cell.value = text if text else ""
session = sessions_by_day_slot.get((day, row_offset))
fill = cell_fill_for(session)
if fill:
cell.fill = fill
ws.column_dimensions["A"].width = 16
for col in range(2, 2 + len(DAYS)):
ws.column_dimensions[get_column_letter(col)].width = 26
for row_offset in range(len(time_slots)):
ws.row_dimensions[header_row + 1 + row_offset].height = 42
def save_workbook(wb, output_dir, filename):
os.makedirs(output_dir, exist_ok=True)
path = os.path.join(output_dir, filename)
wb.save(path)
return path
def new_workbook():
wb = Workbook()
return wb