feat: add site prefix column to device and rack tables
- derive prefix from the first word of the site name - display prefix as the first data column - support NetBox 4.6.5 through 4.6.x - add packaging, installation documentation, and tests
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.egg-info/
|
||||||
|
.pytest_cache/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
.venv/
|
||||||
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# NetBox Site Prefix
|
||||||
|
|
||||||
|
Plugin für NetBox 4.6.5, das in den Geräte- und Rack-Listen eine Spalte
|
||||||
|
**Prefix** als erste Datenspalte anzeigt. Der Wert ist immer das erste Wort
|
||||||
|
des zugewiesenen Standortnamens (`Site.name`).
|
||||||
|
|
||||||
|
Beispiele:
|
||||||
|
|
||||||
|
| Standort | Prefix |
|
||||||
|
|---|---|
|
||||||
|
| `Berlin Campus West` | `Berlin` |
|
||||||
|
| `DC01 Frankfurt` | `DC01` |
|
||||||
|
| kein Standort | `—` |
|
||||||
|
|
||||||
|
Das Plugin benötigt keine Datenbankmigration und speichert keine eigenen
|
||||||
|
Daten.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Im Python-Virtualenv von NetBox installieren:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
/opt/netbox/venv/bin/pip install /pfad/zu/Netbox-Prefixes
|
||||||
|
```
|
||||||
|
|
||||||
|
Danach in `configuration.py` aktivieren:
|
||||||
|
|
||||||
|
```python
|
||||||
|
PLUGINS = [
|
||||||
|
# weitere Plugins ...
|
||||||
|
"netbox_site_prefix",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Anschließend NetBox neu starten. `upgrade.sh` ist nicht erforderlich, kann
|
||||||
|
aber wie bei anderen Plugin-Installationen ausgeführt werden.
|
||||||
|
|
||||||
|
Bei bestehenden Benutzern kann eine bereits gespeicherte persönliche
|
||||||
|
Spaltenauswahl Vorrang vor den Standardspalten haben. In diesem Fall in der
|
||||||
|
Geräte- bzw. Rack-Liste **Spalten konfigurieren** öffnen, **Prefix** auswählen
|
||||||
|
und an die erste Position schieben oder die gespeicherte Tabellenkonfiguration
|
||||||
|
zurücksetzen.
|
||||||
|
|
||||||
|
## Kompatibilität
|
||||||
|
|
||||||
|
- NetBox: 4.6.5 bis einschließlich 4.6.x
|
||||||
|
- Python: ab 3.12
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
from netbox.plugins import PluginConfig
|
||||||
|
|
||||||
|
|
||||||
|
class SitePrefixConfig(PluginConfig):
|
||||||
|
name = "netbox_site_prefix"
|
||||||
|
verbose_name = "Site Prefix"
|
||||||
|
author = "LKE"
|
||||||
|
description = "Shows the first word of the site name in device and rack tables."
|
||||||
|
version = "1.0.0"
|
||||||
|
min_version = "4.6.5"
|
||||||
|
max_version = "4.6.99"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
super().ready()
|
||||||
|
|
||||||
|
# Importing the module registers its columns with NetBox.
|
||||||
|
from . import tables # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
config = SitePrefixConfig
|
||||||
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import django_tables2 as tables
|
||||||
|
|
||||||
|
from dcim.tables import DeviceTable, RackTable
|
||||||
|
from utilities.tables import register_table_column
|
||||||
|
|
||||||
|
|
||||||
|
COLUMN_NAME = "site_prefix"
|
||||||
|
|
||||||
|
|
||||||
|
def first_word(value):
|
||||||
|
"""Return the first whitespace-delimited word of a value."""
|
||||||
|
words = str(value or "").split()
|
||||||
|
return words[0] if words else None
|
||||||
|
|
||||||
|
|
||||||
|
class SitePrefixColumn(tables.Column):
|
||||||
|
"""Render the first word of a record's site name."""
|
||||||
|
|
||||||
|
def render(self, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return first_word(value.name)
|
||||||
|
|
||||||
|
|
||||||
|
site_prefix = SitePrefixColumn(
|
||||||
|
accessor=tables.A("site"),
|
||||||
|
verbose_name="Prefix",
|
||||||
|
empty_values=(),
|
||||||
|
orderable=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
register_table_column(site_prefix, COLUMN_NAME, DeviceTable, RackTable)
|
||||||
|
|
||||||
|
|
||||||
|
def _prepend_to_defaults(table):
|
||||||
|
"""Show the registered column first for users without saved preferences."""
|
||||||
|
defaults = tuple(table.Meta.default_columns)
|
||||||
|
table.Meta.default_columns = (
|
||||||
|
"pk",
|
||||||
|
COLUMN_NAME,
|
||||||
|
*(name for name in defaults if name not in {"pk", COLUMN_NAME}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_prepend_to_defaults(DeviceTable)
|
||||||
|
_prepend_to_defaults(RackTable)
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
urlpatterns = []
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=77"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "netbox-site-prefix"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "Adds a site-name prefix column to NetBox device and rack tables."
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
license = "MIT"
|
||||||
|
authors = [
|
||||||
|
{ name = "NetBox Site Prefix contributors" },
|
||||||
|
]
|
||||||
|
classifiers = [
|
||||||
|
"Framework :: Django",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
include = ["netbox_site_prefix*"]
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import ast
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
MODULE = Path(__file__).parents[1] / "netbox_site_prefix" / "tables.py"
|
||||||
|
|
||||||
|
|
||||||
|
def _load_first_word():
|
||||||
|
"""Load the pure helper without requiring a local NetBox installation."""
|
||||||
|
module = ast.parse(MODULE.read_text(encoding="utf-8"))
|
||||||
|
function = next(
|
||||||
|
node for node in module.body
|
||||||
|
if isinstance(node, ast.FunctionDef) and node.name == "first_word"
|
||||||
|
)
|
||||||
|
namespace = {}
|
||||||
|
exec(compile(ast.Module(body=[function], type_ignores=[]), str(MODULE), "exec"), namespace)
|
||||||
|
return namespace["first_word"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_first_word():
|
||||||
|
first_word = _load_first_word()
|
||||||
|
|
||||||
|
assert first_word("Berlin Campus West") == "Berlin"
|
||||||
|
assert first_word(" DC01\tFrankfurt ") == "DC01"
|
||||||
|
|
||||||
|
|
||||||
|
def test_first_word_handles_missing_or_blank_value():
|
||||||
|
first_word = _load_first_word()
|
||||||
|
|
||||||
|
assert first_word(None) is None
|
||||||
|
assert first_word(" ") is None
|
||||||
|
|
||||||
Reference in New Issue
Block a user