Files
shopdb-flask/plugins/network/models/network_device.py
cproudlock 1c6c7ba14b
Some checks failed
CI / backend (push) Successful in 1m39s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 8s
CI / migrations-mysql (push) Failing after 8s
DB review fixes: drop redundant indexes + dead column, add CI MySQL-upgrade job
From the database review (verdict: sound-with-minor-issues). Applies the
actionable findings.

Redundant indexes: five non-unique secondary indexes duplicated a named idx_*
or a unique index on the same column - ix_communications_assetid,
ix_computers_hostname, ix_networkdevices_hostname, ix_printers_hostname (each
shadowing an idx_*), and idx_usb_serial (shadowing the serialnumber unique
index). Removed the redundant index source from the models (column index=True /
the extra db.Index) and added core migration 7d25 dropping the live duplicates.
The unique ix_*_assetid indexes are kept (they enforce assetid uniqueness).

Dead column: usbcheckouts.machineid was a NOT NULL soft-ref to the retired
machines table storing sentinel 0 (ADR-001). Dropped from the model + the
machineid=0 literal in selfhosted checkout; usb plugin migration 0002 drops it
live (downgrade restores it default 0).

Index: notifications.businessunitid (filtered by the shopfloor feed) was
unindexed; added index=True + notifications migration 0002.

CI: new migrations-mysql job proves the real multi-site deploy path - fresh
`flask db upgrade` + per-plugin install on utf8mb4 MySQL from empty, asserting
table count + charset and a clean second-run no-op. The pytest suite only
exercises SQLite create_all(), so a regression in the Alembic chain on MySQL
would otherwise ship undetected.

Verified: fresh core upgrade on a scratch utf8mb4 MySQL builds clean + no-op on
rerun (redundant indexes absent, unique assetid kept); plugin migrations applied
+ verified on the dev DB (machineid gone, bu index present). 953 backend tests
pass; naming + pyflakes green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 09:29:45 -04:00

121 lines
3.2 KiB
Python

"""Network device plugin models."""
from shopdb.api import db, BaseModel
class NetworkDeviceType(BaseModel):
"""
Network device type classification.
Examples: Switch, Router, Access Point, Camera, IDF, Firewall, etc.
"""
__tablename__ = 'networkdevicetypes'
networkdevicetypeid = db.Column(db.Integer, primary_key=True)
networkdevicetype = db.Column(db.String(100), unique=True, nullable=False)
description = db.Column(db.Text)
icon = db.Column(db.String(50), comment='Icon name for UI')
color = db.Column(db.String(20), comment='CSS color for UI/map markers')
def __repr__(self):
return f"<NetworkDeviceType {self.networkdevicetype}>"
class NetworkDevice(BaseModel):
"""
Network device-specific extension data.
Links to core Asset table via assetid.
Stores network device-specific fields like hostname, firmware, ports, etc.
"""
__tablename__ = 'networkdevices'
networkdeviceid = db.Column(db.Integer, primary_key=True)
# Link to core asset
assetid = db.Column(
db.Integer,
db.ForeignKey('assets.assetid', ondelete='CASCADE'),
unique=True,
nullable=False,
index=True
)
# Network device classification
networkdevicetypeid = db.Column(
db.Integer,
db.ForeignKey('networkdevicetypes.networkdevicetypeid'),
nullable=True
)
# Vendor
vendorid = db.Column(
db.Integer,
db.ForeignKey('vendors.vendorid'),
nullable=True
)
# Network identity
hostname = db.Column(
db.String(100),
comment='Network hostname'
)
# Firmware/software version
firmwareversion = db.Column(db.String(100), nullable=True)
# Physical characteristics
portcount = db.Column(
db.Integer,
nullable=True,
comment='Number of ports (for switches)'
)
# Features
ispoe = db.Column(
db.Boolean,
default=False,
comment='Power over Ethernet capable'
)
ismanaged = db.Column(
db.Boolean,
default=False,
comment='Managed device (SNMP, web interface, etc.)'
)
# For IDF/closet locations
rackunit = db.Column(
db.String(20),
nullable=True,
comment='Rack unit position (e.g., U1, U5)'
)
# Relationships
asset = db.relationship(
'Asset',
backref=db.backref('network_device', uselist=False, lazy='joined')
)
networkdevicetype = db.relationship('NetworkDeviceType', backref='networkdevices')
vendor = db.relationship('Vendor', backref='network_devices')
__table_args__ = (
db.Index('idx_netdev_type', 'networkdevicetypeid'),
db.Index('idx_netdev_hostname', 'hostname'),
db.Index('idx_netdev_vendor', 'vendorid'),
)
def __repr__(self):
return f"<NetworkDevice {self.hostname or self.assetid}>"
def to_dict(self):
"""Convert to dictionary with related names."""
result = super().to_dict()
# Add related object names
if self.networkdevicetype:
result['networkdevicetypename'] = self.networkdevicetype.networkdevicetype
if self.vendor:
result['vendorname'] = self.vendor.vendor
return result