Dockerized
This commit is contained in:
@@ -23,10 +23,11 @@ COPY templates ./templates
|
||||
# Ensure CGI scripts are executable
|
||||
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 \
|
||||
&& chown -R www-data:www-data /var/www/htdocs/tmp /var/www/htdocs/templates \
|
||||
&& chmod 0775 /var/www/htdocs/tmp
|
||||
/var/www/htdocs/scripts/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) ---
|
||||
RUN pip install --no-cache-dir pandas openpyxl
|
||||
|
||||
@@ -3,15 +3,22 @@
|
||||
|
||||
import cgi
|
||||
import cgitb
|
||||
import html
|
||||
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 datetime import datetime
|
||||
from io import BytesIO
|
||||
import base64
|
||||
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()
|
||||
|
||||
print("Content-Type: text/html; charset=utf-8")
|
||||
@@ -24,9 +31,7 @@ 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']
|
||||
file_item = form['file'] if 'file' in form else None
|
||||
|
||||
def get_program_types(form):
|
||||
program_descriptions = {}
|
||||
@@ -36,9 +41,10 @@ def get_program_types(form):
|
||||
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('#')
|
||||
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_colors[type_code] = color
|
||||
program_colors[type_code] = color_hex
|
||||
return program_descriptions, program_colors
|
||||
|
||||
program_descriptions, program_colors = get_program_types(form)
|
||||
@@ -52,12 +58,11 @@ def normalize_time(time_str):
|
||||
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 = pd.read_excel(BytesIO(file_content), skiprows=0)
|
||||
excel_data.columns = ["Datum", "Zacatek", "Konec", "Program", "Typ", "Garant", "Poznamka"]
|
||||
|
||||
if show_debug:
|
||||
print("<pre>")
|
||||
print("Raw data:")
|
||||
print("<pre>Raw data:\n")
|
||||
print(excel_data.head())
|
||||
print("</pre>")
|
||||
|
||||
@@ -76,7 +81,7 @@ def read_excel(file_content):
|
||||
"index": index,
|
||||
"Datum": datum,
|
||||
"Zacatek": zacatek,
|
||||
"Konec": row["Konec"], # Changed from `konec` to `row["Konec"]` to avoid ValueError in future calculations
|
||||
"Konec": konec,
|
||||
"Program": row["Program"],
|
||||
"Typ": row["Typ"],
|
||||
"Garant": row["Garant"],
|
||||
@@ -84,55 +89,48 @@ def read_excel(file_content):
|
||||
"row_data": row
|
||||
})
|
||||
except Exception as e:
|
||||
error_rows.append({
|
||||
"index": index,
|
||||
"row": row,
|
||||
"error": str(e)
|
||||
})
|
||||
error_rows.append({"index": index, "row": row, "error": str(e)})
|
||||
|
||||
valid_data = pd.DataFrame(valid_data)
|
||||
|
||||
if show_debug:
|
||||
print("<pre>")
|
||||
print("Cleaned data:")
|
||||
print("<pre>Cleaned data:\n")
|
||||
print(valid_data.head())
|
||||
print("</pre>")
|
||||
print("<pre>")
|
||||
print("Error rows:")
|
||||
for error_row in error_rows:
|
||||
print(f"Index: {error_row['index']}, Error: {error_row['error']}")
|
||||
print(error_row['row'])
|
||||
print("<pre>Error rows:\n")
|
||||
for er in error_rows:
|
||||
print(f"Index: {er['index']}, Error: {er['error']}")
|
||||
print(er['row'])
|
||||
print("</pre>")
|
||||
|
||||
# Detekce překryvů
|
||||
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:
|
||||
for _, r in sorted_group.iterrows():
|
||||
if previous_end_time and r['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"],
|
||||
"index": r["index"],
|
||||
"Datum": r["Datum"],
|
||||
"Zacatek": r["Zacatek"],
|
||||
"Konec": r["Konec"],
|
||||
"Program": r["Program"],
|
||||
"Typ": r["Typ"],
|
||||
"Garant": r["Garant"],
|
||||
"Poznamka": r["Poznamka"],
|
||||
"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 show_debug:
|
||||
print("<pre>")
|
||||
print("Overlap errors:")
|
||||
for error in overlap_errors:
|
||||
print(error)
|
||||
print("<pre>Overlap errors:\n")
|
||||
for e in overlap_errors:
|
||||
print(e)
|
||||
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)
|
||||
|
||||
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):
|
||||
if not cell_value:
|
||||
return 15
|
||||
|
||||
max_line_length = column_width * 1.2
|
||||
lines = cell_value.split('\n')
|
||||
lines = str(cell_value).split('\n')
|
||||
line_count = 0
|
||||
for line in lines:
|
||||
line_count += len(line) // max_line_length + 1
|
||||
|
||||
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):
|
||||
if data.empty:
|
||||
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'].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)
|
||||
detail_row_height = calculate_row_height(detail, ws.column_dimensions[get_column_letter(1)].width)
|
||||
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():
|
||||
print("<p>Chyba: Načtená data obsahují neplatné hodnoty času. Zkontrolujte vstupní soubor.</p>")
|
||||
if show_debug:
|
||||
print("<pre>")
|
||||
print("Start times:")
|
||||
print("<pre>Start times:\n")
|
||||
print(start_times)
|
||||
print("End times:")
|
||||
print("\nEnd times:\n")
|
||||
print(end_times)
|
||||
print("</pre>")
|
||||
return None
|
||||
@@ -202,17 +205,20 @@ def create_timetable(data, title, detail, program_descriptions, program_colors):
|
||||
min_time = min(start_times)
|
||||
max_time = max(end_times)
|
||||
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:
|
||||
print("<pre>")
|
||||
print("Start times:")
|
||||
print("<pre>Start times:\n")
|
||||
print(start_times)
|
||||
print("End times:")
|
||||
print("\nEnd times:\n")
|
||||
print(end_times)
|
||||
print("</pre>")
|
||||
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
|
||||
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
|
||||
end_index = list(time_slots).index(end_time) + col_offset + 1
|
||||
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:
|
||||
print("<pre>")
|
||||
print("Start time: {}".format(start_time))
|
||||
print("End time: {}".format(end_time))
|
||||
print("</pre>")
|
||||
print("<pre>Start time: {}\nEnd time: {}</pre>".format(start_time, end_time))
|
||||
continue
|
||||
|
||||
cell_value = f"{row['Program']}"
|
||||
@@ -274,14 +277,12 @@ def create_timetable(data, title, detail, program_descriptions, program_colors):
|
||||
except AttributeError as e:
|
||||
print(f"<p>Chyba: {str(e)}. Zkontrolujte vstupní data, která způsobují překrývající se časy bloků.</p>")
|
||||
if show_debug:
|
||||
print("<pre>")
|
||||
print(f"Overlapping block:\n{row}")
|
||||
print("</pre>")
|
||||
print("<pre>Overlapping block:\n{}</pre>".format(row))
|
||||
return None
|
||||
|
||||
cell.alignment = Alignment(wrap_text=True, horizontal="center", vertical="center")
|
||||
lines = cell_value.split("\n")
|
||||
for idx, line in enumerate(lines):
|
||||
lines = str(cell_value).split("\n")
|
||||
for idx, _ in enumerate(lines):
|
||||
if idx == 0:
|
||||
cell.font = Font(bold=True)
|
||||
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']):
|
||||
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
|
||||
|
||||
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_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):
|
||||
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
|
||||
|
||||
def calculate_column_width(text):
|
||||
max_length = max(len(line) for line in text.split('\n'))
|
||||
return max_length * 1.2
|
||||
|
||||
# ====== HTML flow ======
|
||||
if step == '1':
|
||||
print('''
|
||||
<!DOCTYPE html>
|
||||
<html lang="cs">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Vytvoření Scénáře - Krok 1</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background-color: #f0f4f8;
|
||||
color: #333;
|
||||
}
|
||||
.form-container {
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
color: #555;
|
||||
}
|
||||
.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-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 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>
|
||||
''')
|
||||
print('''<!DOCTYPE html>
|
||||
<html lang="cs">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Vytvoření Scénáře - Krok 1</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f0f4f8; color: #333; }
|
||||
.form-container { max-width: 600px; margin: auto; padding: 20px; background-color: #fff;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1); border-radius: 8px; }
|
||||
.form-group { margin-bottom: 15px; }
|
||||
.form-group label { display: block; font-weight: bold; margin-bottom: 5px; color: #555; }
|
||||
.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-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; }
|
||||
.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><a href="/templates/scenar_template.xlsx">STÁHNOUT ŠABLONU SCÉNÁŘE 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ý • <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:
|
||||
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>")
|
||||
else:
|
||||
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('''
|
||||
<!DOCTYPE html>
|
||||
<html lang="cs">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Vytvoření Scénáře - Krok 2</title>
|
||||
<style>
|
||||
body {{
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background-color: #f0f4f8;
|
||||
color: #333;
|
||||
}}
|
||||
.form-container {{
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
border-radius: 8px;
|
||||
}}
|
||||
.form-group {{
|
||||
margin-bottom: 15px;
|
||||
}}
|
||||
.form-group label {{
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
color: #555;
|
||||
}}
|
||||
.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-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))
|
||||
print('''<!DOCTYPE html>
|
||||
<html lang="cs">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Vytvoření Scénáře - Krok 2</title>
|
||||
<style>
|
||||
body {{ font-family: Arial, sans-serif; margin: 20px; background-color: #f0f4f8; color: #333; }}
|
||||
.form-container {{ max-width: 600px; margin: auto; padding: 20px; background-color: #fff;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1); border-radius: 8px; }}
|
||||
.form-group {{ margin-bottom: 15px; }}
|
||||
.form-group label {{ display: block; font-weight: bold; margin-bottom: 5px; color: #555; }}
|
||||
.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-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; }}
|
||||
.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>Vyplň tituly a barvy pro nalezené typy programu a odešli.</p>
|
||||
<form action="/cgi-bin/scenar.py" method="post">
|
||||
<input type="hidden" name="title" value="{title}">
|
||||
<input type="hidden" name="detail" value="{detail}">
|
||||
<input type="hidden" name="file_content_base64" value="{fcb64}">
|
||||
<input type="hidden" name="step" value="3">'''.format(
|
||||
title=html.escape(title or ""),
|
||||
detail=html.escape(detail or ""),
|
||||
fcb64=html.escape(file_content_base64))
|
||||
)
|
||||
|
||||
for i, typ in enumerate(program_types, start=1):
|
||||
print('''
|
||||
<div class="form-group program-type">
|
||||
<label for="type_code_{}">Typ programu {}:</label>
|
||||
<input type="text" id="type_code_{}" name="type_code_{}" value="{}" required>
|
||||
<input type="text" id="desc_{}" name="desc_{}" required>
|
||||
<input type="color" id="color_{}" name="color_{}" required>
|
||||
</div>
|
||||
'''.format(i, i, i, i, typ, i, i, i, i))
|
||||
<div class="form-group program-type">
|
||||
<label for="type_code_{i}">Typ programu {i}:</label>
|
||||
<input type="text" id="type_code_{i}" name="type_code_{i}" value="{typ}" required>
|
||||
<input type="text" id="desc_{i}" name="desc_{i}" required>
|
||||
<input type="color" id="color_{i}" name="color_{i}" value="{default_color}" required>
|
||||
</div>'''.format(i=i, typ=html.escape(typ), default_color=DEFAULT_COLOR))
|
||||
|
||||
print('''
|
||||
<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="submit" value="Vygenerovat scénář">
|
||||
</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>
|
||||
'''.format(' checked' if show_debug else ''))
|
||||
<div class="form-group">
|
||||
<label for="debug">Zobrazit debug informace:</label>
|
||||
<input type="checkbox" id="debug" name="debug"{checked}>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="submit" value="Vygenerovat scénář">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>© 2024 Martin Sukaný • <a href="mailto:martin@sukany.cz">martin@sukany.cz</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>'''.format(checked=' checked' if show_debug else ''))
|
||||
|
||||
elif step == '3' and title and detail:
|
||||
file_content_base64 = form.getvalue('file_content_base64')
|
||||
if file_content_base64:
|
||||
file_content = base64.b64decode(file_content_base64)
|
||||
|
||||
data, error_rows = read_excel(file_content)
|
||||
if data.empty:
|
||||
print("<p>Chyba: Načtená data jsou prázdná nebo obsahují neplatné hodnoty. Zkontrolujte vstupní soubor.</p>")
|
||||
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)
|
||||
if wb:
|
||||
os.makedirs(TMP_DIR, exist_ok=True)
|
||||
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)
|
||||
|
||||
print(f'''
|
||||
<div class="output-container">
|
||||
<h2>Výsledky zpracování</h2>
|
||||
<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>Detail akce:</strong> {detail}</p>
|
||||
<h3>Data ze souboru:</h3>
|
||||
{data.to_html(index=False)}
|
||||
<p><a href="/tmp/{filename}" download>Stáhnout scénář pro {title} ZDE</a></p>
|
||||
</div>
|
||||
''')
|
||||
|
||||
if error_rows:
|
||||
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>
|
||||
''')
|
||||
print('''<div class="output-container">
|
||||
<h2>Výsledky zpracování</h2>
|
||||
<p><a href="/tmp/{name}" download>Stáhnout scénář pro {title} ZDE</a></p>
|
||||
<p><strong>Název akce:</strong> {title}</p>
|
||||
<p><strong>Detail akce:</strong> {detail}</p>
|
||||
<h3>Data ze souboru:</h3>
|
||||
{table}
|
||||
<p><a href="/tmp/{name}" download>Stáhnout scénář pro {title} ZDE</a></p>
|
||||
</div>'''.format(
|
||||
name=html.escape(safe_name),
|
||||
title=html.escape(title or ""),
|
||||
detail=html.escape(detail or ""),
|
||||
table=data.to_html(index=False)))
|
||||
else:
|
||||
print("<p>Chyba: Soubor nebyl nalezen. Zkontrolujte vstupní data.</p>")
|
||||
else:
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user