From b90ab7b2b68000d62f40fed9d781061b353ce6e8 Mon Sep 17 00:00:00 2001 From: Darek Date: Sat, 18 Apr 2026 15:36:01 -0500 Subject: [PATCH] Add location field to devices with dropdown selector --- app.py | 16 +++++++++++++--- models.py | 1 + templates/device_add.html | 14 ++++++++++++++ templates/device_detail.html | 32 ++++++++++++++++++++++++++++++++ tests/test_acceptance.py | 19 +++++++++++++++++++ 5 files changed, 79 insertions(+), 3 deletions(-) diff --git a/app.py b/app.py index 8dd0654..2ca8e91 100644 --- a/app.py +++ b/app.py @@ -643,17 +643,20 @@ def create_app(config_object="config"): def device_add(): all_devices = db.query(Device).all() device_types = sorted({d.device_type for d in all_devices if d.device_type}) + device_locations = sorted({d.location for d in all_devices if d.location}) if request.method == "POST": name = request.form.get("name", "").strip() slots_raw = request.form.get("battery_slots", "1").strip() notes = request.form.get("notes", "").strip() or None device_type = request.form.get("device_type", "").strip() or None + location = request.form.get("location", "").strip() or None if not name: flash("Device name is required.", "error") return render_template("device_add.html", - device_types=device_types), 400 + device_types=device_types, + device_locations=device_locations), 400 try: slots = int(slots_raw) @@ -663,6 +666,7 @@ def create_app(config_object="config"): flash("Battery slots must be a positive integer.", "error") return render_template("device_add.html", device_types=device_types, + device_locations=device_locations, form_name=name, form_notes=notes or "", form_device_type=request.form.get("device_type", "")), 400 @@ -670,17 +674,20 @@ def create_app(config_object="config"): flash(f"A device named '{name}' already exists.", "error") return render_template("device_add.html", device_types=device_types, + device_locations=device_locations, form_name=name, form_slots=slots, form_notes=notes or "", form_device_type=request.form.get("device_type", "")), 400 - device = Device(name=name, battery_slots=slots, notes=notes, device_type=device_type) + device = Device(name=name, battery_slots=slots, notes=notes, + device_type=device_type, location=location) db.add(device) db.commit() flash(f"Device '{name}' added.", "success") return redirect(url_for("device_list")) - return render_template("device_add.html", device_types=device_types) + return render_template("device_add.html", device_types=device_types, + device_locations=device_locations) # ------------------------------------------------------------------ # # Devices — detail @@ -696,6 +703,7 @@ def create_app(config_object="config"): .filter_by(status="available") .order_by(Battery.label).all()) device_types = sorted({d.device_type for d in db.query(Device).all() if d.device_type}) + device_locations = sorted({d.location for d in db.query(Device).all() if d.location}) ha_live_pct = None if ha_client.enabled and device.ha_entity_id: ha_live_pct = ha_client.get_state(device.ha_entity_id, timeout=1) @@ -716,6 +724,7 @@ def create_app(config_object="config"): return render_template("device_detail.html", device=device, brands=brands, available_batteries=available_batteries, device_types=device_types, + device_locations=device_locations, ha_enabled=ha_client.enabled, ha_live_pct=ha_live_pct) @@ -752,6 +761,7 @@ def create_app(config_object="config"): device.battery_slots = slots device.notes = notes device.device_type = device_type + device.location = request.form.get("location", "").strip() or None device.ha_entity_id = request.form.get("ha_entity_id", "").strip() or None db.commit() flash("Device updated.", "success") diff --git a/models.py b/models.py index 4cd8cb6..e7de76f 100644 --- a/models.py +++ b/models.py @@ -13,6 +13,7 @@ class Device(Base): name = Column(String(100), nullable=False, unique=True) battery_slots = Column(Integer, nullable=False, default=1) device_type = Column(String(50), nullable=True) + location = Column(String(100), nullable=True) notes = Column(Text, nullable=True) ha_entity_id = Column(String(100), nullable=True) # e.g. "sensor.tv_remote_battery" diff --git a/templates/device_add.html b/templates/device_add.html index d0e51bf..eafb04b 100644 --- a/templates/device_add.html +++ b/templates/device_add.html @@ -39,6 +39,20 @@ style="display:{% if _cur_type and _cur_type not in _preset_types %}''{% else %}none{% endif %};margin-top:0.4rem;"> +
+ + + +
+
diff --git a/templates/device_detail.html b/templates/device_detail.html index 5be7dfa..ed89f55 100644 --- a/templates/device_detail.html +++ b/templates/device_detail.html @@ -45,6 +45,12 @@ ⚠ Mixed brands installed {% endif %} + {% if device.location %} + + Location + {{ device.location }} + + {% endif %} {% if device.notes %} Notes @@ -109,6 +115,14 @@ function editTypeSelectChanged(sel) { input.style.display = 'none'; input.value = sel.value; } } +function editLocationSelectChanged(sel) { + var input = document.getElementById('edit-location'); + if (sel.value === '__new__') { + input.style.display = ''; input.value = ''; input.focus(); + } else { + input.style.display = 'none'; input.value = sel.value; + } +} function brandSelectChanged(sel) { var input = sel.nextElementSibling; if (sel.value === '__new__') { @@ -224,6 +238,24 @@ function addInstallRow() { placeholder="Enter device type" style="display:{% if device.device_type and device.device_type not in _preset_types %}''{% else %}none{% endif %};margin-top:0.4rem;">
+
+ + + +
+
diff --git a/tests/test_acceptance.py b/tests/test_acceptance.py index b21c9ba..bc13eef 100644 --- a/tests/test_acceptance.py +++ b/tests/test_acceptance.py @@ -498,6 +498,25 @@ def test_edit_device_type(client): assert b"Flashlight" in resp.data +def test_add_device_with_location(client): + resp = client.post("/device/add", + data={"name": "Bedroom Remote", "battery_slots": "2", "location": "Bedroom"}, + follow_redirects=True) + assert resp.status_code == 200 + assert b"Bedroom Remote" in resp.data + resp = client.get("/device/1") + assert b"Bedroom" in resp.data + + +def test_edit_device_location(client): + client.post("/device/add", data={"name": "Living Room Clock", "battery_slots": "1"}) + resp = client.post("/device/1/edit", + data={"name": "Living Room Clock", "battery_slots": "1", "location": "Living Room"}, + follow_redirects=True) + assert resp.status_code == 200 + assert b"Living Room" in resp.data + + def test_add_capacity_test(client): client.post("/battery/add", data={"brand": "Eneloop", "count": "1"}) resp = client.post("/battery/1/capacity-test/add",