73 lines
2.7 KiB
Python
73 lines
2.7 KiB
Python
"""phase52 — per-user web portal design preference
|
|
|
|
Adds to `users`:
|
|
|
|
ui_theme VARCHAR(16) NOT NULL DEFAULT 'classic'
|
|
|
|
'classic' renders the original top-navbar shell (layouts/classic.html),
|
|
'modern' renders the sidebar shell (layouts/modern.html). Every existing
|
|
account starts on 'classic', so the portal looks and behaves exactly as before
|
|
until a user opts in from the account menu.
|
|
|
|
MULTI-TENANT NOTE — why this differs from ST
|
|
--------------------------------------------
|
|
ST shipped this as phase48 (default 'classic') and then phase50, which flipped
|
|
the column default to 'modern' AND ran
|
|
|
|
UPDATE users SET ui_theme = 'modern' WHERE ui_theme = 'classic'
|
|
|
|
overwriting every saved preference. That was a defensible call for a
|
|
single-tenant deployment deciding for its own staff after its own A/B test.
|
|
|
|
It is NOT portable to MT. Here the same statement would run against EVERY
|
|
tenant database, flipping the entire UI for tenants who never saw the test and
|
|
never asked. So MT ships the phase48 semantics only: the column defaults to
|
|
'classic' and there is no backfill of any kind.
|
|
|
|
The default for accounts that have never chosen is config DEFAULT_UI_THEME
|
|
(app/__init__.py::resolve_ui_theme), which reads the environment. A stored
|
|
users.ui_theme always wins over it. To put a tenant on the modern design by
|
|
default, set DEFAULT_UI_THEME=modern for that tenant's process — no migration,
|
|
no overwritten preferences, and individual users can still switch either way.
|
|
|
|
Uses an INFORMATION_SCHEMA existence check — safe to re-run.
|
|
"""
|
|
|
|
revision = 'phase52_user_ui_theme'
|
|
down_revision = 'phase51_external_inspector'
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
|
|
def _column_exists(conn, table, column):
|
|
return conn.execute(sa.text(
|
|
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS "
|
|
"WHERE TABLE_SCHEMA = DATABASE() "
|
|
"AND TABLE_NAME = :t AND COLUMN_NAME = :c"
|
|
), {"t": table, "c": column}).scalar() > 0
|
|
|
|
|
|
def upgrade():
|
|
bind = op.get_bind()
|
|
if not _column_exists(bind, 'users', 'ui_theme'):
|
|
op.execute(sa.text(
|
|
"ALTER TABLE users "
|
|
"ADD COLUMN ui_theme VARCHAR(16) NOT NULL DEFAULT 'classic'"
|
|
))
|
|
# Idempotent repair for any row holding a value outside the known set (for
|
|
# example a partially-applied earlier run). This only ever touches NULL or
|
|
# invalid values — it does NOT reset a user's deliberate 'modern' choice.
|
|
op.execute(sa.text(
|
|
"UPDATE users SET ui_theme = 'classic' "
|
|
"WHERE ui_theme IS NULL OR ui_theme NOT IN ('classic', 'modern')"
|
|
))
|
|
|
|
|
|
def downgrade():
|
|
bind = op.get_bind()
|
|
if _column_exists(bind, 'users', 'ui_theme'):
|
|
op.execute(sa.text("ALTER TABLE users DROP COLUMN ui_theme"))
|