Three features: device dropdown filter, charge log history, unassign-all
- Device dropdowns (quick-assign, bulk install, assign page) now only show devices with free slots; full devices are excluded entirely - New ChargeLog model tracks charge dates with optional cycle increment; battery detail page gets a Charge History card with add/delete rows - Device list page gets per-device Unassign All button (with confirmation) via new POST /device/<id>/unassign-all route
This commit is contained in:
@@ -2,7 +2,7 @@ from flask import Flask, render_template, redirect, url_for, request, flash, abo
|
||||
from sqlalchemy import create_engine, func
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
|
||||
from models import Base, Battery, Device, CapacityTest
|
||||
from models import Base, Battery, Device, CapacityTest, ChargeLog
|
||||
|
||||
|
||||
def create_app(config_object="config"):
|
||||
@@ -39,8 +39,10 @@ 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]
|
||||
return render_template("dashboard.html", batteries=batteries,
|
||||
storage_locations=storage_locations, devices=devices)
|
||||
storage_locations=storage_locations, devices=devices,
|
||||
devices_with_slots=devices_with_slots)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Battery — add
|
||||
@@ -114,9 +116,14 @@ def create_app(config_object="config"):
|
||||
.filter_by(battery_id=battery_id)
|
||||
.order_by(CapacityTest.tested_date, CapacityTest.id)
|
||||
.all())
|
||||
charge_logs = (db.query(ChargeLog)
|
||||
.filter_by(battery_id=battery_id)
|
||||
.order_by(ChargeLog.charged_date.desc(), ChargeLog.id.desc())
|
||||
.all())
|
||||
return render_template("battery_detail.html", battery=battery,
|
||||
storage_locations=storage_locations,
|
||||
capacity_tests=capacity_tests)
|
||||
capacity_tests=capacity_tests,
|
||||
charge_logs=charge_logs)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Battery — edit notes
|
||||
@@ -199,6 +206,42 @@ def create_app(config_object="config"):
|
||||
flash("Test record deleted.", "success")
|
||||
return redirect(url_for("battery_detail", battery_id=battery_id))
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Battery — charge log history
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@app.route("/battery/<int:battery_id>/charge-log/add", methods=["POST"])
|
||||
def battery_charge_log_add(battery_id):
|
||||
battery = db.get(Battery, battery_id)
|
||||
if battery is None:
|
||||
abort(404)
|
||||
date_val = request.form.get("charged_date", "").strip()
|
||||
if not date_val:
|
||||
flash("Date is required.", "error")
|
||||
return redirect(url_for("battery_detail", battery_id=battery_id))
|
||||
increment = 1 if request.form.get("increment_cycles") else 0
|
||||
notes = request.form.get("notes", "").strip() or None
|
||||
if increment:
|
||||
battery.charge_cycles = (battery.charge_cycles or 0) + 1
|
||||
db.add(ChargeLog(battery_id=battery_id, charged_date=date_val,
|
||||
increment_cycles=increment, notes=notes))
|
||||
db.commit()
|
||||
flash("Charge log entry added.", "success")
|
||||
return redirect(url_for("battery_detail", battery_id=battery_id))
|
||||
|
||||
@app.route("/battery/<int:battery_id>/charge-log/<int:log_id>/delete", methods=["POST"])
|
||||
def battery_charge_log_delete(battery_id, log_id):
|
||||
battery = db.get(Battery, battery_id)
|
||||
log = db.get(ChargeLog, log_id)
|
||||
if battery is None or log is None or log.battery_id != battery_id:
|
||||
abort(404)
|
||||
if log.increment_cycles:
|
||||
battery.charge_cycles = max(0, (battery.charge_cycles or 0) - 1)
|
||||
db.delete(log)
|
||||
db.commit()
|
||||
flash("Charge log entry deleted.", "success")
|
||||
return redirect(url_for("battery_detail", battery_id=battery_id))
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Battery — assign
|
||||
# ------------------------------------------------------------------ #
|
||||
@@ -214,13 +257,14 @@ 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]
|
||||
|
||||
if request.method == "POST":
|
||||
device_id = request.form.get("device_id", type=int)
|
||||
device = db.get(Device, device_id)
|
||||
if device is None:
|
||||
flash("Device not found.", "error")
|
||||
return render_template("assign.html", battery=battery, devices=devices)
|
||||
return render_template("assign.html", battery=battery, devices=devices_with_slots)
|
||||
|
||||
if device.installed_count() >= device.battery_slots:
|
||||
flash(
|
||||
@@ -228,7 +272,7 @@ def create_app(config_object="config"):
|
||||
f"({device.battery_slots}/{device.battery_slots} slots used).",
|
||||
"error",
|
||||
)
|
||||
return render_template("assign.html", battery=battery, devices=devices)
|
||||
return render_template("assign.html", battery=battery, devices=devices_with_slots)
|
||||
|
||||
# Warn on brand mix (non-blocking)
|
||||
existing_brands = device.installed_brands()
|
||||
@@ -249,7 +293,7 @@ def create_app(config_object="config"):
|
||||
flash(f"{battery.label} assigned to {device.name}.", "success")
|
||||
return redirect(url_for("dashboard"))
|
||||
|
||||
return render_template("assign.html", battery=battery, devices=devices)
|
||||
return render_template("assign.html", battery=battery, devices=devices_with_slots)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Battery — unassign
|
||||
@@ -668,6 +712,28 @@ def create_app(config_object="config"):
|
||||
flash(f"Device '{name}' deleted. All batteries marked available.", "success")
|
||||
return redirect(url_for("device_list"))
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Devices — unassign all batteries
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@app.route("/device/<int:device_id>/unassign-all", methods=["POST"])
|
||||
def device_unassign_all(device_id):
|
||||
device = db.get(Device, device_id)
|
||||
if device is None:
|
||||
abort(404)
|
||||
count = 0
|
||||
for battery in device.batteries:
|
||||
if battery.status == "installed":
|
||||
battery.status = "available"
|
||||
battery.device_id = None
|
||||
count += 1
|
||||
db.commit()
|
||||
flash(
|
||||
f"Unassigned {count} batter{'y' if count == 1 else 'ies'} from {device.name}.",
|
||||
"success",
|
||||
)
|
||||
return redirect(url_for("device_list"))
|
||||
|
||||
return app
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user