Add Flask web UI, Docker Compose, core engine + tests
- physcom core: CLI, 5-pass pipeline, SQLite repo, 37 tests - physcom_web: Flask app with HTMX for entity/domain/pipeline/results CRUD - Docker Compose: web + cli services sharing a named volume for the DB - Clean up local settings to use wildcard permissions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
128
src/physcom_web/routes/entities.py
Normal file
128
src/physcom_web/routes/entities.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""Entity + dependency CRUD routes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
||||
|
||||
from physcom.models.entity import Dependency, Entity
|
||||
from physcom_web.app import get_repo
|
||||
|
||||
bp = Blueprint("entities", __name__, url_prefix="/entities")
|
||||
|
||||
|
||||
@bp.route("/")
|
||||
def entity_list():
|
||||
repo = get_repo()
|
||||
entities = repo.list_entities()
|
||||
# Group by dimension
|
||||
grouped: dict[str, list[Entity]] = {}
|
||||
for e in entities:
|
||||
grouped.setdefault(e.dimension, []).append(e)
|
||||
return render_template("entities/list.html", grouped=grouped)
|
||||
|
||||
|
||||
@bp.route("/new", methods=["GET", "POST"])
|
||||
def entity_new():
|
||||
repo = get_repo()
|
||||
if request.method == "POST":
|
||||
dimension = request.form["dimension"].strip()
|
||||
name = request.form["name"].strip()
|
||||
description = request.form.get("description", "").strip()
|
||||
if not dimension or not name:
|
||||
flash("Dimension and name are required.", "error")
|
||||
return render_template("entities/form.html", entity=None,
|
||||
dimensions=repo.list_dimensions())
|
||||
entity = Entity(name=name, dimension=dimension, description=description)
|
||||
repo.add_entity(entity)
|
||||
flash(f"Entity '{name}' added to '{dimension}'.", "success")
|
||||
return redirect(url_for("entities.entity_detail", entity_id=entity.id))
|
||||
return render_template("entities/form.html", entity=None,
|
||||
dimensions=repo.list_dimensions())
|
||||
|
||||
|
||||
@bp.route("/<int:entity_id>")
|
||||
def entity_detail(entity_id: int):
|
||||
repo = get_repo()
|
||||
entity = repo.get_entity(entity_id)
|
||||
if not entity:
|
||||
flash("Entity not found.", "error")
|
||||
return redirect(url_for("entities.entity_list"))
|
||||
return render_template("entities/detail.html", entity=entity)
|
||||
|
||||
|
||||
@bp.route("/<int:entity_id>/edit", methods=["GET", "POST"])
|
||||
def entity_edit(entity_id: int):
|
||||
repo = get_repo()
|
||||
entity = repo.get_entity(entity_id)
|
||||
if not entity:
|
||||
flash("Entity not found.", "error")
|
||||
return redirect(url_for("entities.entity_list"))
|
||||
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("entities/form.html", entity=entity,
|
||||
dimensions=repo.list_dimensions())
|
||||
repo.update_entity(entity_id, name, description)
|
||||
flash(f"Entity '{name}' updated.", "success")
|
||||
return redirect(url_for("entities.entity_detail", entity_id=entity_id))
|
||||
return render_template("entities/form.html", entity=entity,
|
||||
dimensions=repo.list_dimensions())
|
||||
|
||||
|
||||
@bp.route("/<int:entity_id>/delete", methods=["POST"])
|
||||
def entity_delete(entity_id: int):
|
||||
repo = get_repo()
|
||||
entity = repo.get_entity(entity_id)
|
||||
if entity:
|
||||
repo.delete_entity(entity_id)
|
||||
flash(f"Entity '{entity.name}' deleted.", "success")
|
||||
return redirect(url_for("entities.entity_list"))
|
||||
|
||||
|
||||
# ── Dependency CRUD (HTMX partials) ─────────────────────────
|
||||
|
||||
|
||||
@bp.route("/<int:entity_id>/deps/add", methods=["POST"])
|
||||
def dep_add(entity_id: int):
|
||||
repo = get_repo()
|
||||
dep = Dependency(
|
||||
category=request.form["category"].strip(),
|
||||
key=request.form["key"].strip(),
|
||||
value=request.form["value"].strip(),
|
||||
unit=request.form.get("unit", "").strip() or None,
|
||||
constraint_type=request.form.get("constraint_type", "requires").strip(),
|
||||
)
|
||||
if not dep.category or not dep.key or not dep.value:
|
||||
flash("Category, key, and value are required.", "error")
|
||||
else:
|
||||
repo.add_dependency(entity_id, dep)
|
||||
flash("Dependency added.", "success")
|
||||
entity = repo.get_entity(entity_id)
|
||||
return render_template("entities/_dep_table.html", entity=entity)
|
||||
|
||||
|
||||
@bp.route("/<int:entity_id>/deps/<int:dep_id>/edit", methods=["POST"])
|
||||
def dep_edit(entity_id: int, dep_id: int):
|
||||
repo = get_repo()
|
||||
dep = Dependency(
|
||||
category=request.form["category"].strip(),
|
||||
key=request.form["key"].strip(),
|
||||
value=request.form["value"].strip(),
|
||||
unit=request.form.get("unit", "").strip() or None,
|
||||
constraint_type=request.form.get("constraint_type", "requires").strip(),
|
||||
)
|
||||
repo.update_dependency(dep_id, dep)
|
||||
flash("Dependency updated.", "success")
|
||||
entity = repo.get_entity(entity_id)
|
||||
return render_template("entities/_dep_table.html", entity=entity)
|
||||
|
||||
|
||||
@bp.route("/<int:entity_id>/deps/<int:dep_id>/delete", methods=["POST"])
|
||||
def dep_delete(entity_id: int, dep_id: int):
|
||||
repo = get_repo()
|
||||
repo.delete_dependency(dep_id)
|
||||
flash("Dependency deleted.", "success")
|
||||
entity = repo.get_entity(entity_id)
|
||||
return render_template("entities/_dep_table.html", entity=entity)
|
||||
Reference in New Issue
Block a user