Dockerized

This commit is contained in:
Martin Sukany
2025-11-10 18:05:37 +01:00
parent 604e159dd3
commit f476a1009c
3 changed files with 195 additions and 318 deletions

View File

@@ -23,10 +23,11 @@ COPY templates ./templates
# Ensure CGI scripts are executable # Ensure CGI scripts are executable
RUN find /var/www/htdocs/cgi-bin -type f -name "*.py" -exec chmod 0755 {} \; RUN find /var/www/htdocs/cgi-bin -type f -name "*.py" -exec chmod 0755 {} \;
# Writable tmp for the app # Writable tmp + kompatibilita s /scripts/tmp (skrypt nic neupravujeme)
RUN mkdir -p /var/www/htdocs/tmp \ RUN mkdir -p /var/www/htdocs/tmp \
&& chown -R www-data:www-data /var/www/htdocs/tmp /var/www/htdocs/templates \ /var/www/htdocs/scripts/tmp \
&& chmod 0775 /var/www/htdocs/tmp && chown -R www-data:www-data /var/www/htdocs/tmp /var/www/htdocs/scripts \
&& chmod 0775 /var/www/htdocs/tmp /var/www/htdocs/scripts/tmp
# --- Python dependencies (add more as needed) --- # --- Python dependencies (add more as needed) ---
RUN pip install --no-cache-dir pandas openpyxl RUN pip install --no-cache-dir pandas openpyxl

View File

@@ -3,15 +3,22 @@
import cgi import cgi
import cgitb import cgitb
import html
import pandas as pd import pandas as pd
from openpyxl import Workbook from openpyxl import Workbook
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from openpyxl.utils import get_column_letter from openpyxl.utils import get_column_letter
from datetime import datetime, time from datetime import datetime
from io import BytesIO from io import BytesIO
import base64 import base64
import os import os
# ===== Config =====
DOCROOT = "/var/www/htdocs"
TMP_DIR = os.path.join(DOCROOT, "tmp") # soubory budou dostupné jako /tmp/<soubor>
DEFAULT_COLOR = "#ffffff" # výchozí barva pro <input type="color">
# ===================
cgitb.enable() cgitb.enable()
print("Content-Type: text/html; charset=utf-8") print("Content-Type: text/html; charset=utf-8")
@@ -24,9 +31,7 @@ detail = form.getvalue('detail')
show_debug = form.getvalue('debug') == 'on' show_debug = form.getvalue('debug') == 'on'
step = form.getvalue('step', '1') step = form.getvalue('step', '1')
file_item = None file_item = form['file'] if 'file' in form else None
if 'file' in form:
file_item = form['file']
def get_program_types(form): def get_program_types(form):
program_descriptions = {} program_descriptions = {}
@@ -36,9 +41,10 @@ def get_program_types(form):
index = key.split('_')[-1] index = key.split('_')[-1]
type_code = form.getvalue(f'type_code_{index}') type_code = form.getvalue(f'type_code_{index}')
description = form.getvalue(f'desc_{index}', '') description = form.getvalue(f'desc_{index}', '')
color = 'FF' + form.getvalue(f'color_{index}', 'FFFFFF').lstrip('#') raw_color = form.getvalue(f'color_{index}', DEFAULT_COLOR) or DEFAULT_COLOR
color_hex = 'FF' + raw_color.lstrip('#') # openpyxl chce AARRGGBB
program_descriptions[type_code] = description program_descriptions[type_code] = description
program_colors[type_code] = color program_colors[type_code] = color_hex
return program_descriptions, program_colors return program_descriptions, program_colors
program_descriptions, program_colors = get_program_types(form) program_descriptions, program_colors = get_program_types(form)
@@ -52,12 +58,11 @@ def normalize_time(time_str):
return None return None
def read_excel(file_content): def read_excel(file_content):
excel_data = pd.read_excel(BytesIO(file_content), skiprows=0) # Do not skip the header row excel_data = pd.read_excel(BytesIO(file_content), skiprows=0)
excel_data.columns = ["Datum", "Zacatek", "Konec", "Program", "Typ", "Garant", "Poznamka"] excel_data.columns = ["Datum", "Zacatek", "Konec", "Program", "Typ", "Garant", "Poznamka"]
if show_debug: if show_debug:
print("<pre>") print("<pre>Raw data:\n")
print("Raw data:")
print(excel_data.head()) print(excel_data.head())
print("</pre>") print("</pre>")
@@ -76,7 +81,7 @@ def read_excel(file_content):
"index": index, "index": index,
"Datum": datum, "Datum": datum,
"Zacatek": zacatek, "Zacatek": zacatek,
"Konec": row["Konec"], # Changed from `konec` to `row["Konec"]` to avoid ValueError in future calculations "Konec": konec,
"Program": row["Program"], "Program": row["Program"],
"Typ": row["Typ"], "Typ": row["Typ"],
"Garant": row["Garant"], "Garant": row["Garant"],
@@ -84,55 +89,48 @@ def read_excel(file_content):
"row_data": row "row_data": row
}) })
except Exception as e: except Exception as e:
error_rows.append({ error_rows.append({"index": index, "row": row, "error": str(e)})
"index": index,
"row": row,
"error": str(e)
})
valid_data = pd.DataFrame(valid_data) valid_data = pd.DataFrame(valid_data)
if show_debug: if show_debug:
print("<pre>") print("<pre>Cleaned data:\n")
print("Cleaned data:")
print(valid_data.head()) print(valid_data.head())
print("</pre>") print("</pre>")
print("<pre>") print("<pre>Error rows:\n")
print("Error rows:") for er in error_rows:
for error_row in error_rows: print(f"Index: {er['index']}, Error: {er['error']}")
print(f"Index: {error_row['index']}, Error: {error_row['error']}") print(er['row'])
print(error_row['row'])
print("</pre>") print("</pre>")
# Detekce překryvů
overlap_errors = [] overlap_errors = []
for date, group in valid_data.groupby('Datum'): for date, group in valid_data.groupby('Datum'):
sorted_group = group.sort_values(by='Zacatek') sorted_group = group.sort_values(by='Zacatek')
previous_end_time = None previous_end_time = None
for _, r in sorted_group.iterrows():
for idx, row in sorted_group.iterrows(): if previous_end_time and r['Zacatek'] < previous_end_time:
if previous_end_time and row['Zacatek'] < previous_end_time:
overlap_errors.append({ overlap_errors.append({
"index": row["index"], "index": r["index"],
"Datum": row["Datum"], "Datum": r["Datum"],
"Zacatek": row["Zacatek"], "Zacatek": r["Zacatek"],
"Konec": row["Konec"], "Konec": r["Konec"],
"Program": row["Program"], "Program": r["Program"],
"Typ": row["Typ"], "Typ": r["Typ"],
"Garant": row["Garant"], "Garant": r["Garant"],
"Poznamka": row["Poznamka"], "Poznamka": r["Poznamka"],
"Error": f"Overlapping time block with previous block ending at {previous_end_time}", "Error": f"Overlapping time block with previous block ending at {previous_end_time}",
"row_data": row["row_data"] "row_data": r["row_data"]
}) })
previous_end_time = row['Konec'] previous_end_time = r['Konec']
if overlap_errors: if overlap_errors:
if show_debug: if show_debug:
print("<pre>") print("<pre>Overlap errors:\n")
print("Overlap errors:") for e in overlap_errors:
for error in overlap_errors: print(e)
print(error)
print("</pre>") print("</pre>")
valid_data = valid_data[~valid_data.index.isin([error['index'] for error in overlap_errors])] valid_data = valid_data[~valid_data.index.isin([e['index'] for e in overlap_errors])]
error_rows.extend(overlap_errors) error_rows.extend(overlap_errors)
return valid_data.drop(columns='index'), error_rows return valid_data.drop(columns='index'), error_rows
@@ -140,15 +138,17 @@ def read_excel(file_content):
def calculate_row_height(cell_value, column_width): def calculate_row_height(cell_value, column_width):
if not cell_value: if not cell_value:
return 15 return 15
max_line_length = column_width * 1.2 max_line_length = column_width * 1.2
lines = cell_value.split('\n') lines = str(cell_value).split('\n')
line_count = 0 line_count = 0
for line in lines: for line in lines:
line_count += len(line) // max_line_length + 1 line_count += len(line) // max_line_length + 1
return line_count * 15 return line_count * 15
def calculate_column_width(text):
max_length = max(len(line) for line in str(text).split('\n'))
return max_length * 1.2
def create_timetable(data, title, detail, program_descriptions, program_colors): def create_timetable(data, title, detail, program_descriptions, program_colors):
if data.empty: if data.empty:
print("<p>Chyba: Načtená data jsou prázdná nebo obsahují neplatné hodnoty. Zkontrolujte vstupní soubor.</p>") print("<p>Chyba: Načtená data jsou prázdná nebo obsahují neplatné hodnoty. Zkontrolujte vstupní soubor.</p>")
@@ -177,6 +177,10 @@ def create_timetable(data, title, detail, program_descriptions, program_colors):
ws['A2'].font = Font(size=16, italic=True) ws['A2'].font = Font(size=16, italic=True)
ws['A2'].border = thick_border ws['A2'].border = thick_border
# rozumný default šířky prvního sloupce
if ws.column_dimensions[get_column_letter(1)].width is None:
ws.column_dimensions[get_column_letter(1)].width = 40
title_row_height = calculate_row_height(title, ws.column_dimensions[get_column_letter(1)].width) 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) 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[1].height = title_row_height
@@ -190,10 +194,9 @@ def create_timetable(data, title, detail, program_descriptions, program_colors):
if start_times.isnull().any() or end_times.isnull().any(): if start_times.isnull().any() or end_times.isnull().any():
print("<p>Chyba: Načtená data obsahují neplatné hodnoty času. Zkontrolujte vstupní soubor.</p>") print("<p>Chyba: Načtená data obsahují neplatné hodnoty času. Zkontrolujte vstupní soubor.</p>")
if show_debug: if show_debug:
print("<pre>") print("<pre>Start times:\n")
print("Start times:")
print(start_times) print(start_times)
print("End times:") print("\nEnd times:\n")
print(end_times) print(end_times)
print("</pre>") print("</pre>")
return None return None
@@ -202,17 +205,20 @@ def create_timetable(data, title, detail, program_descriptions, program_colors):
min_time = min(start_times) min_time = min(start_times)
max_time = max(end_times) max_time = max(end_times)
except ValueError as e: except ValueError as e:
print("<p>Chyba při zjišťování minimálního a maximálního času: {}</p>".format(e)) print(f"<p>Chyba při zjišťování minimálního a maximálního času: {e}</p>")
if show_debug: if show_debug:
print("<pre>") print("<pre>Start times:\n")
print("Start times:")
print(start_times) print(start_times)
print("End times:") print("\nEnd times:\n")
print(end_times) print(end_times)
print("</pre>") print("</pre>")
return None return None
time_slots = pd.date_range(datetime.combine(datetime.today(), min_time), datetime.combine(datetime.today(), max_time), freq='15min').time 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 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=1, start_column=1, end_row=1, end_column=total_columns)
@@ -253,12 +259,9 @@ def create_timetable(data, title, detail, program_descriptions, program_colors):
start_index = list(time_slots).index(start_time) + col_offset + 1 start_index = list(time_slots).index(start_time) + col_offset + 1
end_index = list(time_slots).index(end_time) + col_offset + 1 end_index = list(time_slots).index(end_time) + col_offset + 1
except ValueError as e: except ValueError as e:
print("<p>Chyba při hledání indexu časového slotu: {}</p>".format(e)) print(f"<p>Chyba při hledání indexu časového slotu: {e}</p>")
if show_debug: if show_debug:
print("<pre>") print("<pre>Start time: {}\nEnd time: {}</pre>".format(start_time, end_time))
print("Start time: {}".format(start_time))
print("End time: {}".format(end_time))
print("</pre>")
continue continue
cell_value = f"{row['Program']}" cell_value = f"{row['Program']}"
@@ -274,14 +277,12 @@ def create_timetable(data, title, detail, program_descriptions, program_colors):
except AttributeError as e: except AttributeError as e:
print(f"<p>Chyba: {str(e)}. Zkontrolujte vstupní data, která způsobují překrývající se časy bloků.</p>") print(f"<p>Chyba: {str(e)}. Zkontrolujte vstupní data, která způsobují překrývající se časy bloků.</p>")
if show_debug: if show_debug:
print("<pre>") print("<pre>Overlapping block:\n{}</pre>".format(row))
print(f"Overlapping block:\n{row}")
print("</pre>")
return None return None
cell.alignment = Alignment(wrap_text=True, horizontal="center", vertical="center") cell.alignment = Alignment(wrap_text=True, horizontal="center", vertical="center")
lines = cell_value.split("\n") lines = str(cell_value).split("\n")
for idx, line in enumerate(lines): for idx, _ in enumerate(lines):
if idx == 0: if idx == 0:
cell.font = Font(bold=True) cell.font = Font(bold=True)
elif idx == 1: elif idx == 1:
@@ -289,7 +290,9 @@ def create_timetable(data, title, detail, program_descriptions, program_colors):
elif idx > 1 and pd.notna(row['Poznamka']): elif idx > 1 and pd.notna(row['Poznamka']):
cell.font = Font(italic=True) cell.font = Font(italic=True)
cell.fill = PatternFill(start_color=program_colors[row["Typ"]], end_color=program_colors[row["Typ"]], fill_type="solid") cell.fill = PatternFill(start_color=program_colors[row["Typ"]],
end_color=program_colors[row["Typ"]],
fill_type="solid")
cell.border = thick_border cell.border = thick_border
current_row += 1 current_row += 1
@@ -305,8 +308,7 @@ def create_timetable(data, title, detail, program_descriptions, program_colors):
legend_max_length = max(legend_max_length, calculate_column_width(legend_text)) legend_max_length = max(legend_max_length, calculate_column_width(legend_text))
legend_row += 1 legend_row += 1
ws.column_dimensions[get_column_letter(col_offset)].width = legend_max_length ws.column_dimensions[get_column_letter(1)].width = legend_max_length
for col in range(2, total_columns + 1): for col in range(2, total_columns + 1):
ws.column_dimensions[get_column_letter(col)].width = 15 ws.column_dimensions[get_column_letter(col)].width = 15
@@ -326,119 +328,60 @@ def create_timetable(data, title, detail, program_descriptions, program_colors):
return wb return wb
def calculate_column_width(text): # ====== HTML flow ======
max_length = max(len(line) for line in text.split('\n'))
return max_length * 1.2
if step == '1': if step == '1':
print(''' print('''<!DOCTYPE html>
<!DOCTYPE html> <html lang="cs">
<html lang="cs"> <head>
<head> <meta charset="UTF-8">
<meta charset="UTF-8"> <title>Vytvoření Scénáře - Krok 1</title>
<title>Vytvoření Scénáře - Krok 1</title> <style>
<style> body { font-family: Arial, sans-serif; margin: 20px; background-color: #f0f4f8; color: #333; }
body { .form-container { max-width: 600px; margin: auto; padding: 20px; background-color: #fff;
font-family: Arial, sans-serif; box-shadow: 0 0 10px rgba(0,0,0,0.1); border-radius: 8px; }
margin: 20px; .form-group { margin-bottom: 15px; }
background-color: #f0f4f8; .form-group label { display: block; font-weight: bold; margin-bottom: 5px; color: #555; }
color: #333; .form-group input, .form-group textarea, .form-group select {
} width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 5px; box-sizing: border-box; margin-top: 5px; }
.form-container { .form-group input[type="color"] { width: auto; padding: 5px; }
max-width: 600px; .form-group button { padding: 10px 15px; border: none; background-color: #007BFF; color: white;
margin: auto; border-radius: 5px; cursor: pointer; }
padding: 20px; .form-group button:hover { background-color: #0056b3; }
background-color: #fff; .footer { margin-top: 20px; text-align: center; font-size: 0.9em; color: #777; }
box-shadow: 0 0 10px rgba(0,0,0,0.1); </style>
border-radius: 8px; </head>
} <body>
.form-group { <div class="form-container">
margin-bottom: 15px; <h1>Vytvoření Scénáře - Krok 1</h1>
} <p><a href="/templates/scenar_template.xlsx">STÁHNOUT ŠABLONU SCÉNÁŘE ZDE</a></p>
.form-group label { <form action="/cgi-bin/scenar.py" method="post" enctype="multipart/form-data">
display: block; <div class="form-group">
font-weight: bold; <label for="title">Název akce:</label>
margin-bottom: 5px; <input type="text" id="title" name="title" required>
color: #555; </div>
} <div class="form-group">
.form-group input, .form-group textarea, .form-group select { <label for="detail">Detail akce:</label>
width: 100%; <input type="text" id="detail" name="detail" required>
padding: 10px; </div>
border: 1px solid #ccc; <div class="form-group">
border-radius: 5px; <label for="file">Excel soubor:</label>
box-sizing: border-box; <input type="file" id="file" name="file" accept=".xlsx" required>
margin-top: 5px; </div>
} <div class="form-group">
.form-group input[type="color"] { <label for="debug">Zobrazit debug informace:</label>
width: auto; <input type="checkbox" id="debug" name="debug">
padding: 5px; </div>
} <div class="form-group">
.form-group button { <input type="hidden" name="step" value="2">
padding: 10px 15px; <input type="submit" value="Načíst typy programu">
border: none; </div>
background-color: #007BFF; </form>
color: white; </div>
border-radius: 5px; <div class="footer">
cursor: pointer; <p>© 2024 Martin Sukaný • <a href="mailto:martin@sukany.cz">martin@sukany.cz</a></p>
} </div>
.form-group button:hover { </body>
background-color: #0056b3; </html>''')
}
.output-container {
max-width: 800px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
background: #f9f9f9;
}
.footer {
margin-top: 20px;
text-align: center;
font-size: 0.9em;
color: #777;
}
</style>
</head>
<body>
<div class="form-container">
<h1>Vytvoření Scénáře - Krok 1</h1>
<p>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".</p>
<p><a href="/templates/scenar_template.xlsx">STAHNOUT SABLONU SCENARE ZDE</a></p>
<form action="/cgi-bin/scenar.py" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="title">Název akce:</label>
<input type="text" id="title" name="title" required>
</div>
<div class="form-group">
<label for="detail">Detail akce:</label>
<input type="text" id="detail" name="detail" required>
</div>
<div class="form-group">
<label for="file">Excel soubor:</label>
<input type="file" id="file" name="file" accept=".xlsx" required>
</div>
<div class="form-group">
<label for="debug">Zobrazit debug informace:</label>
<input type="checkbox" id="debug" name="debug">
</div>
<div class="form-group">
<input type="hidden" name="step" value="2">
<input type="submit" value="Načíst typy programu">
</div>
</form>
</div>
<div class="footer">
<p>© 2024 Martin Sukaný. Vytvořil Martin Sukaný. Podpora: <a href="mailto:martin@sukany.cz">martin@sukany.cz</a></p>
</div>
</body>
</html>
''')
elif step == '2' and file_item is not None and file_item.filename: elif step == '2' and file_item is not None and file_item.filename:
file_content = file_item.file.read() file_content = file_item.file.read()
@@ -448,165 +391,98 @@ elif step == '2' and file_item is not None and file_item.filename:
print("<p>Chyba: Načtená data jsou prázdná nebo obsahují neplatné hodnoty. Zkontrolujte vstupní soubor.</p>") print("<p>Chyba: Načtená data jsou prázdná nebo obsahují neplatné hodnoty. Zkontrolujte vstupní soubor.</p>")
else: else:
program_types = data["Typ"].dropna().unique() program_types = data["Typ"].dropna().unique()
program_types = [typ.strip() for typ in program_types] # Remove any whitespace characters program_types = [typ.strip() for typ in program_types]
print(''' print('''<!DOCTYPE html>
<!DOCTYPE html> <html lang="cs">
<html lang="cs"> <head>
<head> <meta charset="UTF-8">
<meta charset="UTF-8"> <title>Vytvoření Scénáře - Krok 2</title>
<title>Vytvoření Scénáře - Krok 2</title> <style>
<style> body {{ font-family: Arial, sans-serif; margin: 20px; background-color: #f0f4f8; color: #333; }}
body {{ .form-container {{ max-width: 600px; margin: auto; padding: 20px; background-color: #fff;
font-family: Arial, sans-serif; box-shadow: 0 0 10px rgba(0,0,0,0.1); border-radius: 8px; }}
margin: 20px; .form-group {{ margin-bottom: 15px; }}
background-color: #f0f4f8; .form-group label {{ display: block; font-weight: bold; margin-bottom: 5px; color: #555; }}
color: #333; .form-group input, .form-group textarea, .form-group select {{
}} width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 5px; box-sizing: border-box; margin-top: 5px; }}
.form-container {{ .form-group input[type="color"] {{ width: auto; padding: 5px; }}
max-width: 600px; .form-group button {{ padding: 10px 15px; border: none; background-color: #007BFF; color: white;
margin: auto; border-radius: 5px; cursor: pointer; }}
padding: 20px; .form-group button:hover {{ background-color: #0056b3; }}
background-color: #fff; .footer {{ margin-top: 20px; text-align: center; font-size: 0.9em; color: #777; }}
box-shadow: 0 0 10px rgba(0,0,0,0.1); </style>
border-radius: 8px; </head>
}} <body>
.form-group {{ <div class="form-container">
margin-bottom: 15px; <h1>Vytvoření Scénáře - Krok 2</h1>
}} <p>Vyplň tituly a barvy pro nalezené typy programu a odešli.</p>
.form-group label {{ <form action="/cgi-bin/scenar.py" method="post">
display: block; <input type="hidden" name="title" value="{title}">
font-weight: bold; <input type="hidden" name="detail" value="{detail}">
margin-bottom: 5px; <input type="hidden" name="file_content_base64" value="{fcb64}">
color: #555; <input type="hidden" name="step" value="3">'''.format(
}} title=html.escape(title or ""),
.form-group input, .form-group textarea, .form-group select {{ detail=html.escape(detail or ""),
width: 100%; fcb64=html.escape(file_content_base64))
padding: 10px; )
border: 1px solid #ccc;
border-radius: 5px;
box-sizing: border-box;
margin-top: 5px;
}}
.form-group input[type="color"] {{
width: auto;
padding: 5px;
}}
.form-group button {{
padding: 10px 15px;
border: none;
background-color: #007BFF;
color: white;
border-radius: 5px;
cursor: pointer;
}}
.form-group button:hover {{
background-color: #0056b3;
}}
.output-container {{
max-width: 800px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
background: #f9f9f9;
}}
.footer {{
margin-top: 20px;
text-align: center;
font-size: 0.9em;
color: #777;
}}
</style>
</head>
<body>
<div class="form-container">
<h1>Vytvoření Scénáře - Krok 2</h1>
<p>Prosím, vyplňte tituly a barvy pro nalezené typy programu. Po vyplnění formuláře klikněte na tlačítko "Vygenerovat scénář".</p>
<form action="/cgi-bin/scenar.py" method="post">
<input type="hidden" name="title" value="{}">
<input type="hidden" name="detail" value="{}">
<input type="hidden" name="file_content_base64" value="{}">
<input type="hidden" name="step" value="3">
'''.format(title, detail, file_content_base64))
for i, typ in enumerate(program_types, start=1): for i, typ in enumerate(program_types, start=1):
print(''' print('''
<div class="form-group program-type"> <div class="form-group program-type">
<label for="type_code_{}">Typ programu {}:</label> <label for="type_code_{i}">Typ programu {i}:</label>
<input type="text" id="type_code_{}" name="type_code_{}" value="{}" required> <input type="text" id="type_code_{i}" name="type_code_{i}" value="{typ}" required>
<input type="text" id="desc_{}" name="desc_{}" required> <input type="text" id="desc_{i}" name="desc_{i}" required>
<input type="color" id="color_{}" name="color_{}" required> <input type="color" id="color_{i}" name="color_{i}" value="{default_color}" required>
</div> </div>'''.format(i=i, typ=html.escape(typ), default_color=DEFAULT_COLOR))
'''.format(i, i, i, i, typ, i, i, i, i))
print(''' print('''
<div class="form-group"> <div class="form-group">
<label for="debug">Zobrazit debug informace:</label> <label for="debug">Zobrazit debug informace:</label>
<input type="checkbox" id="debug" name="debug"{}> <input type="checkbox" id="debug" name="debug"{checked}>
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="submit" value="Vygenerovat scénář"> <input type="submit" value="Vygenerovat scénář">
</div> </div>
</form> </form>
</div> </div>
<div class="footer"> <div class="footer">
<p>© 2024 Martin Sukaný. Vytvořil Martin Sukaný. Podpora: <a href="mailto:martin@sukany.cz">martin@sukany.cz</a></p> <p>© 2024 Martin Sukaný <a href="mailto:martin@sukany.cz">martin@sukany.cz</a></p>
</div> </div>
</body> </body>
</html> </html>'''.format(checked=' checked' if show_debug else ''))
'''.format(' checked' if show_debug else ''))
elif step == '3' and title and detail: elif step == '3' and title and detail:
file_content_base64 = form.getvalue('file_content_base64') file_content_base64 = form.getvalue('file_content_base64')
if file_content_base64: if file_content_base64:
file_content = base64.b64decode(file_content_base64) file_content = base64.b64decode(file_content_base64)
data, error_rows = read_excel(file_content) data, error_rows = read_excel(file_content)
if data.empty: if data.empty:
print("<p>Chyba: Načtená data jsou prázdná nebo obsahují neplatné hodnoty. Zkontrolujte vstupní soubor.</p>") print("<p>Chyba: Načtená data jsou prázdná nebo obsahují neplatné hodnoty. Zkontrolujte vstupní soubor.</p>")
else: else:
# z POSTu teď přijdou zvolené popisy a barvy
program_descriptions, program_colors = get_program_types(form)
wb = create_timetable(data, title, detail, program_descriptions, program_colors) wb = create_timetable(data, title, detail, program_descriptions, program_colors)
if wb: if wb:
os.makedirs(TMP_DIR, exist_ok=True)
filename = f"{title}.xlsx" filename = f"{title}.xlsx"
file_path = os.path.join("/var/www/htdocs/scripts/tmp", filename) safe_name = "".join(ch if ch.isalnum() or ch in "._- " else "_" for ch in filename)
file_path = os.path.join(TMP_DIR, safe_name)
wb.save(file_path) wb.save(file_path)
print(f''' print('''<div class="output-container">
<div class="output-container"> <h2>Výsledky zpracování</h2>
<h2>Výsledky zpracování</h2> <p><a href="/tmp/{name}" download>Stáhnout scénář pro {title} ZDE</a></p>
<p><a href="/tmp/{filename}" download>Stáhnout scénář pro {title} ZDE</a></p> <p><strong>Název akce:</strong> {title}</p>
<p><strong>Název akce:</strong> {title}</p> <p><strong>Detail akce:</strong> {detail}</p>
<p><strong>Detail akce:</strong> {detail}</p> <h3>Data ze souboru:</h3>
<h3>Data ze souboru:</h3> {table}
{data.to_html(index=False)} <p><a href="/tmp/{name}" download>Stáhnout scénář pro {title} ZDE</a></p>
<p><a href="/tmp/{filename}" download>Stáhnout scénář pro {title} ZDE</a></p> </div>'''.format(
</div> name=html.escape(safe_name),
''') title=html.escape(title or ""),
detail=html.escape(detail or ""),
if error_rows: table=data.to_html(index=False)))
print(f'''
<div class="error-container">
<h3>Ignorované řádky:</h3>
<table border="1">
<tr>
<th>Index</th>
<th>Řádek</th>
<th>Důvod</th>
</tr>
''')
for error_row in error_rows:
print(f'''
<tr>
<td>{error_row["index"]}</td>
<td>{error_row["row"].to_dict()}</td>
<td>{error_row["error"]}</td>
</tr>
''')
print('''
</table>
</div>
''')
else: else:
print("<p>Chyba: Soubor nebyl nalezen. Zkontrolujte vstupní data.</p>") print("<p>Chyba: Soubor nebyl nalezen. Zkontrolujte vstupní data.</p>")
else: else:

Binary file not shown.