Scope device uniqueness per parent, null type/location for sub-components, hide sub-components from list, flat battery summary for parent devices

This commit is contained in:
2026-06-09 19:26:31 -05:00
parent 2a54cd8297
commit 23eeeafff7
7 changed files with 340 additions and 42 deletions
+44 -17
View File
@@ -650,7 +650,7 @@ def create_app(config_object="config"):
@app.route("/device/")
def device_list():
devices = db.query(Device).order_by(Device.name).all()
devices = db.query(Device).filter(Device.parent_id == None).order_by(Device.name).all()
device_types = sorted({d.device_type for d in devices if d.device_type})
device_locations = sorted({d.location for d in devices if d.location})
device_battery_sizes = sorted({d.battery_size for d in devices if d.battery_size})
@@ -733,8 +733,11 @@ def create_app(config_object="config"):
form_device_type=request.form.get("device_type", ""),
prefill_parent=parent_device), 400
if db.query(Device).filter_by(name=name).first():
flash(f"A device named '{name}' already exists.", "error")
parent_key_val = parent_device.id if parent_device else -1
if db.query(Device).filter(
Device.parent_key == parent_key_val, Device.name == name
).first():
flash(f"A device named '{name}' already exists here.", "error")
return render_template("device_add.html",
device_types=device_types,
device_locations=device_locations,
@@ -744,10 +747,13 @@ def create_app(config_object="config"):
form_device_type=request.form.get("device_type", ""),
prefill_parent=parent_device), 400
is_sub = parent_device is not None
device = Device(name=name, battery_slots=slots, notes=notes,
device_type=device_type, battery_size=battery_size,
location=location,
parent_id=parent_device.id if parent_device else None)
device_type=None if is_sub else device_type,
battery_size=battery_size,
location=None if is_sub else location,
parent_id=parent_device.id if parent_device else None,
parent_key=parent_key_val)
db.add(device)
db.commit()
flash(f"Device '{name}' added.", "success")
@@ -758,9 +764,7 @@ def create_app(config_object="config"):
return render_template("device_add.html", device_types=device_types,
device_locations=device_locations,
device_battery_sizes=device_battery_sizes,
prefill_parent=prefill_parent,
form_device_type=prefill_parent.device_type if prefill_parent else None,
form_location=prefill_parent.location if prefill_parent else None)
prefill_parent=prefill_parent)
# ------------------------------------------------------------------ #
# Devices — detail
@@ -804,6 +808,12 @@ def create_app(config_object="config"):
changed = True
if changed:
db.commit()
flat_installed = []
if device.has_children():
for child in device.children:
for b in child.batteries:
if b.status == "installed":
flat_installed.append((b, child.name, child.id))
return render_template("device_detail.html", device=device, brands=brands,
available_batteries=available_batteries,
device_types=device_types,
@@ -812,7 +822,8 @@ def create_app(config_object="config"):
device_list_all=all_devices,
ha_enabled=ha_client.enabled,
ha_live_pct=ha_live_pct,
logbook_entries=device.logbook_entries)
logbook_entries=device.logbook_entries,
flat_installed=flat_installed)
# ------------------------------------------------------------------ #
# Devices — edit
@@ -840,13 +851,20 @@ def create_app(config_object="config"):
except ValueError:
flash("Battery slots must be a positive integer.", "error")
return redirect(url_for("device_detail", device_id=device_id))
existing = db.query(Device).filter_by(name=name).first()
if existing and existing.id != device_id:
flash(f"A device named '{name}' already exists.", "error")
# Determine new parent key for scoped uniqueness check
parent_id_raw = request.form.get("parent_id", "").strip()
new_parent_key_val = int(parent_id_raw) if (parent_id_raw and parent_id_raw.isdigit()) else -1
existing = db.query(Device).filter(
Device.parent_key == new_parent_key_val,
Device.name == name,
Device.id != device_id,
).first()
if existing:
flash(f"A device named '{name}' already exists here.", "error")
return redirect(url_for("device_detail", device_id=device_id))
# Validate parent_id change
parent_id_raw = request.form.get("parent_id", "").strip()
if parent_id_raw and parent_id_raw.isdigit():
new_parent = db.get(Device, int(parent_id_raw))
if not new_parent:
@@ -866,12 +884,19 @@ def create_app(config_object="config"):
)
return redirect(url_for("device_detail", device_id=device_id))
device.parent_id = new_parent.id
device.parent_key = new_parent.id
else:
device.parent_id = None
device.parent_key = -1
device.name = name
device.notes = notes
device.device_type = device_type
if device.is_subcomponent():
device.device_type = None
device.location = None
else:
device.device_type = device_type
device.location = request.form.get("location", "").strip() or None
if device.has_children():
device.battery_slots = 0
device.battery_size = None
@@ -880,7 +905,6 @@ def create_app(config_object="config"):
new_battery_size = request.form.get("battery_size", "").strip() or None
if new_battery_size is not None:
device.battery_size = new_battery_size
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")
@@ -1327,7 +1351,9 @@ def create_app(config_object="config"):
if not name:
devices_skipped += 1
continue
existing = db.query(Device).filter_by(name=name).first()
existing = db.query(Device).filter(
Device.parent_key == -1, Device.name == name
).first()
if existing:
if old_id is not None:
device_id_map[old_id] = existing.id
@@ -1341,6 +1367,7 @@ def create_app(config_object="config"):
location = d.get("location") or None,
ha_entity_id = d.get("ha_entity_id") or None,
notes = d.get("notes") or None,
parent_key = -1,
)
db.add(new_dev)
db.flush()