"""Equipment -> machines rename, core half Part 1 of the approved equipment -> machines rename (the plugin half lives in plugins/machines/migrations/versions/0002_rename_from_equipment.py): 1. machinetypes -> modeltypes (+ machinetypeid -> modeltypeid, machinetype -> modeltype, models.machinetypeid -> models.modeltypeid). The table types the vendor MODELS catalog, not machine instances, so the new name is role-accurate and frees "machinetypes" for the plugin. 2. Data flips: assettypes 'equipment' -> 'machine' (incl pluginname and tablename), auditlogs.entitytype 'Equipment' -> 'Machine', settings keys identifier_*_equipment_enabled -> identifier_*_machine_enabled, search_equipment_enabled -> search_machine_enabled, and permission rows equipment.* -> machines.* (role links survive). 3. alembic_version_equipment -> alembic_version_machines (if present) so the renamed plugin's migration chain resumes seamlessly. Idempotent via inspector guards, following the 7c01 pattern. MySQL is the only dialect the core chain runs against in practice. Revision ID: 7d17_machines_rename Revises: 7d16_directoryemployees Create Date: 2026-07-11 """ from alembic import op import sqlalchemy as sa revision = '7d17_machines_rename' down_revision = '7d16_directoryemployees' branch_labels = None depends_on = None def _fk_names(insp, table, referred_table): # find FK constraint names on table pointing at referred_table return [fk['name'] for fk in insp.get_foreign_keys(table) if fk.get('referred_table') == referred_table and fk.get('name')] def upgrade(): bind = op.get_bind() insp = sa.inspect(bind) tables = set(insp.get_table_names()) # -- 1. machinetypes -> modeltypes ------------------------------------ if 'machinetypes' in tables and 'modeltypes' not in tables: bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=0") # drop the models -> machinetypes FK and its index before renaming for name in _fk_names(insp, 'models', 'machinetypes'): bind.exec_driver_sql(f"ALTER TABLE models DROP FOREIGN KEY {name}") model_indexes = {ix['name'] for ix in insp.get_indexes('models')} if 'machinetypeid' in model_indexes: bind.exec_driver_sql("ALTER TABLE models DROP KEY machinetypeid") bind.exec_driver_sql("RENAME TABLE machinetypes TO modeltypes") bind.exec_driver_sql( "ALTER TABLE modeltypes " "CHANGE machinetypeid modeltypeid INT NOT NULL AUTO_INCREMENT, " "CHANGE machinetype modeltype VARCHAR(100) NOT NULL, " "DROP KEY machinetype, " "ADD UNIQUE KEY modeltype (modeltype)" ) bind.exec_driver_sql( "ALTER TABLE models " "CHANGE machinetypeid modeltypeid INT NULL, " "ADD KEY modeltypeid (modeltypeid), " "ADD CONSTRAINT fk_models_modeltypeid " "FOREIGN KEY (modeltypeid) REFERENCES modeltypes (modeltypeid)" ) bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=1") # -- 2. data flips ------------------------------------------------------ if 'assettypes' in tables: bind.exec_driver_sql( "UPDATE assettypes SET assettype='machine', pluginname='machines', " "tablename='machines' WHERE assettype='equipment'" ) if 'auditlogs' in tables: bind.exec_driver_sql( "UPDATE auditlogs SET entitytype='Machine' " "WHERE entitytype='Equipment'" ) if 'settings' in tables: _rename_setting_keys(bind, old_marker='_equipment_', new_marker='_machine_') if 'permissions' in tables: _rename_permissions(bind, old_prefix='equipment.', new_prefix='machines.', old_category='equipment', new_category='machines') # -- 3. plugin version table -------------------------------------------- if ('alembic_version_equipment' in tables and 'alembic_version_machines' not in tables): bind.exec_driver_sql( "RENAME TABLE alembic_version_equipment TO alembic_version_machines" ) def _rename_setting_keys(bind, old_marker, new_marker): """Rename settings keys embedding the assettype value, collision-safe. Covers identifier__equipment_enabled and search_equipment_enabled. If a row with the new key already exists (fresh seed ran on new code), the old row is dropped instead of renamed. """ # broad fetch; the marker replace below filters non-matches rows = bind.exec_driver_sql( "SELECT settingid, `key` FROM settings" ).fetchall() for settingid, key in rows: newkey = key.replace(old_marker, new_marker) if newkey == key: continue exists = bind.exec_driver_sql( "SELECT 1 FROM settings WHERE `key` = %s", (newkey,) ).fetchone() if exists: bind.exec_driver_sql( "DELETE FROM settings WHERE settingid = %s", (settingid,)) else: bind.exec_driver_sql( "UPDATE settings SET `key` = %s WHERE settingid = %s", (newkey, settingid)) def _rename_permissions(bind, old_prefix, new_prefix, old_category, new_category): """Rename permission rows in place so role links survive, collision-safe.""" rows = bind.exec_driver_sql( "SELECT permissionid, name FROM permissions" ).fetchall() for permissionid, name in rows: if not name.startswith(old_prefix): continue newname = new_prefix + name[len(old_prefix):] exists = bind.exec_driver_sql( "SELECT 1 FROM permissions WHERE name = %s", (newname,) ).fetchone() if exists: bind.exec_driver_sql( "DELETE FROM permissions WHERE permissionid = %s", (permissionid,)) else: bind.exec_driver_sql( "UPDATE permissions SET name = %s, category = %s " "WHERE permissionid = %s", (newname, new_category, permissionid)) def downgrade(): bind = op.get_bind() insp = sa.inspect(bind) tables = set(insp.get_table_names()) if ('alembic_version_machines' in tables and 'alembic_version_equipment' not in tables): bind.exec_driver_sql( "RENAME TABLE alembic_version_machines TO alembic_version_equipment" ) if 'settings' in tables: _rename_setting_keys(bind, old_marker='_machine_', new_marker='_equipment_') if 'permissions' in tables: _rename_permissions(bind, old_prefix='machines.', new_prefix='equipment.', old_category='machines', new_category='equipment') if 'auditlogs' in tables: bind.exec_driver_sql( "UPDATE auditlogs SET entitytype='Equipment' " "WHERE entitytype='Machine'" ) if 'assettypes' in tables: bind.exec_driver_sql( "UPDATE assettypes SET assettype='equipment', " "pluginname='equipment', tablename='equipment' " "WHERE assettype='machine'" ) if 'modeltypes' in tables and 'machinetypes' not in tables: bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=0") for name in _fk_names(insp, 'models', 'modeltypes'): bind.exec_driver_sql(f"ALTER TABLE models DROP FOREIGN KEY {name}") model_indexes = {ix['name'] for ix in insp.get_indexes('models')} if 'modeltypeid' in model_indexes: bind.exec_driver_sql("ALTER TABLE models DROP KEY modeltypeid") bind.exec_driver_sql("RENAME TABLE modeltypes TO machinetypes") bind.exec_driver_sql( "ALTER TABLE machinetypes " "CHANGE modeltypeid machinetypeid INT NOT NULL AUTO_INCREMENT, " "CHANGE modeltype machinetype VARCHAR(100) NOT NULL, " "DROP KEY modeltype, " "ADD UNIQUE KEY machinetype (machinetype)" ) bind.exec_driver_sql( "ALTER TABLE models " "CHANGE modeltypeid machinetypeid INT NULL, " "ADD KEY machinetypeid (machinetypeid), " "ADD CONSTRAINT models_ibfk_1 " "FOREIGN KEY (machinetypeid) REFERENCES machinetypes (machinetypeid)" ) bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=1")