Add suff drink booking tool with PIN auth

Self-service drink tab at /suff/ for festival attendees. Users log in
with username + 3-digit PIN stored in a separate User.pin field, so
staff/admin accounts can keep their strong password for /admin/ and
also use the drink tool with the same username. PINs for staff users
must be set from the admin panel via a dedicated "PIN setzen" view to
prevent account takeover by name collision.

Time-gated to the festival window (Thu–Sun in Berlin tz) with phases
before/booking/readonly/closed; in non-production mode the tool is
always in booking phase for local testing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 12:00:56 +02:00
parent 1d35f0b9b9
commit 47d46e8e6f
16 changed files with 565 additions and 3 deletions
+48
View File
@@ -0,0 +1,48 @@
# Suff drink booking tool
Self-service drink tab for festival attendees. Lives at `/suff/`. Plain Django, no JS, no CSS yet.
## Auth
- `User.pin` field (hashed CharField) stores 3-digit PIN, separate from `password`. Lets staff keep a strong password for `/admin/` and use the same username on `/suff/` with just a PIN.
- `PinBackend` (`gaehsnitz/auth_backends.py`) authenticates by `username` + `pin` via `user.check_pin()`. Default `ModelBackend` stays first in `AUTHENTICATION_BACKENDS` so `/admin/` keeps requiring the strong password.
- Staff PINs cannot be self-set on `/suff/`. If a name matches an existing user with no PIN, the user lands on `suff/no_pin.html` explaining that an admin must set the PIN via the admin panel — otherwise anyone could claim a staff name and lock out the real owner.
- Admin convenience: User change page shows PIN status ("gesetzt"/"nicht gesetzt") + "PIN setzen" link → custom admin view `<id>/pin/` with a 3-digit form, calls `user.set_pin()`.
## Name flow
- Username = `slugify(input)` (e.g. "Flo Hä!" → "flo-ha"). Slug shown back so user can memorize it.
- POST name → check existence:
- not found → set new PIN → create user → login
- found, has PIN → enter PIN → login
- found, no PIN → `no_pin.html` (ask admin)
## Booking
- `/suff/me/` shows: greeting (slug), running paid total, full consumption history with timestamps, drink buttons.
- Each drink = `+1` POST form. Server creates `Consumption(amount=1, day=current_weekday, for_free=False, created_at=auto)`.
- No undo, no delete, no edit. No special bartender role.
- History sorted newest-first, `created_at` shown as `Do 18:42` etc.
## Time gating (Berlin tz)
- Phases: `before` / `booking` / `readonly` / `closed`.
- Booking allowed Thu 2026-06-11 00:00 → Sun 2026-06-14 23:59.
- Read-only until Sun 2026-06-21 23:59.
- After: every `/suff/` URL returns 404.
- Local dev: `settings.PRODUCTION=False` forces `booking` phase always.
## Files
- `gaehsnitz/auth_backends.py``PinBackend`
- `gaehsnitz/suff.py` — views + phase logic
- `gaehsnitz/suff_urls.py` — routes
- `gaehsnitz/admin.py``SetPinForm` + `set_pin_view`
- `gaehsnitz/templates/suff/{base,name,pin,no_pin,me}.html`
- `gaehsnitz/templates/admin/gaehsnitz/user/set_pin.html`
- `gaehsnitz/migrations/0003_consumption_created_at_user_pin.py`
- Edits: `gaehsnitzproject/settings.py`, `gaehsnitzproject/urls.py`, `gaehsnitz/models.py`
## Next step
Mobile-first styling. Currently zero CSS. Big tap targets for drink buttons, sticky/large running total, dark-mode friendly for outdoor evening use. Visual feedback after booking, day-grouped history, friendlier empty state, bigger PIN input. Decide whether `/suff/` integrates into the GOA site header or stays a separate microsite.