fix: make migration 075 idempotent for existing link_templates

Skip creating link_templates when it already exists and only create missing indexes, so partially-migrated customer databases can continue upgrading.
This commit is contained in:
Dries Peeters
2026-01-02 17:12:18 +01:00
parent dd4a5e2f21
commit dcc4f87769

View File

@@ -27,6 +27,22 @@ def _has_column(inspector, table_name: str, column_name: str) -> bool:
return False
def _has_table(inspector, table_name: str) -> bool:
"""Check if a table exists"""
try:
return table_name in inspector.get_table_names()
except Exception:
return False
def _has_index(inspector, table_name: str, index_name: str) -> bool:
"""Check if an index exists on a table"""
try:
return any((idx.get("name") or "") == index_name for idx in inspector.get_indexes(table_name))
except Exception:
return False
def upgrade():
"""Add custom_fields to clients and create link_templates table"""
bind = op.get_bind()
@@ -35,30 +51,44 @@ def upgrade():
bool_true_default = '1' if dialect_name == 'sqlite' else ('true' if dialect_name == 'postgresql' else '1')
# Add custom_fields column to clients table if it doesn't exist
if 'clients' in inspector.get_table_names():
if _has_table(inspector, 'clients'):
if not _has_column(inspector, 'clients', 'custom_fields'):
# Use portable JSON type for cross-db compatibility (SQLite + PostgreSQL).
op.add_column('clients', sa.Column('custom_fields', sa.JSON(), nullable=True))
# Create link_templates table
op.create_table(
'link_templates',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=200), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('url_template', sa.String(length=1000), nullable=False),
sa.Column('icon', sa.String(length=50), nullable=True),
sa.Column('field_key', sa.String(length=100), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False, server_default=sa.text(bool_true_default)),
sa.Column('order', sa.Integer(), nullable=False, server_default='0'),
sa.Column('created_by', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index('idx_link_templates_is_active', 'link_templates', ['is_active'])
op.create_index('idx_link_templates_field_key', 'link_templates', ['field_key'])
# Create link_templates table (idempotent; some installs may already have it)
if not _has_table(inspector, 'link_templates'):
op.create_table(
'link_templates',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=200), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('url_template', sa.String(length=1000), nullable=False),
sa.Column('icon', sa.String(length=50), nullable=True),
sa.Column('field_key', sa.String(length=100), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False, server_default=sa.text(bool_true_default)),
sa.Column('order', sa.Integer(), nullable=False, server_default='0'),
sa.Column('created_by', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
else:
print("[Migration 075] Table link_templates already exists, skipping creation")
# Ensure indexes exist (best-effort / idempotent)
if _has_table(inspector, 'link_templates'):
if not _has_index(inspector, 'link_templates', 'idx_link_templates_is_active'):
try:
op.create_index('idx_link_templates_is_active', 'link_templates', ['is_active'])
except Exception:
pass
if not _has_index(inspector, 'link_templates', 'idx_link_templates_field_key'):
try:
op.create_index('idx_link_templates_field_key', 'link_templates', ['field_key'])
except Exception:
pass
def downgrade():
@@ -67,13 +97,13 @@ def downgrade():
inspector = sa.inspect(bind)
# Drop link_templates table
if 'link_templates' in inspector.get_table_names():
if _has_table(inspector, 'link_templates'):
op.drop_index('idx_link_templates_field_key', table_name='link_templates')
op.drop_index('idx_link_templates_is_active', table_name='link_templates')
op.drop_table('link_templates')
# Remove custom_fields column from clients table
if 'clients' in inspector.get_table_names():
if _has_table(inspector, 'clients'):
if _has_column(inspector, 'clients', 'custom_fields'):
op.drop_column('clients', 'custom_fields')