Skip to content

Developer overview

LifeFile is a Django 6 app + a small static docs site. It's deliberately monolithic — one repo, one runtime, SQLite on a single Fly.io VM. The complexity is in the services (classifier, email scan, schedule eval, DVLA lookup), not in the deployment.

Tech stack

Layer Choice Notes
Backend Django 6 + Python 3.14 One repo, ~12 apps under apps/
Templates Django templates + HTMX + Alpine.js Server-rendered; HTMX for partial swaps
Styling Tailwind via CDN (Play) Build pipeline deferred
Database SQLite on Fly volume Postgres migration planned
Storage Local disk on the volume R2 + envelope encryption planned
Classifier Claude Sonnet via Anthropic SDK Stub mode for tests/CI; live mode in prod
Email scan Gmail API (read-only) OAuth via auth_google (sign-in) + emailscan (Gmail)
Vehicle lookup DVLA VES + DVSA MOT History Mock mode by default; live keys via Fly secrets
Tests pytest + pytest-django + model-bakery 376+ tests, runs in ~75s
CI GitHub Actions Pytest gate on PRs; deploy to Fly on main
Docs Zensical (this site) Cloudflare Pages
Pre-commit prek docs-drift check

Where to start

Conventions

A few decisions made repeatedly:

  • Services-first. Views are thin shells that parse the request, call a service function, render a template. Most logic lives in apps/<app>/services.py.
  • Household-scoped managers. Every domain model has a HouseholdScopedManager as objects and an AllTenantsManager as all_tenants. Default reads scope automatically.
  • Stub modes for external APIs. Classifier, vehicle lookup, and (planned) email-in all support a mock / stub mode toggled by an env var so dev and CI run without keys.
  • Static template checks. We have CI tests for multi-line {# #} Django comments (which silently leak as text) and cross-file id class drift (which OOB swaps used to silently overwrite).
  • Atomic commits, conventional messages. PR titles are short; bodies explain the why. CHANGELOG.md gets a dated entry per merged PR.
  • Tests before code. Most slices are TDD: write a failing test, watch it go red, write just enough code to go green, refactor.