Initial commit: Flask battery tracker app

- Flask + SQLAlchemy (MariaDB-compatible schema) battery tracking web app
- 40 pre-seeded batteries (Eneloop, BONAI, Energizer NiMH) across 5 devices
- Business rules: block retired assignment, brand-mix warnings, capacity checks
- Mobile-friendly Jinja2 templates with inline CSS
- waitress WSGI server via systemd user service (sbin/install-service.sh)
- SQLite → MariaDB migration script (migrate_to_mariadb.py)
- 26 passing acceptance tests (pytest + Flask test client)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-11 22:38:16 -05:00
commit 6ea3eae981
22 changed files with 1689 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
{% extends "base.html" %}
{% block title %}Assign {{ battery.label }} — Battery Tracker{% endblock %}
{% block content %}
<h1>Assign {{ battery.label }}</h1>
<p class="text-muted" style="margin-bottom:1rem;">Brand: {{ battery.brand }}</p>
<div class="card">
{% if devices %}
<form method="post" action="{{ url_for('battery_assign', battery_id=battery.id) }}">
<div class="form-group">
<label>Select Device</label>
{% for device in devices %}
{% set full = device.installed_count() >= device.battery_slots %}
{% set mix = device.installed_brands() and battery.brand not in device.installed_brands() %}
<div style="margin-bottom:0.75rem;padding:0.6rem 0.75rem;border:1px solid #e2e8f0;border-radius:4px;
{% if full %}opacity:0.5;{% endif %}background:#fff;">
<label style="display:flex;align-items:center;gap:0.6rem;font-weight:normal;cursor:{% if full %}not-allowed{% else %}pointer{% endif %};">
<input type="radio" name="device_id" value="{{ device.id }}"
{% if full %}disabled{% endif %}
style="cursor:{% if full %}not-allowed{% else %}pointer{% endif %};">
<span>
<strong>{{ device.name }}</strong>
<span class="text-muted">({{ device.installed_count() }}/{{ device.battery_slots }} slots used)</span>
{% if full %}
<span class="badge badge-retired">Full</span>
{% endif %}
</span>
</label>
{% if mix and not full %}
<p class="text-warning" style="margin-top:0.3rem;margin-left:1.6rem;">
⚠ Already has {{ device.installed_brands()|join(', ') }} — mixing brands not recommended.
</p>
{% endif %}
</div>
{% endfor %}
</div>
<div class="form-actions">
<button class="btn btn-primary" type="submit">Assign Battery</button>
<a class="btn btn-secondary" href="{{ url_for('battery_detail', battery_id=battery.id) }}">Cancel</a>
</div>
</form>
{% else %}
<p class="text-muted">No devices exist yet. <a href="{{ url_for('device_add') }}">Add a device first.</a></p>
{% endif %}
</div>
{% endblock %}