.
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
"""Add export_html field to RotaRun.
|
||||
|
||||
Generated by assistant.
|
||||
"""
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("rota", "0002_rotaschedule_options_rotaschedule_shifts"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="rotarun",
|
||||
name="export_html",
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@@ -180,6 +180,10 @@ class RotaRun(models.Model):
|
||||
result = models.JSONField(null=True, blank=True)
|
||||
log = models.TextField(blank=True)
|
||||
|
||||
# Optional HTML export produced by a run (stored inline for convenience).
|
||||
# May be large; keep nullable to avoid breaking existing runs.
|
||||
export_html = models.TextField(null=True, blank=True)
|
||||
|
||||
def mark_running(self):
|
||||
self.status = self.STATUS_RUNNING
|
||||
self.started_at = timezone.now()
|
||||
|
||||
@@ -7,7 +7,7 @@ from django.utils import timezone
|
||||
from .models import RotaRun, RotaSchedule
|
||||
|
||||
|
||||
def _run_rota_job(run_id: int, use_external_generator: bool = False):
|
||||
def _run_rota_job(run_id: int, use_external_generator: bool = False, generate_builder: bool = False, builder_solve: bool = False):
|
||||
"""Background job that executes the scheduler and updates RotaRun.
|
||||
|
||||
If `use_external_generator` is True, callers should implement a project
|
||||
@@ -47,6 +47,30 @@ def _run_rota_job(run_id: int, use_external_generator: bool = False):
|
||||
"generated_at": timezone.now().isoformat(),
|
||||
}
|
||||
|
||||
# Optionally build the RotaBuilder and attach generated HTML to the
|
||||
# result. This allows the web UI to present the generated HTML for a
|
||||
# run without requiring a separate on-demand build.
|
||||
if generate_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
|
||||
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)
|
||||
|
||||
run.mark_finished(result=result)
|
||||
|
||||
except Exception:
|
||||
@@ -59,7 +83,7 @@ def _run_rota_job(run_id: int, use_external_generator: bool = False):
|
||||
print(tb)
|
||||
|
||||
|
||||
def run_rota_async(rota_id: int, use_external_generator: bool = False) -> RotaRun:
|
||||
def run_rota_async(rota_id: int, use_external_generator: bool = False, generate_builder: bool = False, builder_solve: bool = False) -> RotaRun:
|
||||
"""Create a RotaRun and start background thread to run it.
|
||||
|
||||
Returns the created RotaRun instance.
|
||||
@@ -68,7 +92,11 @@ def run_rota_async(rota_id: int, use_external_generator: bool = False) -> RotaRu
|
||||
rota = RotaSchedule.objects.get(pk=rota_id)
|
||||
run = RotaRun.objects.create(rota=rota)
|
||||
|
||||
thread = threading.Thread(target=_run_rota_job, args=(run.id, use_external_generator), daemon=True)
|
||||
thread = threading.Thread(
|
||||
target=_run_rota_job,
|
||||
args=(run.id, use_external_generator, generate_builder, builder_solve),
|
||||
daemon=True,
|
||||
)
|
||||
thread.start()
|
||||
|
||||
return run
|
||||
|
||||
@@ -19,4 +19,5 @@ urlpatterns = [
|
||||
path("run/<int:run_id>/export/html/", views.rota_run_export_html, name="rota_run_export_html"),
|
||||
path("run/<int:run_id>/export/download/", views.rota_run_export_download, name="rota_run_export_download"),
|
||||
path("rota/<int:rota_id>/export/builder/", views.rota_export_builder, name="rota_export_builder"),
|
||||
path("rota/<int:rota_id>/runs/clear/", views.rota_runs_clear, name="rota_runs_clear"),
|
||||
]
|
||||
|
||||
@@ -8,6 +8,7 @@ from .forms import ShiftForm
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.template.loader import render_to_string
|
||||
from django.http import HttpResponse, HttpResponseBadRequest
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
|
||||
def index(request):
|
||||
@@ -19,7 +20,9 @@ def rota_detail(request, rota_id):
|
||||
rota = get_object_or_404(RotaSchedule, pk=rota_id)
|
||||
if request.method == "POST":
|
||||
# trigger a background run
|
||||
run = run_rota_async(rota.id)
|
||||
generate_builder = request.POST.get("generate_builder") == "1"
|
||||
builder_solve = request.POST.get("builder_solve") == "1"
|
||||
run = run_rota_async(rota.id, generate_builder=generate_builder, builder_solve=builder_solve)
|
||||
return redirect(reverse("rota:rota_run_detail", args=(run.id,)))
|
||||
|
||||
return render(request, "rota/rota_detail.html", {"rota": rota})
|
||||
@@ -253,6 +256,21 @@ def rota_export_builder(request, rota_id):
|
||||
return HttpResponse(html, content_type="text/html; charset=utf-8")
|
||||
|
||||
|
||||
@require_POST
|
||||
def rota_runs_clear(request, rota_id):
|
||||
"""Delete all RotaRun records for the given rota and redirect back.
|
||||
|
||||
This is a POST-only endpoint; the template uses an inline form with a
|
||||
JavaScript confirmation to avoid accidental removal.
|
||||
"""
|
||||
rota = get_object_or_404(RotaSchedule, pk=rota_id)
|
||||
# Delete all runs for this rota
|
||||
from .models import RotaRun
|
||||
|
||||
RotaRun.objects.filter(rota=rota).delete()
|
||||
return redirect("rota:rota_detail", rota_id=rota.id)
|
||||
|
||||
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def shift_add(request, rota_id):
|
||||
"""HTMX endpoint to render a shift form or accept submission to append a shift to the rota."""
|
||||
|
||||
Reference in New Issue
Block a user