"""Add dashboarddefaults.displayrole; make businessunitid nullable One "display" PC image resolves its role (dashboard / lobby / partskiosk) from its IP. Existing rows are dashboard mappings, so displayrole defaults to 'dashboard'. Lobby / kiosk roles need no business unit, so businessunitid becomes nullable. Idempotent guards on column presence / nullability. Revision ID: 7d28_dashboarddefault_displayrole Revises: 7d27_roles_color Create Date: 2026-07-21 """ from alembic import op import sqlalchemy as sa revision = '7d28_dashboarddefault_displayrole' down_revision = '7d27_roles_color' branch_labels = None depends_on = None def _cols(insp): return {c['name']: c for c in insp.get_columns('dashboarddefaults')} def upgrade(): bind = op.get_bind() insp = sa.inspect(bind) if 'dashboarddefaults' not in insp.get_table_names(): return cols = _cols(insp) if 'displayrole' not in cols: op.add_column('dashboarddefaults', sa.Column( 'displayrole', sa.String(length=20), nullable=False, server_default='dashboard')) if 'businessunitid' in cols and not cols['businessunitid']['nullable']: op.alter_column('dashboarddefaults', 'businessunitid', existing_type=sa.Integer(), nullable=True) def downgrade(): bind = op.get_bind() insp = sa.inspect(bind) if 'dashboarddefaults' not in insp.get_table_names(): return cols = _cols(insp) # Only restore NOT NULL if no rows would violate it. if 'businessunitid' in cols and cols['businessunitid']['nullable']: nulls = bind.execute(sa.text( 'SELECT COUNT(*) FROM dashboarddefaults ' 'WHERE businessunitid IS NULL')).scalar() if not nulls: op.alter_column('dashboarddefaults', 'businessunitid', existing_type=sa.Integer(), nullable=False) if 'displayrole' in cols: op.drop_column('dashboarddefaults', 'displayrole')