Assign printers to a machine, and let the PC that drives it inherit them
Printers belong to the bay, not to the box currently driving it. The assignment
goes on the MACHINE asset and reaches whichever PC controls it, so a reimaged or
swapped PC comes back with the right printers and nothing had to be saved off the
old one. The asset register is the backup.
New relationship type usesprinter ("this printer is installed here"), beside the
existing defaultprinter ("which of them is the default"), both seeded and both
given a propagation rail through controls. The rails are consumed at READ time
only: the create-time fan-out skips directional through-types, and controls is
directional, so assigning a printer to a machine does not copy rows onto its PC.
That is what keeps own-beats-inherited possible.
Resolution for a PC is its OWN rows if it has any, otherwise one hop out along
controls to the machines it drives. Whole set at a time, not merged: a PC with
its own assignment is overriding the bay deliberately, and the UI has to say so
or a tech "fixing" a bay by editing the PC will shadow the machine's record and
wonder why they keep disagreeing.
GET /api/printers/for-host/<hostname> is what the convergence client asks every
cycle. Resolved by hostname because the collector upserts PCs by hostname and an
office PC has no machine number. An unknown host, a site without the computers
plugin, and nothing assigned all return an empty set - that is the client's
designed no-op and it must stay indistinguishable from "assigned nothing".
PUT /api/printers/assignments/for-asset/<id> reconciles the whole set in one
call. The endpoint was specified, documented and asserted by three tests, and
never written - the verification pass caught that, with four failures. It
validates the default BEFORE any write, so a rejected request changes nothing;
soft-deletes rows that went away; and REACTIVATES soft-deleted rows rather than
inserting, because the unique constraint spans inactive rows and a blind insert
after an unassign raises IntegrityError on MySQL while passing on SQLite.
One default per asset, enforced here because the schema cannot: the constraint is
(source, target, type), which accepts two different defaults quite happily. Two
active defaults are still reachable through the generic relationships endpoint,
where the oldest silently wins - recorded in the proposal as the next thing to
close.
printerdrivers gains drivername: the exact string the INF declares, which
Add-PrinterDriver matches on and nothing else. Deriving it by parsing INFs on
hundreds of bays is fragile; a human confirming it once is not.
This commit is contained in:
@@ -516,12 +516,33 @@ def seed_reference_data():
|
||||
for at in adr_types:
|
||||
if not _lookup_binary(at['relationshiptype']):
|
||||
db.session.add(RelationshipType(**at))
|
||||
|
||||
# Printer assignment edges. usesprinter = "this printer is installed here",
|
||||
# defaultprinter = which of them is the default. Assignment lives on the
|
||||
# MACHINE asset and reaches whichever PC controls it through the controls
|
||||
# rail seeded below, so a reimaged PC gets its printers back with no
|
||||
# restore step. Created before the rails: _seed_propagation no-ops in
|
||||
# silence when either type is missing.
|
||||
printer_types = [
|
||||
{'relationshiptype': 'usesprinter',
|
||||
'description': 'Asset to a printer installed on it (ADR-001)',
|
||||
'isdirectional': True},
|
||||
{'relationshiptype': 'defaultprinter',
|
||||
'description': 'PC to its default printer (installer preselect, ADR-001)',
|
||||
'isdirectional': True},
|
||||
]
|
||||
for pt in printer_types:
|
||||
if not _lookup_binary(pt['relationshiptype']):
|
||||
db.session.add(RelationshipType(**pt))
|
||||
db.session.flush()
|
||||
|
||||
# Seed `controls` propagation rails as M:N rows. controls -> partof
|
||||
# (declared; directional rail, not consumed yet) and controls -> Dualpath
|
||||
# (consumed; a dual-bay pair shares one controller so both bays carry
|
||||
# controls). Idempotent, resolved by name, skipped if a type is missing.
|
||||
# Seed propagation rails as M:N rows. controls -> partof (declared;
|
||||
# directional rail, not consumed yet) and controls -> Dualpath (consumed;
|
||||
# a dual-bay pair shares one controller so both bays carry controls).
|
||||
# The two printer rails are READ-TIME only: the create-time fan-out skips
|
||||
# directional through-types, and controls is directional, so a PC's own
|
||||
# rows keep beating what it inherits from the machine it controls.
|
||||
# Idempotent, resolved by name, skipped if a type is missing.
|
||||
from shopdb.core.models.relationship import RelationshipTypePropagation
|
||||
|
||||
def _seed_propagation(sourcename, throughname):
|
||||
@@ -541,15 +562,8 @@ def seed_reference_data():
|
||||
|
||||
_seed_propagation('controls', 'partof')
|
||||
_seed_propagation('controls', 'Dualpath')
|
||||
|
||||
# Default-printer link: a PC asset -> its default printer asset. Read by the
|
||||
# printer-installer endpoint (parity with classic apipcdefaultprinter.asp).
|
||||
# Attribute-style edge, not a position rail, so no propagation.
|
||||
if not _lookup_binary('defaultprinter'):
|
||||
db.session.add(RelationshipType(
|
||||
relationshiptype='defaultprinter',
|
||||
description='PC to its default printer (installer preselect, ADR-001)'
|
||||
))
|
||||
_seed_propagation('usesprinter', 'controls')
|
||||
_seed_propagation('defaultprinter', 'controls')
|
||||
|
||||
db.session.commit()
|
||||
click.echo(click.style("Reference data seeded.", fg='green'))
|
||||
|
||||
Reference in New Issue
Block a user