From 3eaaee0e50403a73a21381011f7d246b3da30dc4 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Wed, 29 Jul 2026 12:56:31 -0400 Subject: [PATCH] printers: resolve installer vendor via the model + fix batch download base URL Two fixes for the printer install-batch on prod data: 1. Vendor was read only from the printer's direct vendorid, which the legacy import never sets (it sets the model; legacy resolved vendor through the model). Every prod printer came back vendor "unknown", so all fell into the manual group and the universal PrinterInstaller.exe block never emitted. Now resolve vendor via the model's vendor when the printer has no direct one, as the classic installprinter.asp did. 2. Harden the download base URL. Behind IIS the app sees http on a loopback port and url_root drops the /shopdb mount, giving a broken download URL when site_base_url is unset. Fall back to https + the forwarded Host + script_root. Test: a printer with no vendorid but an HP/Xerox model now groups universal. --- plugins/printers/api/asset_routes.py | 25 ++++++++++++++--- .../test_printer_install_batch.py | 27 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/plugins/printers/api/asset_routes.py b/plugins/printers/api/asset_routes.py index d45401b..6ad1c21 100644 --- a/plugins/printers/api/asset_routes.py +++ b/plugins/printers/api/asset_routes.py @@ -364,12 +364,29 @@ UNIVERSAL_INSTALL_VENDORS = frozenset({'HP', 'Xerox'}) def _batch_base_url(): """Base URL the generated .bat downloads installers from. Prefer the - configured site_base_url (already includes the /shopdb mount); fall back to - the request root so a site that never set it still produces a usable batch.""" + configured site_base_url (it already includes scheme + the /shopdb mount). + + Fallback matters: behind IIS the app sees http on a loopback port and its + url_root drops the mount, so a naive request.url_root yields a broken + http://127.0.0.1/installers/... URL. Rebuild from the forwarded Host + the + mount (script_root) and force https instead.""" base = (Setting.get('site_base_url') or '').strip().rstrip('/') if base: return base - return request.url_root.rstrip('/') + host = request.headers.get('X-Forwarded-Host') or request.host + root = (request.script_root or '').rstrip('/') + return 'https://%s%s' % (host, root) + + +def _printer_vendor(printer): + """Vendor name for install grouping. The legacy import sets the printer's + model but not its direct vendorid, so resolve via the model's vendor (as the + classic installprinter.asp did) when the printer has no direct vendor.""" + if printer.vendor: + return (printer.vendor.vendor or '').strip() + if printer.model and printer.model.vendor: + return (printer.model.vendor.vendor or '').strip() + return '' def _installer_url(installpath, base): @@ -429,7 +446,7 @@ def printer_install_batch(): name = _install_name(printer, asset) if not name: continue - vendor = (printer.vendor.vendor if printer.vendor else '').strip() + vendor = _printer_vendor(printer) installpath = (printer.installpath or '').strip() if vendor in UNIVERSAL_INSTALL_VENDORS: universal.append(name) diff --git a/tests/test_plugins/test_printer_install_batch.py b/tests/test_plugins/test_printer_install_batch.py index 7d71356..e0c5eb6 100644 --- a/tests/test_plugins/test_printer_install_batch.py +++ b/tests/test_plugins/test_printer_install_batch.py @@ -72,6 +72,33 @@ def test_install_batch_groups_universal_specific_and_manual( assert 'c350navi.zip' not in bat # never handed to the runner +def test_install_batch_resolves_vendor_via_model( + client, db, auth_headers, printer_assettype): + """A printer with no direct vendorid still groups as universal when its + MODEL's vendor is HP/Xerox (the import sets the model, not the printer's + vendorid) - the bug that dumped every prod printer into 'manual (unknown)'.""" + from shopdb.core.models import Vendor, Model + + xerox = Vendor(vendor='Xerox') + db.session.add(xerox) + db.session.commit() + model = Model(modelnumber='VersaLink C7125', vendorid=xerox.vendorid) + db.session.add(model) + db.session.commit() + + # No vendorid on the printer - only the model ties it to Xerox. + pid = _make_printer(client, auth_headers, assetnumber='SpoolsInspection-Xerox', + hostname='wjprn07', modelnumberid=model.modelnumberid) + + resp = client.get('/api/printers/install-batch?printerids=%d' % pid, + headers=auth_headers) + assert resp.status_code == 200 + bat = resp.get_data(as_text=True) + assert 'PrinterInstaller.exe' in bat + assert '/PRINTER="SpoolsInspection-Xerox"' in bat + assert 'MANUAL' not in bat + + def test_install_batch_requires_ids(client, db, auth_headers, printer_assettype): resp = client.get('/api/printers/install-batch', headers=auth_headers) assert resp.status_code == 400