122 lines
5.0 KiB
Python
122 lines
5.0 KiB
Python
"""Fill in an asset's vendor from the catalog model it already points at.
|
|
|
|
An asset that carries a model but no vendor is showing a blank the database can
|
|
answer: the model records its own vendor, and both sides reference the same
|
|
vendors table. The detail pages fall back to it at display time, but the record
|
|
itself stays empty, so the edit form shows an empty vendor box and any report
|
|
reading vendorid directly still sees nothing. This writes it down.
|
|
|
|
ONLY fills rows where the asset's vendor is NULL and the model names one. It
|
|
never overwrites a vendor somebody chose, and it never guesses: if the model has
|
|
no vendor either, the row is left alone.
|
|
|
|
DELIBERATELY NOT DONE HERE: the asset's TYPE. modeltypes is the catalog-wide
|
|
list covering every kind of asset - it holds "Access Point", "Camera" and
|
|
"Desktop PC" alongside the machine entries - so it is a different taxonomy from
|
|
machinetypes, and mapping one onto the other by name would mistype whatever does
|
|
not match. Only about two thirds of the names overlap.
|
|
|
|
Dry run by default; nothing is written without --commit.
|
|
|
|
python scripts/backfill_vendor_from_model.py
|
|
python scripts/backfill_vendor_from_model.py --commit
|
|
python scripts/backfill_vendor_from_model.py --only machines --commit
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
# Each asset table that carries both a model link and its own vendor, with the
|
|
# column that identifies a row in reports and logs.
|
|
TARGETS = [
|
|
('machines', 'machineid', 'machinename'),
|
|
('computers', 'computerid', 'computername'),
|
|
('printers', 'printerid', 'printername'),
|
|
('networkdevices', 'networkdeviceid', 'hostname'),
|
|
]
|
|
|
|
|
|
def resolve(connection, table, idcol, labelcol):
|
|
"""Rows that would change, newest table columns tolerated."""
|
|
from sqlalchemy import text
|
|
columns = {c['name'] for c in __import__('sqlalchemy').inspect(connection).get_columns(table)}
|
|
if 'modelnumberid' not in columns or 'vendorid' not in columns:
|
|
return None, []
|
|
label = labelcol if labelcol in columns else idcol
|
|
rows = connection.execute(text(f"""
|
|
SELECT a.{idcol} AS assetid,
|
|
a.{label} AS label,
|
|
m.modelnumber AS modelnumber,
|
|
m.vendorid AS vendorid,
|
|
v.vendor AS vendorname
|
|
FROM {table} a
|
|
JOIN models m ON a.modelnumberid = m.modelnumberid
|
|
JOIN vendors v ON m.vendorid = v.vendorid
|
|
WHERE a.vendorid IS NULL
|
|
AND m.vendorid IS NOT NULL
|
|
ORDER BY a.{idcol}
|
|
""")).fetchall()
|
|
return label, rows
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
parser.add_argument('--commit', action='store_true',
|
|
help='write the changes (default is a dry run)')
|
|
parser.add_argument('--only', metavar='TABLE',
|
|
help='restrict to one table, e.g. machines')
|
|
parser.add_argument('--limit', type=int, default=20,
|
|
help='rows to list per table in the preview (default 20)')
|
|
args = parser.parse_args()
|
|
|
|
# FLASK_ENV is deliberately NOT forced. The app reads it from .env, which on
|
|
# a server already says production; overriding it here demanded a SECRET_KEY
|
|
# the environment had no reason to provide and the script could not run.
|
|
from sqlalchemy import text
|
|
from shopdb import create_app
|
|
from shopdb.extensions import db
|
|
|
|
app = create_app()
|
|
with app.app_context():
|
|
connection = db.session.connection()
|
|
total = 0
|
|
for table, idcol, labelcol in TARGETS:
|
|
if args.only and args.only != table:
|
|
continue
|
|
label, rows = resolve(connection, table, idcol, labelcol)
|
|
if label is None:
|
|
print(f'{table}: no model or vendor column here, skipped')
|
|
continue
|
|
print(f'\n{table}: {len(rows)} row(s) would get a vendor')
|
|
for row in rows[:args.limit]:
|
|
print(f' {row.assetid:>7} {str(row.label)[:28]:<28} '
|
|
f'model {row.modelnumber[:22]:<22} -> {row.vendorname}')
|
|
if len(rows) > args.limit:
|
|
print(f' ... and {len(rows) - args.limit} more')
|
|
total += len(rows)
|
|
|
|
if args.commit and rows:
|
|
connection.execute(text(f"""
|
|
UPDATE {table} a
|
|
JOIN models m ON a.modelnumberid = m.modelnumberid
|
|
SET a.vendorid = m.vendorid
|
|
WHERE a.vendorid IS NULL
|
|
AND m.vendorid IS NOT NULL
|
|
"""))
|
|
|
|
if args.commit:
|
|
db.session.commit()
|
|
print(f'\nCommitted. {total} row(s) updated.')
|
|
else:
|
|
print(f'\nDRY RUN - nothing written. {total} row(s) would change.')
|
|
print('Re-run with --commit to apply.')
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|