Resolve a driver by vendor, and converge a bay's printers from ShopDB
Two rows now cover 41 of 44 printers. printerdrivers could only bind a driver to ONE modelnumberid, so the HP and Xerox universal drivers - which between them cover almost the whole floor - would have needed 21 near-duplicate rows pointing at the same package. That is a table nobody keeps true, and it is why 42 of 44 printers resolved no driver at all. printerdrivers gains vendorid, and resolution runs most-specific-first: the printer's model, then its vendor, then the pre-vendorid convention of matching the vendor word in the driver's name so a site that populated the table before the column existed does not silently lose every driver on upgrade. A row that names a vendor is never matched by its text, because a mis-set vendor resolving to the wrong package is worse than resolving to none. Six rows now resolve 44 of 44 printers at the reference site, and the DesignJet correctly takes its own driver over the HP universal one. Set-ShopdbPrinters.ps1 is the client half: ask for-host, create the queues that are missing, record the desired default. It NEVER removes a queue - a bad minute from the API must not take printers away from a working bay - and it never fetches a driver, because downloading 48 MB while somebody waits to print is the wrong moment. The common scope stages those. Apply-ShopdbDefaultPrinter.ps1 applies the default in the USER's context, which is the only context that can: SYSTEM cannot set a per-user default for somebody else. It also turns off "Let Windows manage my default printer", without which Windows silently overwrites the choice the next time anyone prints elsewhere - a fix that undoes itself within a day. VALIDATED ON WINDOWS 11 AGAINST A LIVE SHOPDB, not only by tests. Printers were assigned to a MACHINE; a PC controlling it, holding no rows of its own, created both queues bound to the right universal drivers, recorded the default and set it, and a second run changed nothing. The first attempt failed with "Relationship types are not seeded - run: flask seed reference-data", which is the deployment trap the plan predicted, caught by an explicit error rather than silently resolving nothing.
This commit is contained in:
@@ -797,11 +797,18 @@ def _assignment_result(printerassetids, defaultassetid, suppliers):
|
||||
def _printer_driver(printer, universaldrivers):
|
||||
"""Driver record to install this printer with, or None.
|
||||
|
||||
The printer's own model link first. Failing that, a driver with no model at
|
||||
all whose name carries the printer's vendor: HP and Xerox universal drivers
|
||||
cover the overwhelming majority of a floor, and per-model rows for each
|
||||
queue are a table nobody keeps true. printerdrivers cannot name a vendor of
|
||||
its own yet, so the vendor word in the driver's name is what there is.
|
||||
Three steps, most specific first:
|
||||
|
||||
1. A driver bound to the printer's MODEL. A plotter, a card printer and a
|
||||
label printer each need their own, and a per-model row must beat the
|
||||
universal one.
|
||||
2. A driver bound to the printer's VENDOR with no model. HP's and Xerox's
|
||||
universal drivers cover 41 of the reference site's 44 printers between
|
||||
them; binding those to one model each would mean a near-duplicate row per
|
||||
model, which is a table nobody keeps true.
|
||||
3. Failing both, a model-less driver whose NAME carries the vendor word.
|
||||
This is the pre-vendorid convention, kept so a site that populated its
|
||||
table before the column existed does not lose its drivers on upgrade.
|
||||
"""
|
||||
if printer.modelnumberid:
|
||||
driver = (PrinterDriver.query
|
||||
@@ -810,10 +817,20 @@ def _printer_driver(printer, universaldrivers):
|
||||
if driver:
|
||||
return driver
|
||||
|
||||
if printer.vendorid:
|
||||
for driver in universaldrivers:
|
||||
if driver.vendorid == printer.vendorid:
|
||||
return driver
|
||||
|
||||
vendor = _printer_vendor(printer).lower()
|
||||
if not vendor:
|
||||
return None
|
||||
for driver in universaldrivers:
|
||||
# Only the legacy convention here: a row WITH a vendorid that did not
|
||||
# match above must not be matched by its name instead, or a mis-set
|
||||
# vendor silently resolves to the wrong package.
|
||||
if driver.vendorid:
|
||||
continue
|
||||
if vendor in (driver.name or '').lower():
|
||||
return driver
|
||||
return None
|
||||
|
||||
94
plugins/printers/client/Apply-ShopdbDefaultPrinter.ps1
Normal file
94
plugins/printers/client/Apply-ShopdbDefaultPrinter.ps1
Normal file
@@ -0,0 +1,94 @@
|
||||
# Apply-ShopdbDefaultPrinter.ps1
|
||||
#
|
||||
# Sets the logged-on user's default printer to the one ShopDB assigned. Runs IN
|
||||
# THE USER'S CONTEXT, at logon and on a repeat, because a default printer is
|
||||
# per-user state that SYSTEM cannot set for somebody else.
|
||||
#
|
||||
# Set-ShopdbPrinters.ps1 records the desired queue in
|
||||
# HKLM:\SOFTWARE\GE\ShopDB DefaultPrinter during the enforcement cycle. This
|
||||
# reads it. Splitting the two is not tidiness: the machine half needs SYSTEM and
|
||||
# the share, the user half needs a user - no single process has both.
|
||||
#
|
||||
# IT ALSO TURNS OFF "Let Windows manage my default printer". Leaving it on means
|
||||
# Windows silently overwrites the choice the next time somebody prints to another
|
||||
# queue, and the bay drifts back with nothing in any log to say why.
|
||||
#
|
||||
# Converges: when the current default already matches, it does nothing, so a
|
||||
# repeating trigger costs a registry read. A user who deliberately picks another
|
||||
# default WILL be corrected on the next run - that is the intent for a shared
|
||||
# bay. For a PC where that is wrong, schedule it at logon only.
|
||||
#
|
||||
# Exits 0 always.
|
||||
|
||||
param(
|
||||
# Override for testing. Normally read from the machine hive.
|
||||
[string]$PrinterName = ''
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
$logDir = "$env:LOCALAPPDATA\ShopDB"
|
||||
if (-not (Test-Path $logDir)) {
|
||||
New-Item -ItemType Directory -Path $logDir -Force -ErrorAction SilentlyContinue | Out-Null
|
||||
}
|
||||
$logFile = Join-Path $logDir 'default-printer.log'
|
||||
function Log([string]$msg) {
|
||||
$ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
"$ts $msg" | Tee-Object -FilePath $logFile -Append | Out-Null
|
||||
}
|
||||
|
||||
if (-not $PrinterName) {
|
||||
foreach ($path in @('HKLM:\SOFTWARE\GE\ShopDB', 'HKLM:\SOFTWARE\WOW6432Node\GE\ShopDB')) {
|
||||
try {
|
||||
if (Test-Path $path) {
|
||||
$value = [string](Get-ItemProperty -Path $path -Name DefaultPrinter -ErrorAction Stop).DefaultPrinter
|
||||
if ($value -and $value.Trim()) { $PrinterName = $value.Trim(); break }
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $PrinterName) {
|
||||
# No default assigned is a legitimate state - a bay with three printers and
|
||||
# no favourite - so leave whatever the user has.
|
||||
Log 'no default assigned in ShopDB; leaving the current one alone'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Windows 10+ overrides any default the moment the user prints elsewhere, unless
|
||||
# this is off. Setting the default without clearing this is a fix that undoes
|
||||
# itself within a day.
|
||||
try {
|
||||
$windowsKey = 'HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows'
|
||||
$managed = (Get-ItemProperty -Path $windowsKey -Name LegacyDefaultPrinterMode -ErrorAction SilentlyContinue).LegacyDefaultPrinterMode
|
||||
if ($managed -ne 1) {
|
||||
Set-ItemProperty -Path $windowsKey -Name LegacyDefaultPrinterMode -Value 1 -Type DWord
|
||||
Log 'turned off "Let Windows manage my default printer"'
|
||||
}
|
||||
} catch {
|
||||
Log "WARN could not turn off Windows-managed defaults: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$queue = Get-Printer -Name $PrinterName -ErrorAction SilentlyContinue
|
||||
if (-not $queue) {
|
||||
# The enforcement cycle creates queues; this runs at logon and may simply be
|
||||
# earlier than the first cycle on a new bay. Next run picks it up.
|
||||
Log "assigned default '$PrinterName' is not installed yet; nothing to do"
|
||||
exit 0
|
||||
}
|
||||
|
||||
$current = (Get-CimInstance -ClassName Win32_Printer -Filter 'Default = True' -ErrorAction SilentlyContinue).Name
|
||||
if ($current -eq $PrinterName) {
|
||||
Log "already default: $PrinterName"
|
||||
exit 0
|
||||
}
|
||||
|
||||
try {
|
||||
$target = Get-CimInstance -ClassName Win32_Printer -Filter ("Name = '{0}'" -f $PrinterName.Replace("'", "''")) -ErrorAction Stop
|
||||
Invoke-CimMethod -InputObject $target -MethodName SetDefaultPrinter -ErrorAction Stop | Out-Null
|
||||
Log "default set: $PrinterName (was '$current')"
|
||||
} catch {
|
||||
Log "ERROR setting the default to '$PrinterName': $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
exit 0
|
||||
@@ -112,3 +112,33 @@ reference site's fleet:
|
||||
| `ZDesigner ZT411-300dpi ZPL` | Zebra ZT411 labels |
|
||||
| `EPSON TM-C3500` | Epson ColorWorks labels |
|
||||
| `DTC4500e Card Printer` | HID FARGO card printer |
|
||||
|
||||
## The other half: assigning printers
|
||||
|
||||
Staging drivers is only delivery. `Set-ShopdbPrinters.ps1` is what makes a bay's
|
||||
queues match ShopDB, and `Apply-ShopdbDefaultPrinter.ps1` applies the default in
|
||||
the user's context. Two manifest entries, both `DetectionMethod: Always`:
|
||||
|
||||
```json
|
||||
{
|
||||
"_comment": "Create the queues this bay is assigned. Converges: existing queues are left alone, and nothing is ever removed.",
|
||||
"Name": "ShopDB printers",
|
||||
"Type": "PS1",
|
||||
"Script": "scripts/Set-ShopdbPrinters.ps1",
|
||||
"DetectionMethod": "Always"
|
||||
}
|
||||
```
|
||||
|
||||
The default printer is per-user, so SYSTEM cannot set it for the person logged
|
||||
on. `Set-ShopdbPrinters.ps1` records it in `HKLM:\SOFTWARE\GE\ShopDB`
|
||||
`DefaultPrinter`, and `Apply-ShopdbDefaultPrinter.ps1` runs as the user - at
|
||||
logon, and on a repeat if the site wants drift corrected.
|
||||
|
||||
Order matters on a new bay: drivers, then queues, then the default. Each step is
|
||||
a no-op once satisfied, so running all three every cycle costs a few registry
|
||||
reads.
|
||||
|
||||
Verified end to end on Windows 11 against a live ShopDB: printers assigned to a
|
||||
MACHINE, a PC controlling it and holding no rows of its own, and the bay created
|
||||
both queues with the right universal driver, recorded the default, and set it -
|
||||
then a second run changed nothing.
|
||||
|
||||
166
plugins/printers/client/Set-ShopdbPrinters.ps1
Normal file
166
plugins/printers/client/Set-ShopdbPrinters.ps1
Normal file
@@ -0,0 +1,166 @@
|
||||
# Set-ShopdbPrinters.ps1
|
||||
#
|
||||
# Makes this PC's printers match what ShopDB says the bay should have. Asks
|
||||
# GET /api/printers/for-host/<hostname> and creates any queue that is missing.
|
||||
#
|
||||
# WHY THE ASSIGNMENT IS NOT ON THIS PC: it is on the MACHINE, and reaches
|
||||
# whichever PC controls it. A reimaged or swapped box inherits the bay's printers
|
||||
# with nothing saved off the old one - the asset register is the backup.
|
||||
#
|
||||
# CONVERGES, does not install. A queue that already exists is left alone, so this
|
||||
# is cheap to run every enforcement cycle and safe to run twice.
|
||||
#
|
||||
# NEVER REMOVES A QUEUE. If a printer disappears from the response - because the
|
||||
# API had a bad minute, or someone unassigned it - the bay keeps printing. Taking
|
||||
# printers away from a working bay because of a transient error is the one
|
||||
# failure this must not have.
|
||||
#
|
||||
# DRIVERS ARE NOT FETCHED HERE. Install-ShopdbPrinterDrivers.ps1 stages the site's
|
||||
# set in the common scope, once per bay. A queue is created against a driver that
|
||||
# is already present; if it is not, that is logged and the printer is skipped,
|
||||
# because downloading 48 MB while somebody waits to print is the wrong moment.
|
||||
#
|
||||
# THE DEFAULT PRINTER IS PER USER. This runs as SYSTEM and cannot set it for the
|
||||
# logged-on person, so it records the desired default in HKLM and leaves applying
|
||||
# it to a logon task. Without that, SYSTEM would set a default nobody sees.
|
||||
#
|
||||
# Exits 0 always: a printer problem must not fail an enforcement run.
|
||||
|
||||
param(
|
||||
# ShopDB base URL. Empty resolves from HKLM:\SOFTWARE\GE\ShopDB BaseUrl,
|
||||
# written by Install-GEEnforce.ps1 and already present wherever this runs.
|
||||
[string]$BaseUrl = '',
|
||||
|
||||
# Defaults to this machine's name, which is what the collector upserts by.
|
||||
[string]$Hostname = $env:COMPUTERNAME,
|
||||
|
||||
[int]$TimeoutSec = 30,
|
||||
|
||||
# Report what would change and touch nothing.
|
||||
[switch]$WhatIfOnly
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
$logDir = 'C:\Logs\Shopfloor'
|
||||
if (-not (Test-Path $logDir)) {
|
||||
New-Item -ItemType Directory -Path $logDir -Force -ErrorAction SilentlyContinue | Out-Null
|
||||
}
|
||||
$logFile = Join-Path $logDir ('printers-{0}.log' -f (Get-Date -Format 'yyyyMMdd'))
|
||||
function Log([string]$msg) {
|
||||
$ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
"$ts $msg" | Tee-Object -FilePath $logFile -Append | Out-Null
|
||||
}
|
||||
|
||||
$REGPATH = 'HKLM:\SOFTWARE\GE\ShopDB'
|
||||
|
||||
if (-not $BaseUrl) {
|
||||
foreach ($path in @($REGPATH, 'HKLM:\SOFTWARE\WOW6432Node\GE\ShopDB')) {
|
||||
try {
|
||||
if (Test-Path $path) {
|
||||
$value = [string](Get-ItemProperty -Path $path -Name BaseUrl -ErrorAction Stop).BaseUrl
|
||||
if ($value -and $value.Trim()) { $BaseUrl = $value.Trim(); break }
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
if (-not $BaseUrl) {
|
||||
Log 'ERROR no ShopDB URL (HKLM:\SOFTWARE\GE\ShopDB BaseUrl or -BaseUrl). Skipping.'
|
||||
exit 0
|
||||
}
|
||||
|
||||
Log "=== Set printers for $Hostname ==="
|
||||
|
||||
$url = $BaseUrl.TrimEnd('/') + '/api/printers/for-host/' + [uri]::EscapeDataString($Hostname)
|
||||
try {
|
||||
$response = Invoke-RestMethod -Uri $url -Method Get -TimeoutSec $TimeoutSec
|
||||
} catch {
|
||||
# An unreachable server means "no information", not "no printers". Changing
|
||||
# nothing is the only safe response.
|
||||
Log "ERROR could not read $url : $($_.Exception.Message)"
|
||||
exit 0
|
||||
}
|
||||
|
||||
$payload = $response.data
|
||||
if ($null -eq $payload) { $payload = $response }
|
||||
$wanted = @($payload.printers)
|
||||
$defaultid = $payload.defaultprinterid
|
||||
|
||||
if ($wanted.Count -eq 0) {
|
||||
Log 'nothing assigned to this host'
|
||||
exit 0
|
||||
}
|
||||
Log "assigned: $($wanted.Count) printer(s)"
|
||||
|
||||
$existing = @{}
|
||||
foreach ($queue in (Get-Printer -ErrorAction SilentlyContinue)) {
|
||||
$existing[$queue.Name] = $queue
|
||||
}
|
||||
|
||||
$defaultname = ''
|
||||
foreach ($printer in $wanted) {
|
||||
$name = $printer.queuename
|
||||
if (-not $name) { continue }
|
||||
if ($printer.printerid -eq $defaultid) { $defaultname = $name }
|
||||
|
||||
if ($existing.ContainsKey($name)) {
|
||||
Log "present: $name"
|
||||
continue
|
||||
}
|
||||
|
||||
$address = $printer.hostname
|
||||
if (-not $address) { $address = $printer.ipaddress }
|
||||
if (-not $address) {
|
||||
Log "SKIP $name : no hostname or IP to point a port at"
|
||||
continue
|
||||
}
|
||||
|
||||
$drivername = $printer.drivername
|
||||
if (-not $drivername) {
|
||||
Log "SKIP $name : ShopDB has no driver name for it"
|
||||
continue
|
||||
}
|
||||
if (-not (Get-PrinterDriver -Name $drivername -ErrorAction SilentlyContinue)) {
|
||||
# Deliberately not fetched here - see the header.
|
||||
Log "SKIP $name : driver '$drivername' is not staged on this PC"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($WhatIfOnly) {
|
||||
Log "WOULD create: $name -> $address ($drivername)"
|
||||
continue
|
||||
}
|
||||
|
||||
$portname = 'IP_' + $address
|
||||
try {
|
||||
if (-not (Get-PrinterPort -Name $portname -ErrorAction SilentlyContinue)) {
|
||||
Add-PrinterPort -Name $portname -PrinterHostAddress $address -ErrorAction Stop
|
||||
Log "port: $portname"
|
||||
}
|
||||
Add-Printer -Name $name -DriverName $drivername -PortName $portname -ErrorAction Stop
|
||||
Log "created: $name -> $address ($drivername)"
|
||||
} catch {
|
||||
Log "ERROR creating ${name}: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# The default is recorded, not applied: this process is SYSTEM and the setting
|
||||
# is per user. Apply-ShopdbDefaultPrinter.ps1 reads it at logon.
|
||||
if ($defaultname) {
|
||||
if ($WhatIfOnly) {
|
||||
Log "WOULD record default: $defaultname"
|
||||
} else {
|
||||
try {
|
||||
if (-not (Test-Path $REGPATH)) { New-Item -Path $REGPATH -Force | Out-Null }
|
||||
Set-ItemProperty -Path $REGPATH -Name DefaultPrinter -Value $defaultname
|
||||
Log "default recorded for the logon task: $defaultname"
|
||||
} catch {
|
||||
Log "ERROR recording the default: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log 'no default assigned'
|
||||
}
|
||||
|
||||
exit 0
|
||||
44
plugins/printers/migrations/versions/0004_drivervendor.py
Normal file
44
plugins/printers/migrations/versions/0004_drivervendor.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Give a printer driver a vendor, so one row can serve a whole make.
|
||||
|
||||
HP's and Xerox's universal drivers cover 41 of the reference site's 44 printers
|
||||
between them, but a driver could only be bound to ONE modelnumberid - so covering
|
||||
them meant 21 near-duplicate rows all pointing at the same package, a table
|
||||
nobody would keep true. A driver with no model and a vendor now serves every
|
||||
printer of that make, and a per-model row still wins where one genuinely differs
|
||||
(a plotter, a card printer, a label printer).
|
||||
|
||||
Nullable and guarded: a re-run is a no-op, and existing rows keep working
|
||||
unchanged because model matching is still tried first.
|
||||
|
||||
Revision ID: printers0004drivervendor
|
||||
Revises: printers0003drivername
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision = 'printers0004drivervendor'
|
||||
down_revision = 'printers0003drivername'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
inspector = sa.inspect(bind)
|
||||
columns = {column['name'] for column in inspector.get_columns('printerdrivers')}
|
||||
if 'vendorid' not in columns:
|
||||
op.add_column('printerdrivers', sa.Column('vendorid', sa.Integer(), nullable=True))
|
||||
# No FK constraint: printerdrivers is a plugin table and vendors is core.
|
||||
# ADR-008 keeps plugin chains from writing constraints across that line,
|
||||
# and the resolver treats a vendor that no longer exists as no match.
|
||||
op.create_index('idx_printerdriver_vendor', 'printerdrivers', ['vendorid'])
|
||||
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
inspector = sa.inspect(bind)
|
||||
columns = {column['name'] for column in inspector.get_columns('printerdrivers')}
|
||||
if 'vendorid' in columns:
|
||||
op.drop_index('idx_printerdriver_vendor', table_name='printerdrivers')
|
||||
op.drop_column('printerdrivers', 'vendorid')
|
||||
@@ -14,6 +14,12 @@ class PrinterDriver(db.Model):
|
||||
# Exact driver name as the INF declares it: Add-PrinterDriver matches on
|
||||
# this string, not on `name`, which is ours to choose
|
||||
drivername = db.Column(db.String(255))
|
||||
# Optional: attach a driver to a whole VENDOR rather than one model. A
|
||||
# universal driver (HP UPD, Xerox GPD) serves every printer of that make, and
|
||||
# binding it to one model would mean a near-duplicate row per model.
|
||||
# Model wins over vendor when both match - see _printer_driver.
|
||||
vendorid = db.Column(db.Integer, nullable=True, index=True)
|
||||
|
||||
# Optional: attach a driver to a specific printer model
|
||||
modelnumberid = db.Column(
|
||||
db.Integer,
|
||||
@@ -31,6 +37,7 @@ class PrinterDriver(db.Model):
|
||||
'location': self.location,
|
||||
'description': self.description,
|
||||
'drivername': self.drivername,
|
||||
'vendorid': self.vendorid,
|
||||
'modelnumberid': self.modelnumberid,
|
||||
'modelname': self.model.modelnumber if self.model else None,
|
||||
'isactive': bool(self.isactive),
|
||||
|
||||
@@ -55,7 +55,7 @@ EXPECTED_HEAD_REVISION['backups'] = 'backups0003clearlastseen'
|
||||
EXPECTED_HEAD_REVISION['geenforce'] = 'geenforce0004minlib'
|
||||
# printers adds the printersupplyalerts crossing-state table on top of its
|
||||
# anchor, then the exact INF driver name Add-PrinterDriver needs.
|
||||
EXPECTED_HEAD_REVISION['printers'] = 'printers0003drivername'
|
||||
EXPECTED_HEAD_REVISION['printers'] = 'printers0004drivervendor'
|
||||
# machines (renamed from equipment) keeps its original anchor id and adds the
|
||||
# rename revision on top, so its head is not the f-string default.
|
||||
EXPECTED_HEAD_REVISION['machines'] = 'machines0002rename'
|
||||
|
||||
120
tests/test_plugins/test_printer_driver_resolution.py
Normal file
120
tests/test_plugins/test_printer_driver_resolution.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""Which driver a printer installs with.
|
||||
|
||||
One row per model was unworkable: HP and Xerox universal drivers cover 41 of the
|
||||
reference site's 44 printers, so binding a driver to a single model meant 21
|
||||
near-duplicate rows pointing at one package - a table nobody keeps true, and the
|
||||
reason 42 of 44 printers could not resolve a driver at all.
|
||||
|
||||
The order is most-specific-first, and each step exists for a printer that really
|
||||
is on this floor: a plotter and a card printer need their own driver, everything
|
||||
else takes its make's universal one.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from shopdb.core.models import Vendor, Model
|
||||
from plugins.printers.models import Printer, PrinterDriver
|
||||
from plugins.printers.api.asset_routes import _printer_driver
|
||||
|
||||
|
||||
def _universal():
|
||||
return (PrinterDriver.query
|
||||
.filter(PrinterDriver.modelnumberid.is_(None),
|
||||
PrinterDriver.isactive == True)
|
||||
.order_by(PrinterDriver.name).all())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fleet(db):
|
||||
hp = Vendor(vendor='HP')
|
||||
xerox = Vendor(vendor='Xerox')
|
||||
db.session.add_all([hp, xerox])
|
||||
db.session.flush()
|
||||
|
||||
laserjet = Model(modelnumber='LaserJet M602', vendorid=hp.vendorid)
|
||||
designjet = Model(modelnumber='DesignJet T1700', vendorid=hp.vendorid)
|
||||
db.session.add_all([laserjet, designjet])
|
||||
db.session.flush()
|
||||
|
||||
upd = PrinterDriver(name='HP Universal Print Driver', drivername='HP Universal Printing PS',
|
||||
location=r'\\server\share\hp_upd', vendorid=hp.vendorid, isactive=True)
|
||||
plotter = PrinterDriver(name='HP DesignJet T1700', drivername='HP DesignJet T1700dr V4',
|
||||
location=r'\\server\share\designjet', vendorid=hp.vendorid,
|
||||
modelnumberid=designjet.modelnumberid, isactive=True)
|
||||
gpd = PrinterDriver(name='Xerox Global Print Driver PCL6',
|
||||
drivername='Xerox Global Print Driver PCL6',
|
||||
location=r'\\server\share\xerox', vendorid=xerox.vendorid, isactive=True)
|
||||
db.session.add_all([upd, plotter, gpd])
|
||||
db.session.commit()
|
||||
return {'hp': hp, 'xerox': xerox, 'laserjet': laserjet, 'designjet': designjet,
|
||||
'upd': upd, 'plotter': plotter, 'gpd': gpd}
|
||||
|
||||
|
||||
def test_a_printer_takes_its_makes_universal_driver(fleet):
|
||||
"""The case that covers most of a floor: no per-model row exists, and none
|
||||
should have to."""
|
||||
printer = Printer(vendorid=fleet['hp'].vendorid, modelnumberid=fleet['laserjet'].modelnumberid)
|
||||
assert _printer_driver(printer, _universal()).name == 'HP Universal Print Driver'
|
||||
|
||||
|
||||
def test_a_model_specific_driver_beats_the_universal_one(fleet):
|
||||
"""A plotter is not a LaserJet. If the universal driver won here, the
|
||||
DesignJet would be installed with a driver that cannot drive it."""
|
||||
printer = Printer(vendorid=fleet['hp'].vendorid, modelnumberid=fleet['designjet'].modelnumberid)
|
||||
assert _printer_driver(printer, _universal()).name == 'HP DesignJet T1700'
|
||||
|
||||
|
||||
def test_vendors_do_not_bleed_into_each_other(fleet):
|
||||
"""A Xerox must never resolve to the HP driver, whatever the ordering."""
|
||||
printer = Printer(vendorid=fleet['xerox'].vendorid, modelnumberid=None)
|
||||
assert _printer_driver(printer, _universal()).name == 'Xerox Global Print Driver PCL6'
|
||||
|
||||
|
||||
def test_a_printer_with_no_vendor_resolves_to_nothing(fleet):
|
||||
"""Better nothing than a guess: installing the wrong driver is worse than
|
||||
reporting that a printer has none."""
|
||||
printer = Printer(vendorid=None, modelnumberid=None)
|
||||
assert _printer_driver(printer, _universal()) is None
|
||||
|
||||
|
||||
def test_a_make_with_no_driver_row_resolves_to_nothing(db, fleet):
|
||||
"""A vendor nobody has added a driver for is unresolved, not misresolved."""
|
||||
zebra = Vendor(vendor='Zebra')
|
||||
db.session.add(zebra)
|
||||
db.session.commit()
|
||||
printer = Printer(vendorid=zebra.vendorid, modelnumberid=None)
|
||||
assert _printer_driver(printer, _universal()) is None
|
||||
|
||||
|
||||
def test_the_pre_vendorid_naming_convention_still_resolves(db, fleet):
|
||||
"""A site that populated printerdrivers before the column existed matched on
|
||||
the vendor word in the driver's NAME. That must keep working, or an upgrade
|
||||
silently takes every driver away."""
|
||||
epson = Vendor(vendor='Epson')
|
||||
db.session.add(epson)
|
||||
db.session.flush()
|
||||
db.session.add(PrinterDriver(name='Epson ColorWorks universal', drivername='EPSON TM-C3500',
|
||||
location=r'\\server\share\epson', isactive=True))
|
||||
db.session.commit()
|
||||
# vendor attached, not just vendorid: the legacy path reads the vendor's
|
||||
# NAME, which only exists through the relationship.
|
||||
printer = Printer(vendorid=epson.vendorid, modelnumberid=None)
|
||||
printer.vendor = epson
|
||||
assert _printer_driver(printer, _universal()).name == 'Epson ColorWorks universal'
|
||||
|
||||
|
||||
def test_a_row_that_names_a_vendor_is_not_matched_by_its_text(db, fleet):
|
||||
"""A driver whose vendorid is set and does NOT match must not then be picked
|
||||
up by the name convention: a mis-set vendor would resolve to the wrong
|
||||
package, which is worse than resolving to none."""
|
||||
brother = Vendor(vendor='Brother')
|
||||
db.session.add(brother)
|
||||
db.session.flush()
|
||||
# Named for Brother, but bound to HP by id - the id is the truth.
|
||||
db.session.add(PrinterDriver(name='Brother universal', drivername='Brother Universal',
|
||||
location=r'\\server\share\brother',
|
||||
vendorid=fleet['hp'].vendorid, isactive=True))
|
||||
db.session.commit()
|
||||
printer = Printer(vendorid=brother.vendorid, modelnumberid=None)
|
||||
printer.vendor = brother
|
||||
assert _printer_driver(printer, _universal()) is None
|
||||
Reference in New Issue
Block a user