Files
shopdb-flask/migrations/versions/7d17_machines_rename.py
Cam Proudlock a9d0dfff4a Initial import: shopdb-flask multi-site asset platform
Flask + Vue rewrite of the classic-ASP shopdb, built as a plugin
framework so sister GE Aerospace sites can adopt it. Product version
0.7.0-pre-0.8.0, plugin contract 0.11.0, 966 backend tests.

Key entry points:
- docs/INSTALL-WINDOWS-IIS.md - tested Windows + IIS install runbook
  (own-site and subpath deployment methods)
- docs/PILOT-DEPLOY.md - production pilot: stand up + import + verify
- docs/IMPORT-API.md - schema-agnostic legacy data import contract
- scripts/site_imports/wjf/ - West Jefferson reference import loader
- docs/adr/ - twelve accepted architecture decision records
2026-07-14 07:58:51 -04:00

206 lines
8.3 KiB
Python

"""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_<name>_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")