"""Domain CRUD routes.""" from __future__ import annotations from flask import Blueprint, flash, redirect, render_template, request, url_for from physcom.models.domain import Domain, MetricBound from physcom_web.app import get_repo bp = Blueprint("domains", __name__, url_prefix="/domains") @bp.route("/") def domain_list(): repo = get_repo() domains = repo.list_domains() return render_template("domains/list.html", domains=domains) @bp.route("/new", methods=["GET", "POST"]) def domain_new(): repo = get_repo() if request.method == "POST": name = request.form["name"].strip() description = request.form.get("description", "").strip() if not name: flash("Name is required.", "error") return render_template("domains/form.html") domain = repo.add_domain(Domain(name=name, description=description)) flash(f"Domain '{name}' created.", "success") return redirect(url_for("domains.domain_detail", domain_id=domain.id)) return render_template("domains/form.html") @bp.route("/", methods=["GET", "POST"]) def domain_detail(domain_id: int): repo = get_repo() domain = repo.get_domain_by_id(domain_id) if not domain: flash("Domain not found.", "error") return redirect(url_for("domains.domain_list")) if request.method == "POST": name = request.form["name"].strip() description = request.form.get("description", "").strip() if not name: flash("Name is required.", "error") else: repo.update_domain(domain_id, name, description) flash(f"Domain '{name}' updated.", "success") domain = repo.get_domain_by_id(domain_id) return render_template("domains/detail.html", domain=domain) @bp.route("//delete", methods=["POST"]) def domain_delete(domain_id: int): repo = get_repo() domain = repo.get_domain_by_id(domain_id) if domain: repo.delete_domain(domain_id) flash(f"Domain '{domain.name}' deleted.", "success") return redirect(url_for("domains.domain_list")) # ── Metric bound CRUD (HTMX partials) ──────────────────────── @bp.route("//metrics/add", methods=["POST"]) def metric_add(domain_id: int): repo = get_repo() metric_name = request.form["metric_name"].strip() unit = request.form.get("unit", "").strip() try: weight = float(request.form.get("weight", "1.0")) norm_min = float(request.form.get("norm_min", "0.0")) norm_max = float(request.form.get("norm_max", "1.0")) except ValueError: flash("Weight, norm_min, and norm_max must be numbers.", "error") domain = repo.get_domain_by_id(domain_id) return render_template("domains/_metrics_table.html", domain=domain) if not metric_name: flash("Metric name is required.", "error") else: mb = MetricBound(metric_name=metric_name, weight=weight, norm_min=norm_min, norm_max=norm_max, unit=unit) repo.add_metric_bound(domain_id, mb) flash("Metric added.", "success") domain = repo.get_domain_by_id(domain_id) return render_template("domains/_metrics_table.html", domain=domain) @bp.route("//metrics//edit", methods=["POST"]) def metric_edit(domain_id: int, metric_id: int): repo = get_repo() try: weight = float(request.form.get("weight", "1.0")) norm_min = float(request.form.get("norm_min", "0.0")) norm_max = float(request.form.get("norm_max", "1.0")) except ValueError: flash("Weight, norm_min, and norm_max must be numbers.", "error") domain = repo.get_domain_by_id(domain_id) return render_template("domains/_metrics_table.html", domain=domain) unit = request.form.get("unit", "").strip() repo.update_metric_bound(domain_id, metric_id, weight, norm_min, norm_max, unit) flash("Metric updated.", "success") domain = repo.get_domain_by_id(domain_id) return render_template("domains/_metrics_table.html", domain=domain) @bp.route("//metrics//delete", methods=["POST"]) def metric_delete(domain_id: int, metric_id: int): repo = get_repo() repo.delete_metric_bound(domain_id, metric_id) flash("Metric removed.", "success") domain = repo.get_domain_by_id(domain_id) return render_template("domains/_metrics_table.html", domain=domain)