Every network device on this fleet already follows one convention, applied by hand: AP-<name>, SW-<name>, SVR-<name>, IDF-<name>. 45 records, no exceptions. The create form demanded the asset number anyway, so the same value was typed twice and the convention held only as long as everyone remembered it. The prefix now lives on the device type, and a blank asset number is generated as <PREFIX>-<name>. Left explicit, an asset number always wins: a device carrying a real identifier of its own - a vendor tag, a controller name, a serial - keeps it. That is the platform rule, adopt where an identifier exists and derive only where none does. The prefix is NOT 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 it created. It is nullable, so a type that wants no prefix generates the bare name rather than needing one invented. Names are sanitised before they reach a business key - the existing data already shows why, with IDF-Telco-Demarc-#1 carrying a '#' into an identifier. An existing prefix is never stacked: IDF-03 under type IDF stays IDF-03.
71 lines
2.6 KiB
Python
71 lines
2.6 KiB
Python
"""Generate a network device's asset number from its type prefix and name.
|
|
|
|
Every network device on this fleet already follows one convention, applied by
|
|
hand: AP-<name>, SW-<name>, SVR-<name>, IDF-<name>. This turns that into
|
|
something the create path does, so the same value is not typed twice.
|
|
|
|
Generation only fills a BLANK asset number. A device that carries a real
|
|
identifier of its own - a vendor tag, a controller name, a serial - keeps it.
|
|
That is the platform rule: adopt an external identifier where one exists, derive
|
|
one only where none does.
|
|
"""
|
|
|
|
import re
|
|
|
|
|
|
# Characters that must not reach a unique business key. assetnumber ends up in
|
|
# URLs and in joins other subsystems make, and the existing data already shows
|
|
# why this matters: IDF-Telco-Demarc-#1 came from the name 'Telco Demarc #1',
|
|
# carrying a '#' into an identifier.
|
|
_SEPARATORS = re.compile(r'[\s_/\\]+')
|
|
_ILLEGAL = re.compile(r'[^A-Za-z0-9.-]')
|
|
_RUNS = re.compile(r'-{2,}')
|
|
|
|
|
|
def sanitize(value):
|
|
"""A name reduced to something safe to use as an identifier.
|
|
|
|
Whitespace and slashes become single dashes; anything outside
|
|
letters/digits/dot/dash is dropped rather than transliterated, because a
|
|
guessed transliteration in a business key is worse than a shorter one.
|
|
"""
|
|
text = (value or '').strip()
|
|
if not text:
|
|
return ''
|
|
text = _SEPARATORS.sub('-', text)
|
|
text = _ILLEGAL.sub('', text)
|
|
text = _RUNS.sub('-', text)
|
|
return text.strip('-')
|
|
|
|
|
|
def generate(prefix, name):
|
|
"""'<PREFIX>-<name>', or the bare name when the type has no prefix.
|
|
|
|
Returns '' when there is nothing to build from, so the caller can fall back
|
|
to demanding an explicit value rather than inventing one.
|
|
|
|
Does NOT stack an existing prefix: a name already starting with the prefix
|
|
(IDF-03 under type IDF) is returned as-is, because IDF-IDF-03 is nobody's
|
|
intent. Matched case-insensitively on the prefix plus its dash.
|
|
"""
|
|
cleanname = sanitize(name)
|
|
if not cleanname:
|
|
return ''
|
|
cleanprefix = sanitize(prefix).upper()
|
|
if not cleanprefix:
|
|
return cleanname
|
|
if cleanname.upper().startswith(cleanprefix + '-'):
|
|
return cleanname
|
|
return '{}-{}'.format(cleanprefix, cleanname)
|
|
|
|
|
|
def generate_for_type(networkdevicetypeid, name):
|
|
"""Same, looking the prefix up from the device type. '' if no type given."""
|
|
if not networkdevicetypeid:
|
|
return sanitize(name)
|
|
from shopdb.api import db
|
|
from ..models import NetworkDeviceType
|
|
|
|
devicetype = db.session.get(NetworkDeviceType, networkdevicetypeid)
|
|
return generate(devicetype.prefix if devicetype else None, name)
|