Add README and update CLAUDE.md with current model and rules
This commit is contained in:
@@ -40,15 +40,28 @@ journalctl --user -u battery-tracker -f
|
|||||||
`config.py` reads `DATABASE_URL` from the environment, falling back to `sqlite:///batteries.db`. Swapping to MariaDB is entirely a matter of setting that env var — no code changes needed. All column types are restricted to `Integer`, `String`, `Text`, `Boolean`, `DateTime` for MariaDB compatibility. Status is stored as `String(20)` (not `Enum`) to avoid DDL differences between SQLite and MariaDB.
|
`config.py` reads `DATABASE_URL` from the environment, falling back to `sqlite:///batteries.db`. Swapping to MariaDB is entirely a matter of setting that env var — no code changes needed. All column types are restricted to `Integer`, `String`, `Text`, `Boolean`, `DateTime` for MariaDB compatibility. Status is stored as `String(20)` (not `Enum`) to avoid DDL differences between SQLite and MariaDB.
|
||||||
|
|
||||||
### Models
|
### Models
|
||||||
- `Battery`: label (unique), brand, status (`available`/`installed`/`retired`), device_id (FK nullable, `ondelete=SET NULL`), notes
|
- `Battery`: label (unique), brand, status (`available`/`installed`/`retired`), device_id (FK nullable, `ondelete=SET NULL`), notes, size, chemistry, capacity_mah, tested_capacity_mah, tested_date, charge_cycles, purchase_date, storage_location
|
||||||
- `Device`: name (unique), battery_slots, notes
|
- `Device`: name (unique), battery_slots, device_type, notes
|
||||||
- Helper methods on both models (`is_available()`, `installed_count()`, `has_mixed_brands()`) are used directly in templates and route logic.
|
- Helper methods: `Battery.is_available/installed/retired()`, `Device.installed_count()`, `Device.installed_brands()`, `Device.has_mixed_brands()`
|
||||||
|
|
||||||
### Business rules (enforced in routes, not DB constraints)
|
### Business rules (enforced in routes, not DB constraints)
|
||||||
- Assigning a retired battery → hard block with flash error
|
- Assigning a retired battery → hard block with flash error
|
||||||
- Assigning to a full device → hard block with flash error
|
- Assigning to a full device → hard block with flash error
|
||||||
- Mixed brands on same device → flash warning, assignment still proceeds
|
- Mixed brands on same device → flash warning, assignment still proceeds
|
||||||
- Deleting a battery → requires GET confirmation page first, then POST
|
- Deleting a battery → requires GET confirmation page first, then POST
|
||||||
|
- Bulk install into device: capacity check before write; batteries already in target device are skipped; retired batteries are skipped
|
||||||
|
- Unretire: sets status back to `available`
|
||||||
|
- Unassign with `next` form field → redirects to that URL (must start with `/`); falls back to dashboard
|
||||||
|
|
||||||
|
### Adding new columns to existing DB
|
||||||
|
`create_all()` won't add columns to existing tables. Run via Python:
|
||||||
|
```python
|
||||||
|
import sqlite3
|
||||||
|
conn = sqlite3.connect('batteries.db')
|
||||||
|
conn.execute('ALTER TABLE <table> ADD COLUMN <col> <type>')
|
||||||
|
conn.commit(); conn.close()
|
||||||
|
```
|
||||||
|
Always snapshot the DB first: `cp batteries.db batteries.db.$(date +%Y-%m-%d).snapshot`
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
Tests use a temporary file-based SQLite DB (via `tempfile.mkstemp`) created fresh per test — avoids SQLite in-memory per-connection isolation issues. The `seeded_client` fixture in `conftest.py` pre-populates via HTTP POST calls (not direct DB access), so tests exercise the full stack. Battery IDs in tests are positional (id=1 is always the first battery POSTed by the fixture).
|
Tests use a temporary file-based SQLite DB (via `tempfile.mkstemp`) created fresh per test — avoids SQLite in-memory per-connection isolation issues. The `seeded_client` fixture in `conftest.py` pre-populates via HTTP POST calls (not direct DB access), so tests exercise the full stack. Battery IDs in tests are positional (id=1 is always the first battery POSTed by the fixture).
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
# Battery Tracker
|
||||||
|
|
||||||
|
A self-hosted web app for tracking rechargeable batteries — where they are, what condition they're in, and which devices they're installed in.
|
||||||
|
|
||||||
|
Built with Flask + SQLAlchemy. Runs on a Raspberry Pi or any Linux box with a systemd user service.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Batteries
|
||||||
|
- Add batteries in bulk (auto-generated labels like "Eneloop 001", "Eneloop 002")
|
||||||
|
- Track status: **Available**, **Installed**, **Retired**
|
||||||
|
- Store metadata: size (AA/AAA/18650/…), chemistry (NiMH/Li-ion/…), capacity, tested capacity, charge cycles, purchase date, storage location, notes
|
||||||
|
- Visual health indicator when tested capacity is recorded (good/warn/bad)
|
||||||
|
- Retire and unretire batteries
|
||||||
|
|
||||||
|
### Devices
|
||||||
|
- Add devices with a name, battery slot count, type (Remote Control, Flashlight, Sensor, …), and notes
|
||||||
|
- Install batteries into devices — by brand/quantity or by picking a specific battery
|
||||||
|
- Bulk install multiple batteries at once from the dashboard
|
||||||
|
- Mixed-brand warning when batteries from different brands share a device
|
||||||
|
- Edit device details in-place
|
||||||
|
|
||||||
|
### Dashboard
|
||||||
|
- Overview stats (total / available / installed / retired)
|
||||||
|
- Full battery table with client-side filtering by status, brand, size, storage location, and free-text search
|
||||||
|
- Column picker (Chemistry, Capacity, Storage, Purchase Date, Cycles) stored in localStorage
|
||||||
|
- Quick-assign available batteries to a device without leaving the page
|
||||||
|
- Bulk actions: Unassign, Retire, Delete, Set Field (storage location or brand), Install in Device
|
||||||
|
|
||||||
|
### UI
|
||||||
|
- Mobile-friendly: card-style table rows on small screens, full-width filter controls, stacked form buttons
|
||||||
|
- Dark mode — follows the OS system preference automatically (`prefers-color-scheme`)
|
||||||
|
- No JavaScript framework — vanilla JS only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
| Layer | Technology |
|
||||||
|
|---|---|
|
||||||
|
| Web framework | Flask 3.x |
|
||||||
|
| ORM | SQLAlchemy 2.x (raw session, no Flask-SQLAlchemy) |
|
||||||
|
| Database | SQLite (dev) / MariaDB (prod) |
|
||||||
|
| WSGI server | Waitress |
|
||||||
|
| Process manager | systemd user service |
|
||||||
|
| Tests | pytest (45 acceptance tests) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Python 3.10+
|
||||||
|
- `python3-venv`
|
||||||
|
|
||||||
|
### Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repo-url>
|
||||||
|
cd battery-tracker-app
|
||||||
|
|
||||||
|
python3 -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run (development)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source .venv/bin/activate
|
||||||
|
flask run
|
||||||
|
# → http://127.0.0.1:5000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run (production — systemd user service)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash sbin/install-service.sh # prompts for host/port, installs and enables the service
|
||||||
|
systemctl --user start battery-tracker
|
||||||
|
systemctl --user status battery-tracker
|
||||||
|
journalctl --user -u battery-tracker -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
SQLite is used by default (`batteries.db` in the project root). To switch to MariaDB, set the environment variable before starting:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export DATABASE_URL="mysql+pymysql://user:pass@host/dbname"
|
||||||
|
```
|
||||||
|
|
||||||
|
See `MIGRATION.md` for migrating an existing SQLite database to MariaDB.
|
||||||
|
|
||||||
|
### Seed data (optional)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source .venv/bin/activate
|
||||||
|
python seed.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source .venv/bin/activate
|
||||||
|
pytest tests/ -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Tests use a fresh temporary SQLite database per test and exercise the full HTTP stack.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
app.py Flask app factory + all routes
|
||||||
|
models.py SQLAlchemy models (Battery, Device)
|
||||||
|
config.py Database URL config
|
||||||
|
seed.py Optional seed data script
|
||||||
|
migrate_to_mariadb.py SQLite → MariaDB migration script
|
||||||
|
MIGRATION.md MariaDB migration procedure
|
||||||
|
requirements.txt
|
||||||
|
sbin/
|
||||||
|
install-service.sh systemd user service installer
|
||||||
|
templates/
|
||||||
|
base.html Layout, CSS variables, dark mode, nav
|
||||||
|
dashboard.html Battery list with filters and bulk actions
|
||||||
|
battery_add.html Add batteries form
|
||||||
|
battery_detail.html Battery detail + edit
|
||||||
|
battery_delete.html Delete confirmation
|
||||||
|
assign.html Assign battery to device
|
||||||
|
device_list.html Device list with type filter
|
||||||
|
device_add.html Add device form
|
||||||
|
device_detail.html Device detail + install + edit
|
||||||
|
tests/
|
||||||
|
conftest.py pytest fixtures
|
||||||
|
test_acceptance.py 45 acceptance tests
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user