Tables
Tables — Technical Spec
app/im2.py, template app/templates/tables.html. Split from the Settings screen 2026-09-10 (Dave: "separate system settings and tables into two menu items and make them different security options").
Routes
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /tables | require_action(user, 'tables_edit') | Renders one card per LIST_SETTINGS key. |
| GET | /settings | require_action(user, 'settings_edit') | Non-list settings only. |
| POST | /api/settings | per key: list keys need tables_edit, all others need settings_edit | Shared save endpoint for both screens. Returns {ok:true, changed:N}. |
Both GETs call one builder, _settings_page(request, user, kind), so field metadata, pre-fill of unset lists and the updated_by/updated_at stamps stay identical.
Permissions
- New action keys in
ACTION_DEFS:settings_edit(defaultadmin) andtables_edit(defaultadmin,level1). Editable on/permslike any other action. ADMIN_LOCKED_ACTIONS = {"settings_edit", "tables_edit"}:action_roles()unionsadminback in for these keys, so no permission edit can lock every admin out of the control screens. That replaces the old hard-codedrole != 'admin'checks.can()is exposed as a Jinja global;base.htmlshows the System Settings and Tables nav links independently, both.pc-only.
Storage
Unchanged: app_settings(key pk, value, updated_by, updated_at), resolution table-over-code in settings(), audit row per change with source='settings'. LIST_SETTINGS = uoms(no column), item_groups→item_group, gen_locs→gen_loc, count_groups→count_group, ro_classes(no column), holidays(no column). Keys with a column and no saved row are pre-filled from list_values(conn, key, column).
Smoke test
/opt/im2/verify_routes.sh now covers /tables and /perms alongside /settings.