"""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') # Asset-number prefix for devices of this type: AP-, SW-. # NULLABLE on purpose - a type with no prefix generates the bare name, so # nobody has to invent one for a type that does not want it. # # It cannot be derived from the type NAME: 'Access Point' and 'Access Panel' # both initialise to AP, and assetnumber is unique, so the second type would # collide with the first on every device. prefix = db.Column( db.String(12), comment='Asset-number prefix for this type (AP, SW, SVR). Blank = none') def __repr__(self): return f"" 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 modelnumberid = db.Column( db.Integer, db.ForeignKey('models.modelnumberid'), nullable=True, comment='Catalog model, which is where the device photo comes from' ) 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') model = db.relationship('Model', 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"" 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 # Same shape as machines, PCs and printers: the detail page's hero image # binds to imageurl, and it comes from the catalog model, not the device. if self.model: result['modelname'] = self.model.modelnumber if self.model.imageurl: result['imageurl'] = self.model.imageurl # The catalog model already knows its maker, so an asset that has a # model but no vendor of its own is showing a blank the database can # fill. Flagged rather than merged silently: the edit form still has # an empty vendor box, and a page implying otherwise would be lying. if not self.vendor and self.model.vendor: result['vendorname'] = self.model.vendor.vendor result['vendorfrommodel'] = True # Exposed under its OWN name. modeltypes is the catalog-wide list # covering every kind of asset, so it is not interchangeable with # this asset's own type and must never be substituted for it. if self.model.modeltype: result['modeltypename'] = self.model.modeltype.modeltype return result