Enforce parent device hierarchy: slots/size unset, battery summary, sub-component toggle
This commit is contained in:
@@ -98,7 +98,7 @@ def create_app(config_object="config"):
|
|||||||
.distinct().order_by(Battery.storage_location).all()
|
.distinct().order_by(Battery.storage_location).all()
|
||||||
]
|
]
|
||||||
devices = db.query(Device).order_by(Device.name).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()
|
today = date.today()
|
||||||
one_year_ago = (today - timedelta(days=365)).isoformat()
|
one_year_ago = (today - timedelta(days=365)).isoformat()
|
||||||
total_charges = db.query(func.count(ChargeLog.id)).scalar() or 0
|
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))
|
return redirect(url_for("battery_detail", battery_id=battery_id))
|
||||||
|
|
||||||
devices = db.query(Device).order_by(Device.name).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()]
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
device_id = request.form.get("device_id", type=int)
|
device_id = request.form.get("device_id", type=int)
|
||||||
@@ -406,6 +406,10 @@ def create_app(config_object="config"):
|
|||||||
flash("Device not found.", "error")
|
flash("Device not found.", "error")
|
||||||
return render_template("assign.html", battery=battery, devices=devices_with_slots)
|
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:
|
if device.installed_count() >= device.battery_slots:
|
||||||
flash(
|
flash(
|
||||||
f"{device.name} is already full "
|
f"{device.name} is already full "
|
||||||
@@ -556,6 +560,10 @@ def create_app(config_object="config"):
|
|||||||
flash("Device not found.", "error")
|
flash("Device not found.", "error")
|
||||||
return redirect(url_for("dashboard"))
|
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]
|
already_here = [b for b in batteries if b.device_id == device.id]
|
||||||
retired_sel = [b for b in batteries if b.is_retired()]
|
retired_sel = [b for b in batteries if b.is_retired()]
|
||||||
to_process = [b for b in batteries
|
to_process = [b for b in batteries
|
||||||
@@ -692,6 +700,16 @@ def create_app(config_object="config"):
|
|||||||
device_types=device_types,
|
device_types=device_types,
|
||||||
device_locations=device_locations,
|
device_locations=device_locations,
|
||||||
device_battery_sizes=device_battery_sizes), 400
|
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:
|
if not name:
|
||||||
flash("Device name is required.", "error")
|
flash("Device name is required.", "error")
|
||||||
@@ -701,22 +719,12 @@ def create_app(config_object="config"):
|
|||||||
device_battery_sizes=device_battery_sizes,
|
device_battery_sizes=device_battery_sizes,
|
||||||
prefill_parent=parent_device), 400
|
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:
|
try:
|
||||||
slots = int(slots_raw)
|
slots = int(slots_raw)
|
||||||
if slots < 1:
|
if slots < 0:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
except 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",
|
return render_template("device_add.html",
|
||||||
device_types=device_types,
|
device_types=device_types,
|
||||||
device_locations=device_locations,
|
device_locations=device_locations,
|
||||||
@@ -750,7 +758,9 @@ def create_app(config_object="config"):
|
|||||||
return render_template("device_add.html", device_types=device_types,
|
return render_template("device_add.html", device_types=device_types,
|
||||||
device_locations=device_locations,
|
device_locations=device_locations,
|
||||||
device_battery_sizes=device_battery_sizes,
|
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
|
# Devices — detail
|
||||||
@@ -823,7 +833,9 @@ def create_app(config_object="config"):
|
|||||||
return redirect(url_for("device_detail", device_id=device_id))
|
return redirect(url_for("device_detail", device_id=device_id))
|
||||||
try:
|
try:
|
||||||
slots = int(slots_raw)
|
slots = int(slots_raw)
|
||||||
if slots < 1:
|
if slots < 0:
|
||||||
|
raise ValueError
|
||||||
|
if slots < 1 and not device.has_children():
|
||||||
raise ValueError
|
raise ValueError
|
||||||
except ValueError:
|
except ValueError:
|
||||||
flash("Battery slots must be a positive integer.", "error")
|
flash("Battery slots must be a positive integer.", "error")
|
||||||
@@ -846,17 +858,28 @@ def create_app(config_object="config"):
|
|||||||
if new_parent.is_subcomponent():
|
if new_parent.is_subcomponent():
|
||||||
flash("Cannot nest sub-components more than one level deep.", "error")
|
flash("Cannot nest sub-components more than one level deep.", "error")
|
||||||
return redirect(url_for("device_detail", device_id=device_id))
|
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
|
device.parent_id = new_parent.id
|
||||||
else:
|
else:
|
||||||
device.parent_id = None
|
device.parent_id = None
|
||||||
|
|
||||||
device.name = name
|
device.name = name
|
||||||
device.battery_slots = slots
|
|
||||||
device.notes = notes
|
device.notes = notes
|
||||||
device.device_type = device_type
|
device.device_type = device_type
|
||||||
new_battery_size = request.form.get("battery_size", "").strip() or None
|
if device.has_children():
|
||||||
if new_battery_size is not None:
|
device.battery_slots = 0
|
||||||
device.battery_size = new_battery_size
|
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.location = request.form.get("location", "").strip() or None
|
||||||
device.ha_entity_id = request.form.get("ha_entity_id", "").strip() or None
|
device.ha_entity_id = request.form.get("ha_entity_id", "").strip() or None
|
||||||
db.commit()
|
db.commit()
|
||||||
@@ -873,6 +896,10 @@ def create_app(config_object="config"):
|
|||||||
if device is None:
|
if device is None:
|
||||||
abort(404)
|
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[]")
|
brands = request.form.getlist("brand[]")
|
||||||
qtys_raw = request.form.getlist("qty[]")
|
qtys_raw = request.form.getlist("qty[]")
|
||||||
|
|
||||||
@@ -945,6 +972,9 @@ def create_app(config_object="config"):
|
|||||||
device = db.get(Device, device_id)
|
device = db.get(Device, device_id)
|
||||||
if device is None:
|
if device is None:
|
||||||
abort(404)
|
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_id = request.form.get("battery_id", type=int)
|
||||||
battery = db.get(Battery, battery_id)
|
battery = db.get(Battery, battery_id)
|
||||||
if battery is None or not battery.is_available():
|
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)
|
device = db.get(Device, device_id)
|
||||||
if device is None:
|
if device is None:
|
||||||
abort(404)
|
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")
|
battery_ids = request.form.getlist("battery_ids")
|
||||||
free = device.battery_slots - device.installed_count()
|
free = device.battery_slots - device.installed_count()
|
||||||
if not battery_ids:
|
if not battery_ids:
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class Device(Base):
|
|||||||
name = Column(String(100), nullable=False, unique=True)
|
name = Column(String(100), nullable=False, unique=True)
|
||||||
battery_slots = Column(Integer, nullable=False, default=1)
|
battery_slots = Column(Integer, nullable=False, default=1)
|
||||||
device_type = Column(String(50), nullable=True)
|
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)
|
location = Column(String(100), nullable=True)
|
||||||
notes = Column(Text, nullable=True)
|
notes = Column(Text, nullable=True)
|
||||||
ha_entity_id = Column(String(100), nullable=True) # e.g. "sensor.tv_remote_battery"
|
ha_entity_id = Column(String(100), nullable=True) # e.g. "sensor.tv_remote_battery"
|
||||||
|
|||||||
@@ -18,6 +18,16 @@
|
|||||||
placeholder="e.g. Game Controller" required>
|
placeholder="e.g. Game Controller" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if not prefill_parent %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label style="display:flex;align-items:center;gap:0.5rem;font-weight:normal;cursor:pointer;">
|
||||||
|
<input type="checkbox" id="has-subcomponents" onchange="toggleSubcomponents(this)">
|
||||||
|
This device has sub-components (batteries tracked per sub-component, not directly)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div id="battery-fields">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="battery_slots">Battery Slots <span class="text-danger">*</span></label>
|
<label for="battery_slots">Battery Slots <span class="text-danger">*</span></label>
|
||||||
<input type="number" id="battery_slots" name="battery_slots"
|
<input type="number" id="battery_slots" name="battery_slots"
|
||||||
@@ -44,6 +54,7 @@
|
|||||||
placeholder="e.g. CR123A"
|
placeholder="e.g. CR123A"
|
||||||
style="display:{% if _cur_size and _cur_size not in _preset_sizes %}''{% else %}none{% endif %};margin-top:0.4rem;">
|
style="display:{% if _cur_size and _cur_size not in _preset_sizes %}''{% else %}none{% endif %};margin-top:0.4rem;">
|
||||||
</div>
|
</div>
|
||||||
|
</div><!-- #battery-fields -->
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Type</label>
|
<label>Type</label>
|
||||||
@@ -68,14 +79,18 @@
|
|||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Location</label>
|
<label>Location</label>
|
||||||
|
{% set _cur_loc = form_location|default('') %}
|
||||||
<select id="location-select" onchange="metaSelectChanged(this,'location')">
|
<select id="location-select" onchange="metaSelectChanged(this,'location')">
|
||||||
<option value="">— none —</option>
|
<option value="">— none —</option>
|
||||||
{% for loc in device_locations|default([]) %}
|
{% for loc in device_locations|default([]) %}
|
||||||
<option value="{{ loc }}">{{ loc }}</option>
|
<option value="{{ loc }}" {% if _cur_loc == loc %}selected{% endif %}>{{ loc }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% if _cur_loc and _cur_loc not in device_locations|default([]) %}
|
||||||
|
<option value="{{ _cur_loc }}" selected>{{ _cur_loc }}</option>
|
||||||
|
{% endif %}
|
||||||
<option value="__new__">➕ New location…</option>
|
<option value="__new__">➕ New location…</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="text" id="location" name="location" value=""
|
<input type="text" id="location" name="location" value="{{ _cur_loc }}"
|
||||||
placeholder="e.g. Living Room, Bedroom"
|
placeholder="e.g. Living Room, Bedroom"
|
||||||
style="display:none;margin-top:0.4rem;">
|
style="display:none;margin-top:0.4rem;">
|
||||||
</div>
|
</div>
|
||||||
@@ -93,6 +108,26 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
function toggleSubcomponents(cb) {
|
||||||
|
var fields = document.getElementById('battery-fields');
|
||||||
|
var slots = document.getElementById('battery_slots');
|
||||||
|
var size = document.getElementById('battery_size');
|
||||||
|
if (cb.checked) {
|
||||||
|
fields.style.display = 'none';
|
||||||
|
slots.value = '0';
|
||||||
|
slots.removeAttribute('required');
|
||||||
|
slots.removeAttribute('min');
|
||||||
|
size.value = '';
|
||||||
|
size.removeAttribute('required');
|
||||||
|
} else {
|
||||||
|
fields.style.display = '';
|
||||||
|
slots.value = '1';
|
||||||
|
slots.setAttribute('required', '');
|
||||||
|
slots.setAttribute('min', '1');
|
||||||
|
size.setAttribute('required', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function metaSelectChanged(sel, inputId) {
|
function metaSelectChanged(sel, inputId) {
|
||||||
var input = document.getElementById(inputId);
|
var input = document.getElementById(inputId);
|
||||||
if (sel.value === '__new__') {
|
if (sel.value === '__new__') {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
<table style="width:auto;border:none;">
|
<table style="width:auto;border:none;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:0.3rem 1rem 0.3rem 0;font-weight:600;color:#64748b;border:none;">Slots</td>
|
<td style="padding:0.3rem 1rem 0.3rem 0;font-weight:600;color:#64748b;border:none;">Slots</td>
|
||||||
<td style="border:none;">{{ device.installed_count() }} / {{ device.battery_slots }} used</td>
|
<td style="border:none;">{% if device.has_children() %}{% set ns = namespace(inst=0, slots=0) %}{% for c in device.children %}{% set ns.inst = ns.inst + c.installed_count() %}{% set ns.slots = ns.slots + c.battery_slots %}{% endfor %}{{ ns.inst }} / {{ ns.slots }} (across sub-components){% else %}{{ device.installed_count() }} / {{ device.battery_slots }} used{% endif %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if device.device_type %}
|
{% if device.device_type %}
|
||||||
<tr>
|
<tr>
|
||||||
@@ -115,6 +115,39 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if device.has_children() %}
|
||||||
|
<div class="card">
|
||||||
|
<h2>Batteries in Sub-components</h2>
|
||||||
|
{% for child in device.children %}
|
||||||
|
{% set child_installed = child.batteries | selectattr('status', 'eq', 'installed') | list %}
|
||||||
|
<h3 style="margin:0.75rem 0 0.4rem;font-size:1rem;">
|
||||||
|
<a href="{{ url_for('device_detail', device_id=child.id) }}">{{ child.name }}</a>
|
||||||
|
<small class="text-muted" style="font-weight:normal;"> {{ child_installed|length }}/{{ child.battery_slots }}</small>
|
||||||
|
</h3>
|
||||||
|
{% if child_installed %}
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table class="responsive-table">
|
||||||
|
<thead><tr><th>Label</th><th>Brand</th>{% if ha_enabled %}<th>Bat %</th>{% endif %}<th>Last Charged</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for b in child_installed %}
|
||||||
|
<tr>
|
||||||
|
<td data-label="Label"><a href="{{ url_for('battery_detail', battery_id=b.id) }}">{{ b.label }}</a></td>
|
||||||
|
<td data-label="Brand">{{ b.brand }}</td>
|
||||||
|
{% if ha_enabled %}<td data-label="Bat %">{% if b.battery_percentage is not none %}{{ b.battery_percentage }}%{% else %}—{% endif %}</td>{% endif %}
|
||||||
|
<td data-label="Last Charged" class="text-muted">{{ b.charge_logs[-1].charged_date if b.charge_logs else '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-muted" style="margin:0 0 0.5rem;">No batteries installed.</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not device.has_children() %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Install Batteries</h2>
|
<h2>Install Batteries</h2>
|
||||||
{% set free_slots = device.battery_slots - device.installed_count() %}
|
{% set free_slots = device.battery_slots - device.installed_count() %}
|
||||||
@@ -191,7 +224,9 @@ function addInstallRow() {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not device.has_children() %}
|
||||||
<div class="card" id="installed">
|
<div class="card" id="installed">
|
||||||
<h2>Installed Batteries</h2>
|
<h2>Installed Batteries</h2>
|
||||||
{% set installed = device.batteries | selectattr('status', 'eq', 'installed') | list %}
|
{% set installed = device.batteries | selectattr('status', 'eq', 'installed') | list %}
|
||||||
@@ -326,6 +361,7 @@ function addInstallRow() {
|
|||||||
<p class="text-muted">No compatible batteries available.</p>
|
<p class="text-muted">No compatible batteries available.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Logbook</h2>
|
<h2>Logbook</h2>
|
||||||
@@ -365,10 +401,12 @@ function addInstallRow() {
|
|||||||
<label for="edit-name">Name</label>
|
<label for="edit-name">Name</label>
|
||||||
<input type="text" id="edit-name" name="name" value="{{ device.name }}" required>
|
<input type="text" id="edit-name" name="name" value="{{ device.name }}" required>
|
||||||
</div>
|
</div>
|
||||||
|
{% if not device.has_children() %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="edit-slots">Battery Slots</label>
|
<label for="edit-slots">Battery Slots</label>
|
||||||
<input type="number" id="edit-slots" name="battery_slots" value="{{ device.battery_slots }}" min="1" required>
|
<input type="number" id="edit-slots" name="battery_slots" value="{{ device.battery_slots }}" min="1" required>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Type</label>
|
<label>Type</label>
|
||||||
{% set _preset_types = ['Remote Control','Game Controller','Flashlight','Lock','Sensor','Toy','Clock','Smoke Detector'] %}
|
{% set _preset_types = ['Remote Control','Game Controller','Flashlight','Lock','Sensor','Toy','Clock','Smoke Detector'] %}
|
||||||
@@ -389,6 +427,7 @@ function addInstallRow() {
|
|||||||
placeholder="Enter device type"
|
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;">
|
style="display:{% if device.device_type and device.device_type not in _preset_types %}''{% else %}none{% endif %};margin-top:0.4rem;">
|
||||||
</div>
|
</div>
|
||||||
|
{% if not device.has_children() %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Battery Size</label>
|
<label>Battery Size</label>
|
||||||
{% set _preset_sizes = ['AA','AAA','C','D','9V','CR2032','CR2025','CR2016','18650','14500','16340','26650','LR44/AG13'] %}
|
{% set _preset_sizes = ['AA','AAA','C','D','9V','CR2032','CR2025','CR2016','18650','14500','16340','26650','LR44/AG13'] %}
|
||||||
@@ -412,6 +451,7 @@ function addInstallRow() {
|
|||||||
placeholder="e.g. CR123A"
|
placeholder="e.g. CR123A"
|
||||||
style="display:{% if device.battery_size and device.battery_size not in _preset_sizes %}''{% else %}none{% endif %};margin-top:0.4rem;">
|
style="display:{% if device.battery_size and device.battery_size not in _preset_sizes %}''{% else %}none{% endif %};margin-top:0.4rem;">
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Location</label>
|
<label>Location</label>
|
||||||
<select id="edit-location-select" onchange="editLocationSelectChanged(this)">
|
<select id="edit-location-select" onchange="editLocationSelectChanged(this)">
|
||||||
@@ -440,7 +480,7 @@ function addInstallRow() {
|
|||||||
<select id="edit-parent" name="parent_id">
|
<select id="edit-parent" name="parent_id">
|
||||||
<option value="">— none (top-level device) —</option>
|
<option value="">— none (top-level device) —</option>
|
||||||
{% for d in device_list_all|default([]) %}
|
{% for d in device_list_all|default([]) %}
|
||||||
{% if d.id != device.id and not d.is_subcomponent() %}
|
{% if d.id != device.id and not d.is_subcomponent() and d.battery_slots == 0 and d.battery_size is none %}
|
||||||
<option value="{{ d.id }}" {% if device.parent_id == d.id %}selected{% endif %}>{{ d.name }}</option>
|
<option value="{{ d.id }}" {% if device.parent_id == d.id %}selected{% endif %}>{{ d.name }}</option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -584,12 +584,6 @@ def test_add_install_delete_battery(client):
|
|||||||
# Device — battery_size
|
# Device — battery_size
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
|
|
||||||
def test_add_device_requires_battery_size(client):
|
|
||||||
"""POST without battery_size returns 400."""
|
|
||||||
resp = client.post("/device/add", data={"name": "No Size Device", "battery_slots": "2"})
|
|
||||||
assert resp.status_code == 400
|
|
||||||
assert b"Battery size is required" in resp.data
|
|
||||||
|
|
||||||
|
|
||||||
def test_add_device_with_battery_size(client):
|
def test_add_device_with_battery_size(client):
|
||||||
"""Device with battery_size shows it on detail page."""
|
"""Device with battery_size shows it on detail page."""
|
||||||
@@ -893,7 +887,7 @@ def test_device_charge_all_no_installed(seeded_client):
|
|||||||
|
|
||||||
def _setup_rc_car(client):
|
def _setup_rc_car(client):
|
||||||
"""Create RC Car Set with Remote and Car sub-components and 6 AA batteries."""
|
"""Create RC Car Set with Remote and Car sub-components and 6 AA batteries."""
|
||||||
client.post("/device/add", data={"name": "RC Car Set", "battery_slots": "6", "battery_size": "AA"})
|
client.post("/device/add", data={"name": "RC Car Set", "battery_slots": "0", "battery_size": ""})
|
||||||
# id=1 above; add sub-components with parent_id=1
|
# id=1 above; add sub-components with parent_id=1
|
||||||
client.post("/device/add", data={"name": "Remote", "battery_slots": "2",
|
client.post("/device/add", data={"name": "Remote", "battery_slots": "2",
|
||||||
"battery_size": "AA", "parent_id": "1"})
|
"battery_size": "AA", "parent_id": "1"})
|
||||||
@@ -967,7 +961,7 @@ def test_device_without_parent_unchanged(client):
|
|||||||
|
|
||||||
|
|
||||||
def test_subcomponent_ha_entity_id(client):
|
def test_subcomponent_ha_entity_id(client):
|
||||||
client.post("/device/add", data={"name": "Hub", "battery_slots": "1", "battery_size": "AA"})
|
client.post("/device/add", data={"name": "Hub", "battery_slots": "0", "battery_size": ""})
|
||||||
resp = client.post(
|
resp = client.post(
|
||||||
"/device/add",
|
"/device/add",
|
||||||
data={"name": "Sensor A", "battery_slots": "1", "battery_size": "AA",
|
data={"name": "Sensor A", "battery_slots": "1", "battery_size": "AA",
|
||||||
|
|||||||
Reference in New Issue
Block a user