From ae4b05d5392d8b1944112c3e8dca543cdc2e49fd Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 24 Sep 2026 15:38:47 +0200 Subject: [PATCH] feat: integrate site prefix column from Netbox-Prefixes Device and rack tables show the first word of the site name as the first data column. Replaces the separate netbox_site_prefix plugin and can be disabled with site_prefix_column_enabled. Co-Authored-By: Claude Opus 5.5 --- README.md | 28 ++++++++++++++ netbox_utilities/__init__.py | 5 +++ netbox_utilities/site_prefix.py | 43 ++++++++++++++++++++++ netbox_utilities/tests/test_site_prefix.py | 36 ++++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 netbox_utilities/site_prefix.py create mode 100644 netbox_utilities/tests/test_site_prefix.py diff --git a/README.md b/README.md index fa9b06e..3ab1108 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ PLUGINS_CONFIG = { "topology_views_rack_width_enabled": True, "tenant_filter_enabled": True, "tenant_required": True, + "site_prefix_column_enabled": True, }, } ``` @@ -246,8 +247,35 @@ Vor Produktivupdates sollte der neue Stand in einer Testumgebung geprüft werden. Ein Backup der NetBox-Datenbank bleibt unabhängig vom Plugin dringend empfohlen. +### Umstieg von NetBox Site Prefix + +Die Prefix-Spalte ist jetzt Teil von NetBox Utilities. Das separate Plugin +`netbox_site_prefix` muss entfernt werden, sonst wird die Spalte doppelt +registriert: + +1. `"netbox_site_prefix"` aus `PLUGINS` in `configuration.py` entfernen. +2. Die Zeile `git+https://git.mrblake.cc/MrBlake/Netbox-Prefixes.git` aus + `/opt/netbox/local_requirements.txt` entfernen. +3. `/opt/netbox/venv/bin/pip uninstall netbox-site-prefix` ausführen und + NetBox neu starten. + +Der Spaltenname `site_prefix` bleibt gleich, gespeicherte Tabellenkonfigurationen +der Benutzer funktionieren daher weiter. + ## Verwendung +### Prefix-Spalte in Geräte- und Rack-Listen + +Die Geräte- und Rack-Listen erhalten eine Spalte **Prefix** als erste +Datenspalte. Der Wert ist das erste Wort des Standortnamens, z. B. `Berlin` +für `Berlin Campus West` oder `DC01` für `DC01 Frankfurt`; ohne Standort +bleibt die Zelle leer. + +Hat ein Benutzer bereits eine eigene Spaltenauswahl gespeichert, hat diese +Vorrang. Dann unter **Spalten konfigurieren** die Spalte **Prefix** auswählen +und nach vorne schieben oder die Tabellenkonfiguration zurücksetzen. Mit +`"site_prefix_column_enabled": False` lässt sich die Spalte abschalten. + ### Vorfilter für die B-Seite einer Verkabelung Beim Anlegen und Bearbeiten eines Kabels erscheint im Abschnitt **B-Seite** diff --git a/netbox_utilities/__init__.py b/netbox_utilities/__init__.py index 0cbcd87..5934e99 100644 --- a/netbox_utilities/__init__.py +++ b/netbox_utilities/__init__.py @@ -21,6 +21,7 @@ class NetBoxUtilitiesConfig(PluginConfig): "tenant_filter_enabled": True, "tenant_required": True, "offline_svg_export_enabled": True, + "site_prefix_column_enabled": True, } middleware = [ "netbox_utilities.middleware.GlobalTenantFilterMiddleware", @@ -37,6 +38,10 @@ class NetBoxUtilitiesConfig(PluginConfig): install_tenant_validation() install_patchpanel_automation() install_rack_width_support() + if get_plugin_config("netbox_utilities", "site_prefix_column_enabled"): + from .site_prefix import install_site_prefix_column + + install_site_prefix_column() if get_plugin_config("netbox_utilities", "connection_vlans_enabled"): from .connection_vlans import install_connection_vlan_support diff --git a/netbox_utilities/site_prefix.py b/netbox_utilities/site_prefix.py new file mode 100644 index 0000000..f361c46 --- /dev/null +++ b/netbox_utilities/site_prefix.py @@ -0,0 +1,43 @@ +import django_tables2 as tables + +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) + + +def prepend_to_default_columns(table): + """Show the prefix column first for users without saved table 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}), + ) + + +def install_site_prefix_column(): + from dcim.tables import DeviceTable, RackTable + from utilities.tables import register_table_column + + column = SitePrefixColumn( + accessor=tables.A("site"), + verbose_name="Prefix", + empty_values=(), + orderable=False, + ) + register_table_column(column, COLUMN_NAME, DeviceTable, RackTable) + prepend_to_default_columns(DeviceTable) + prepend_to_default_columns(RackTable) diff --git a/netbox_utilities/tests/test_site_prefix.py b/netbox_utilities/tests/test_site_prefix.py new file mode 100644 index 0000000..aaa1d94 --- /dev/null +++ b/netbox_utilities/tests/test_site_prefix.py @@ -0,0 +1,36 @@ +from types import SimpleNamespace + +from django.test import SimpleTestCase + +from netbox_utilities.site_prefix import COLUMN_NAME, SitePrefixColumn, first_word, prepend_to_default_columns + + +class SitePrefixTest(SimpleTestCase): + def test_first_word(self): + self.assertEqual(first_word("Berlin Campus West"), "Berlin") + self.assertEqual(first_word(" DC01\tFrankfurt "), "DC01") + + def test_first_word_handles_missing_or_blank_value(self): + self.assertIsNone(first_word(None)) + self.assertIsNone(first_word(" ")) + + def test_column_renders_first_word_of_site_name(self): + column = SitePrefixColumn() + + self.assertEqual(column.render(SimpleNamespace(name="DC01 Frankfurt")), "DC01") + self.assertIsNone(column.render(None)) + + def test_prefix_column_is_first_default_column(self): + table = SimpleNamespace(Meta=SimpleNamespace(default_columns=("pk", "name", COLUMN_NAME, "status"))) + + prepend_to_default_columns(table) + + self.assertEqual(table.Meta.default_columns, ("pk", COLUMN_NAME, "name", "status")) + + def test_device_and_rack_tables_offer_prefix_column(self): + from dcim.tables import DeviceTable, RackTable + + self.assertIn(COLUMN_NAME, DeviceTable.base_columns) + self.assertIn(COLUMN_NAME, RackTable.base_columns) + self.assertEqual(DeviceTable.Meta.default_columns[:2], ("pk", COLUMN_NAME)) + self.assertEqual(RackTable.Meta.default_columns[:2], ("pk", COLUMN_NAME))