This commit is contained in:
Ross
2025-12-14 22:08:30 +00:00
parent 55df9da8cb
commit 792349b2ea
3 changed files with 97 additions and 22 deletions
+69 -19
View File
@@ -1,6 +1,7 @@
import threading
import traceback
from typing import Optional
import io
import contextlib
from django.utils import timezone
@@ -34,7 +35,27 @@ def _run_rota_job(run_id: int, use_external_generator: bool = False, generate_bu
try:
from project_rota_hook import generate_rota_for_schedule # type: ignore
result = generate_rota_for_schedule(rota)
# Capture any stdout/stderr produced by the external generator
out_buf = io.StringIO()
err_buf = io.StringIO()
with contextlib.redirect_stdout(out_buf), contextlib.redirect_stderr(err_buf):
result = generate_rota_for_schedule(rota)
# Attach captured output to the result for debugging
try:
result = result or {}
result["generator_stdout"] = out_buf.getvalue()
result["generator_stderr"] = err_buf.getvalue()
except Exception:
pass
# Also persist captured generator output into the run.log so
# it's visible from the run detail page when executed in the
# background thread.
try:
captured = "\n[GENERATOR STDOUT]\n" + (result.get("generator_stdout") or "") + "\n[GENERATOR STDERR]\n" + (result.get("generator_stderr") or "")
run.log = (run.log or "") + captured
run.save(update_fields=["log"])
except Exception:
pass
except Exception:
tb = traceback.format_exc()
run.mark_failed(message=tb)
@@ -51,25 +72,54 @@ def _run_rota_job(run_id: int, use_external_generator: bool = False, generate_bu
# result. This allows the web UI to present the generated HTML for a
# run without requiring a separate on-demand build.
if generate_builder:
# Defensive: if no workers are assigned to the rota, skip the
# builder step and return a clear message rather than raising an
# exception from deep inside the builder.
try:
rb = rota.to_rota_builder()
# build the model and optionally solve
rb.build_and_solve(solve=builder_solve)
html = rb.get_worker_timetable_html(include_html_tag=True)
# store under result so it's accessible from UI
result["builder_html"] = html
# Persist the HTML on the RotaRun record for easy retrieval
worker_count = rota.workers.count()
except Exception:
worker_count = 0
if worker_count == 0:
result["builder_error"] = "No workers assigned to rota; add workers before running the builder."
else:
try:
run.export_html = html
run.save(update_fields=["export_html"])
except Exception:
# best-effort: if saving fails, include a log entry
run.log = (run.log or "") + "\nFailed to persist export_html"
except Exception as exc:
# include failure information in run.log and result
tb = traceback.format_exc()
run.log = (run.log or "") + "\n" + tb
result["builder_error"] = str(exc)
# Capture stdout/stderr from the builder so debugging info
# is available in the RotaRun result and log.
out_buf = io.StringIO()
err_buf = io.StringIO()
with contextlib.redirect_stdout(out_buf), contextlib.redirect_stderr(err_buf):
rb = rota.to_rota_builder()
# build the model and optionally solve
rb.build_and_solve(solve=builder_solve)
html = rb.get_worker_timetable_html(include_html_tag=True)
# store under result so it's accessible from UI
result["builder_html"] = html
result["builder_stdout"] = out_buf.getvalue()
result["builder_stderr"] = err_buf.getvalue()
# Persist the HTML on the RotaRun record for easy retrieval
try:
run.export_html = html
run.save(update_fields=["export_html"])
except Exception:
# best-effort: if saving fails, include a log entry
run.log = (run.log or "") + "\nFailed to persist export_html"
# Append captured output to run.log for convenient viewing
try:
captured = "\n[BUILDER STDOUT]\n" + result.get("builder_stdout", "") + "\n[BUILDER STDERR]\n" + result.get("builder_stderr", "")
run.log = (run.log or "") + captured
# persist the appended log so it is visible to users
# when the run finishes
run.save(update_fields=["log"])
except Exception:
pass
except Exception as exc:
# include failure information in run.log and result
tb = traceback.format_exc()
run.log = (run.log or "") + "\n" + tb
result["builder_error"] = str(exc)
run.mark_finished(result=result)