diff --git a/app.py b/app.py index 8f35ae2..c946abc 100644 --- a/app.py +++ b/app.py @@ -98,7 +98,7 @@ def create_app(config_object="config"): .distinct().order_by(Battery.storage_location).all() ] devices = db.query(Device).order_by(Device.name).all() - devices_with_slots = [d for d in devices if d.installed_count() < d.battery_slots] + devices_with_slots = [d for d in devices if d.installed_count() < d.battery_slots and not d.has_children()] today = date.today() one_year_ago = (today - timedelta(days=365)).isoformat() total_charges = db.query(func.count(ChargeLog.id)).scalar() or 0 @@ -397,7 +397,7 @@ def create_app(config_object="config"): return redirect(url_for("battery_detail", battery_id=battery_id)) devices = db.query(Device).order_by(Device.name).all() - devices_with_slots = [d for d in devices if d.installed_count() < d.battery_slots] + devices_with_slots = [d for d in devices if d.installed_count() < d.battery_slots and not d.has_children()] if request.method == "POST": device_id = request.form.get("device_id", type=int) @@ -406,6 +406,10 @@ def create_app(config_object="config"): flash("Device not found.", "error") return render_template("assign.html", battery=battery, devices=devices_with_slots) + if device.has_children(): + flash(f"{device.name} has sub-components; assign batteries to its sub-components instead.", "error") + return render_template("assign.html", battery=battery, devices=devices_with_slots) + if device.installed_count() >= device.battery_slots: flash( f"{device.name} is already full " @@ -556,6 +560,10 @@ def create_app(config_object="config"): flash("Device not found.", "error") return redirect(url_for("dashboard")) + if device.has_children(): + flash(f"{device.name} has sub-components; install batteries into those instead.", "error") + return redirect(url_for("dashboard")) + already_here = [b for b in batteries if b.device_id == device.id] retired_sel = [b for b in batteries if b.is_retired()] to_process = [b for b in batteries @@ -692,6 +700,16 @@ def create_app(config_object="config"): device_types=device_types, device_locations=device_locations, device_battery_sizes=device_battery_sizes), 400 + if parent_device.battery_slots != 0 or parent_device.battery_size: + flash( + f"'{parent_device.name}' must have battery slots set to 0 " + f"and battery size cleared before sub-components can be added.", + "error", + ) + return render_template("device_add.html", + device_types=device_types, + device_locations=device_locations, + device_battery_sizes=device_battery_sizes), 400 if not name: flash("Device name is required.", "error") @@ -701,22 +719,12 @@ def create_app(config_object="config"): device_battery_sizes=device_battery_sizes, prefill_parent=parent_device), 400 - if not battery_size: - flash("Battery size is required.", "error") - return render_template("device_add.html", - device_types=device_types, - device_locations=device_locations, - device_battery_sizes=device_battery_sizes, - form_name=name, form_notes=notes or "", - form_device_type=request.form.get("device_type", ""), - prefill_parent=parent_device), 400 - try: slots = int(slots_raw) - if slots < 1: + if slots < 0: raise ValueError except ValueError: - flash("Battery slots must be a positive integer.", "error") + flash("Battery slots must be a non-negative integer.", "error") return render_template("device_add.html", device_types=device_types, device_locations=device_locations, @@ -750,7 +758,9 @@ 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) + 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) # ------------------------------------------------------------------ # # Devices — detail @@ -823,7 +833,9 @@ def create_app(config_object="config"): return redirect(url_for("device_detail", device_id=device_id)) try: slots = int(slots_raw) - if slots < 1: + if slots < 0: + raise ValueError + if slots < 1 and not device.has_children(): raise ValueError except ValueError: flash("Battery slots must be a positive integer.", "error") @@ -846,17 +858,28 @@ def create_app(config_object="config"): if new_parent.is_subcomponent(): flash("Cannot nest sub-components more than one level deep.", "error") return redirect(url_for("device_detail", device_id=device_id)) + if new_parent.battery_slots != 0 or new_parent.battery_size: + flash( + f"'{new_parent.name}' must have battery slots set to 0 " + f"and battery size cleared before sub-components can be added.", + "error", + ) + return redirect(url_for("device_detail", device_id=device_id)) device.parent_id = new_parent.id else: device.parent_id = None device.name = name - device.battery_slots = slots device.notes = notes device.device_type = device_type - new_battery_size = request.form.get("battery_size", "").strip() or None - if new_battery_size is not None: - device.battery_size = new_battery_size + if device.has_children(): + device.battery_slots = 0 + device.battery_size = None + else: + device.battery_slots = slots + 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() @@ -873,6 +896,10 @@ def create_app(config_object="config"): if device is None: abort(404) + if device.has_children(): + flash(f"{device.name} has sub-components; install batteries into those instead.", "error") + return redirect(url_for("device_detail", device_id=device_id)) + brands = request.form.getlist("brand[]") qtys_raw = request.form.getlist("qty[]") @@ -945,6 +972,9 @@ def create_app(config_object="config"): device = db.get(Device, device_id) if device is None: abort(404) + if device.has_children(): + flash(f"{device.name} has sub-components; install batteries into those instead.", "error") + return redirect(url_for("device_detail", device_id=device_id)) battery_id = request.form.get("battery_id", type=int) battery = db.get(Battery, battery_id) if battery is None or not battery.is_available(): @@ -1034,6 +1064,9 @@ def create_app(config_object="config"): device = db.get(Device, device_id) if device is None: abort(404) + if device.has_children(): + flash(f"{device.name} has sub-components; install batteries into those instead.", "error") + return redirect(url_for("device_detail", device_id=device_id)) battery_ids = request.form.getlist("battery_ids") free = device.battery_slots - device.installed_count() if not battery_ids: diff --git a/models.py b/models.py index b9ea531..dad379a 100644 --- a/models.py +++ b/models.py @@ -28,7 +28,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) - battery_size = Column(String(20), nullable=False) # AA, AAA, 9V, CR2032 … + battery_size = Column(String(20), nullable=True) # AA, AAA, 9V, CR2032 …; null for parent-only devices 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 ab2bbfa..b561574 100644 --- a/templates/device_add.html +++ b/templates/device_add.html @@ -18,6 +18,16 @@ placeholder="e.g. Game Controller" required> + {% if not prefill_parent %} +
No compatible batteries available.
{% endif %}