"""Reclassify server 'computer' assets into 'network_device' assets. Servers were imported as computers (a PC type), so they show under PCs instead of Network. This re-points each server's asset in place - the assetid does NOT change, so its communications, relationships, map position, name, location, and audit history all carry over. Only the extension row is swapped (computers -> networkdevices) and the asset's type is flipped, and the network device is given the 'Server' networkdevicetype. Identify servers by a name/hostname prefix (default 'SVR-'), or pass --type to match an exact computer type instead. Usage (on the target instance, in the app dir): DATABASE_URL=... venv/bin/python scripts/reclassify_servers_to_network.py # dry run DATABASE_URL=... venv/bin/python scripts/reclassify_servers_to_network.py --commit # apply ... --type "Server" --commit """ import argparse import os import sys # Run from anywhere: put the repo root (parent of scripts/) on the path. sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from shopdb import create_app from shopdb.api import db from shopdb.core.models import Asset, AssetType from plugins.computers.models import Computer, ComputerType from plugins.network.models import NetworkDevice, NetworkDeviceType def _get_or_create_networkdevicetype(name): ndt = NetworkDeviceType.query.filter_by(networkdevicetype=name).first() if not ndt: ndt = NetworkDeviceType(networkdevicetype=name, description='Server (reclassified from PCs)') db.session.add(ndt) db.session.flush() return ndt def main(): parser = argparse.ArgumentParser(description=__doc__) group = parser.add_mutually_exclusive_group() group.add_argument('--prefix', default='SVR-', help="match servers by name/assetnumber/hostname starting " "with this (default 'SVR-')") group.add_argument('--type', help="instead of --prefix, match by an exact computer type name") parser.add_argument('--commit', action='store_true', help='apply the change; without it this is a dry run') args = parser.parse_args() app = create_app() with app.app_context(): nd_assettype = AssetType.query.filter_by(assettype='network_device').first() if not nd_assettype: sys.exit("No 'network_device' asset type - is the network plugin installed?") query = Computer.query.join(Asset) if args.type: ctype = ComputerType.query.filter_by(computertype=args.type).first() if not ctype: sys.exit("No computer type named %r." % args.type) query = query.filter(Computer.computertypeid == ctype.computertypeid) criterion = "computer type %r" % args.type else: like = args.prefix + '%' query = query.filter(db.or_( Asset.assetnumber.like(like), Asset.name.like(like), Computer.hostname.like(like), )) criterion = "name/hostname prefix %r" % args.prefix servers = query.all() if not servers: print("No computers match %s - nothing to do." % criterion) return ndt = _get_or_create_networkdevicetype('Server') print("%d server(s) matching %s -> network_device / type 'Server':" % (len(servers), criterion)) moved = 0 for computer in servers: asset = computer.asset if not asset: continue label = asset.name or asset.assetnumber or ('asset %d' % asset.assetid) print(" - %s (assetid %d)" % (label, asset.assetid)) if args.commit: # Swap extension: create the network device on the SAME asset, # re-point the asset's type, then drop the computer extension. asset.assettypeid = nd_assettype.assettypeid db.session.add(NetworkDevice( assetid=asset.assetid, networkdevicetypeid=ndt.networkdevicetypeid, hostname=getattr(computer, 'hostname', None), vendorid=getattr(computer, 'vendorid', None), )) db.session.delete(computer) # asset stays (FK cascade is asset->computer) moved += 1 if args.commit: db.session.commit() print("\nDone: %d reclassified. They now appear under Network." % moved) else: print("\nDRY RUN - nothing changed. Re-run with --commit to apply.") if __name__ == '__main__': main()