#!/usr/bin/env python3 # -*- coding: utf-8 -*- import cgi import cgitb import pandas as pd from openpyxl import Workbook from openpyxl.styles import Alignment, Border, Font, PatternFill, Side from openpyxl.utils import get_column_letter from datetime import datetime, time from io import BytesIO import base64 import os cgitb.enable() print("Content-Type: text/html; charset=utf-8") print() form = cgi.FieldStorage() title = form.getvalue('title') detail = form.getvalue('detail') show_debug = form.getvalue('debug') == 'on' step = form.getvalue('step', '1') file_item = None if 'file' in form: file_item = form['file'] def get_program_types(form): program_descriptions = {} program_colors = {} for key in form.keys(): if key.startswith('type_code_'): index = key.split('_')[-1] type_code = form.getvalue(f'type_code_{index}') description = form.getvalue(f'desc_{index}', '') color = 'FF' + form.getvalue(f'color_{index}', 'FFFFFF').lstrip('#') program_descriptions[type_code] = description program_colors[type_code] = color return program_descriptions, program_colors program_descriptions, program_colors = get_program_types(form) def normalize_time(time_str): for fmt in ('%H:%M', '%H:%M:%S'): try: return datetime.strptime(time_str, fmt).time() except ValueError: continue return None def read_excel(file_content): excel_data = pd.read_excel(BytesIO(file_content), skiprows=0) # Do not skip the header row excel_data.columns = ["Datum", "Zacatek", "Konec", "Program", "Typ", "Garant", "Poznamka"] if show_debug: print("
")
print("Raw data:")
print(excel_data.head())
print("")
error_rows = []
valid_data = []
for index, row in excel_data.iterrows():
try:
datum = pd.to_datetime(row["Datum"], errors='coerce').date()
zacatek = normalize_time(str(row["Zacatek"]))
konec = normalize_time(str(row["Konec"]))
if pd.isna(datum) or zacatek is None or konec is None:
raise ValueError("Invalid date or time format")
valid_data.append({
"index": index,
"Datum": datum,
"Zacatek": zacatek,
"Konec": row["Konec"], # Changed from `konec` to `row["Konec"]` to avoid ValueError in future calculations
"Program": row["Program"],
"Typ": row["Typ"],
"Garant": row["Garant"],
"Poznamka": row["Poznamka"],
"row_data": row
})
except Exception as e:
error_rows.append({
"index": index,
"row": row,
"error": str(e)
})
valid_data = pd.DataFrame(valid_data)
if show_debug:
print("")
print("Cleaned data:")
print(valid_data.head())
print("")
print("")
print("Error rows:")
for error_row in error_rows:
print(f"Index: {error_row['index']}, Error: {error_row['error']}")
print(error_row['row'])
print("")
overlap_errors = []
for date, group in valid_data.groupby('Datum'):
sorted_group = group.sort_values(by='Zacatek')
previous_end_time = None
for idx, row in sorted_group.iterrows():
if previous_end_time and row['Zacatek'] < previous_end_time:
overlap_errors.append({
"index": row["index"],
"Datum": row["Datum"],
"Zacatek": row["Zacatek"],
"Konec": row["Konec"],
"Program": row["Program"],
"Typ": row["Typ"],
"Garant": row["Garant"],
"Poznamka": row["Poznamka"],
"Error": f"Overlapping time block with previous block ending at {previous_end_time}",
"row_data": row["row_data"]
})
previous_end_time = row['Konec']
if overlap_errors:
if show_debug:
print("")
print("Overlap errors:")
for error in overlap_errors:
print(error)
print("")
valid_data = valid_data[~valid_data.index.isin([error['index'] for error in overlap_errors])]
error_rows.extend(overlap_errors)
return valid_data.drop(columns='index'), error_rows
def calculate_row_height(cell_value, column_width):
if not cell_value:
return 15
max_line_length = column_width * 1.2
lines = cell_value.split('\n')
line_count = 0
for line in lines:
line_count += len(line) // max_line_length + 1
return line_count * 15
def create_timetable(data, title, detail, program_descriptions, program_colors):
if data.empty:
print("Chyba: Načtená data jsou prázdná nebo obsahují neplatné hodnoty. Zkontrolujte vstupní soubor.
") return None missing_types = [typ for typ in data["Typ"].unique() if typ not in program_colors] if missing_types: print(f"Chyba: Následující typy programu nejsou specifikovány: {', '.join(missing_types)}. Zkontrolujte vstupní soubor a formulář.
") return None wb = Workbook() ws = wb.active thick_border = Border(left=Side(style='thick', color='000000'), right=Side(style='thick', color='000000'), top=Side(style='thick', color='000000'), bottom=Side(style='thick', color='000000')) ws['A1'] = title ws['A1'].alignment = Alignment(horizontal="center", vertical="center") ws['A1'].font = Font(size=24, bold=True) ws['A1'].border = thick_border ws['A2'] = detail ws['A2'].alignment = Alignment(horizontal="center", vertical="center") ws['A2'].font = Font(size=16, italic=True) ws['A2'].border = thick_border title_row_height = calculate_row_height(title, ws.column_dimensions[get_column_letter(1)].width) detail_row_height = calculate_row_height(detail, ws.column_dimensions[get_column_letter(1)].width) ws.row_dimensions[1].height = title_row_height ws.row_dimensions[2].height = detail_row_height data = data.sort_values(by=["Datum", "Zacatek"]) start_times = data["Zacatek"] end_times = data["Konec"] if start_times.isnull().any() or end_times.isnull().any(): print("Chyba: Načtená data obsahují neplatné hodnoty času. Zkontrolujte vstupní soubor.
") if show_debug: print("")
print("Start times:")
print(start_times)
print("End times:")
print(end_times)
print("")
return None
try:
min_time = min(start_times)
max_time = max(end_times)
except ValueError as e:
print("Chyba při zjišťování minimálního a maximálního času: {}
".format(e)) if show_debug: print("")
print("Start times:")
print(start_times)
print("End times:")
print(end_times)
print("")
return None
time_slots = pd.date_range(datetime.combine(datetime.today(), min_time), datetime.combine(datetime.today(), max_time), freq='15min').time
total_columns = len(time_slots) + 1
ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=total_columns)
ws.merge_cells(start_row=2, start_column=1, end_row=2, end_column=total_columns)
row_offset = 3
col_offset = 1
cell = ws.cell(row=row_offset, column=col_offset, value="Datum")
cell.fill = PatternFill(start_color="D3D3D3", end_color="D3D3D3", fill_type="solid")
cell.alignment = Alignment(horizontal="center", vertical="center")
cell.font = Font(bold=True)
cell.border = thick_border
for i, time_slot in enumerate(time_slots, start=col_offset + 1):
cell = ws.cell(row=row_offset, column=i, value=time_slot.strftime("%H:%M"))
cell.fill = PatternFill(start_color="D3D3D3", end_color="D3D3D3", fill_type="solid")
cell.alignment = Alignment(horizontal="center", vertical="center")
cell.font = Font(bold=True)
cell.border = thick_border
current_row = row_offset + 1
grouped_data = data.groupby(data['Datum'])
for date, group in grouped_data:
day_name = date.strftime("%A")
date_str = date.strftime(f"%d.%m {day_name}")
cell = ws.cell(row=current_row, column=col_offset, value=date_str)
cell.alignment = Alignment(horizontal="center", vertical="center")
cell.fill = PatternFill(start_color="D3D3D3", end_color="D3D3D3", fill_type="solid")
cell.font = Font(bold=True, size=14)
cell.border = thick_border
for _, row in group.iterrows():
start_time = row["Zacatek"]
end_time = row["Konec"]
try:
start_index = list(time_slots).index(start_time) + col_offset + 1
end_index = list(time_slots).index(end_time) + col_offset + 1
except ValueError as e:
print("Chyba při hledání indexu časového slotu: {}
".format(e)) if show_debug: print("")
print("Start time: {}".format(start_time))
print("End time: {}".format(end_time))
print("")
continue
cell_value = f"{row['Program']}"
if pd.notna(row['Garant']):
cell_value += f"\n{row['Garant']}"
if pd.notna(row['Poznamka']):
cell_value += f"\n\n{row['Poznamka']}"
try:
ws.merge_cells(start_row=current_row, start_column=start_index, end_row=current_row, end_column=end_index - 1)
cell = ws.cell(row=current_row, column=start_index)
cell.value = cell_value
except AttributeError as e:
print(f"Chyba: {str(e)}. Zkontrolujte vstupní data, která způsobují překrývající se časy bloků.
") if show_debug: print("")
print(f"Overlapping block:\n{row}")
print("")
return None
cell.alignment = Alignment(wrap_text=True, horizontal="center", vertical="center")
lines = cell_value.split("\n")
for idx, line in enumerate(lines):
if idx == 0:
cell.font = Font(bold=True)
elif idx == 1:
cell.font = Font(bold=False)
elif idx > 1 and pd.notna(row['Poznamka']):
cell.font = Font(italic=True)
cell.fill = PatternFill(start_color=program_colors[row["Typ"]], end_color=program_colors[row["Typ"]], fill_type="solid")
cell.border = thick_border
current_row += 1
legend_row = current_row + 2
legend_max_length = 0
ws.cell(row=legend_row, column=1, value="Legenda:").font = Font(bold=True)
legend_row += 1
for typ, desc in program_descriptions.items():
legend_text = f"{desc} ({typ})"
legend_cell = ws.cell(row=legend_row, column=1, value=legend_text)
legend_cell.fill = PatternFill(start_color=program_colors[typ], fill_type="solid")
legend_max_length = max(legend_max_length, calculate_column_width(legend_text))
legend_row += 1
ws.column_dimensions[get_column_letter(col_offset)].width = legend_max_length
for col in range(2, total_columns + 1):
ws.column_dimensions[get_column_letter(col)].width = 15
for row in ws.iter_rows(min_row=1, max_row=current_row - 1, min_col=1, max_col=total_columns):
for cell in row:
cell.alignment = Alignment(wrap_text=True, horizontal="center", vertical="center")
cell.border = thick_border
for row in ws.iter_rows(min_row=1, max_row=current_row - 1):
max_height = 0
for cell in row:
if cell.value:
height = calculate_row_height(cell.value, ws.column_dimensions[get_column_letter(cell.column)].width)
if height > max_height:
max_height = height
ws.row_dimensions[row[0].row].height = max_height
return wb
def calculate_column_width(text):
max_length = max(len(line) for line in text.split('\n'))
return max_length * 1.2
if step == '1':
print('''
Vyplňte níže uvedený formulář k vytvoření nového scénáře. Prosím, vyplňte všechny povinné položky a přiložte požadovaný Excel soubor. Po vyplnění formuláře klikněte na tlačítko "Načíst typy programu".
Chyba: Načtená data jsou prázdná nebo obsahují neplatné hodnoty. Zkontrolujte vstupní soubor.
") else: program_types = data["Typ"].dropna().unique() program_types = [typ.strip() for typ in program_types] # Remove any whitespace characters print('''Prosím, vyplňte tituly a barvy pro nalezené typy programu. Po vyplnění formuláře klikněte na tlačítko "Vygenerovat scénář".
Chyba: Načtená data jsou prázdná nebo obsahují neplatné hodnoty. Zkontrolujte vstupní soubor.
") else: wb = create_timetable(data, title, detail, program_descriptions, program_colors) if wb: filename = f"{title}.xlsx" file_path = os.path.join("/var/www/htdocs/scripts/tmp", filename) wb.save(file_path) print(f'''Stáhnout scénář pro {title} ZDE
Název akce: {title}
Detail akce: {detail}
| Index | Řádek | Důvod |
|---|---|---|
| {error_row["index"]} | {error_row["row"].to_dict()} | {error_row["error"]} |
Chyba: Soubor nebyl nalezen. Zkontrolujte vstupní data.
") else: print("Chyba: Není vybrán žádný soubor nebo došlo k chybě ve formuláři.
")