fix: ignore reverse relations during tenant autofill

This commit is contained in:
2026-08-19 12:40:17 +02:00
parent bd748ef985
commit 4421000409
7 changed files with 30 additions and 14 deletions
+6 -1
View File
@@ -1,6 +1,6 @@
# NetBox Utilities # NetBox Utilities
Plugin für **NetBox 4.6.5 bis 4.6.7** mit zwölf Funktionen: Plugin für **NetBox 4.6.5 bis 4.6.8** mit zwölf Funktionen:
- Jeder Benutzer kann die Menüs der linken Navigation verschieben oder ausblenden. - Jeder Benutzer kann die Menüs der linken Navigation verschieben oder ausblenden.
- Ein Dropdown in der Kopfleiste setzt einen sitzungsweiten Filter für einen Mandanten oder eine Mandantengruppe. - Ein Dropdown in der Kopfleiste setzt einen sitzungsweiten Filter für einen Mandanten oder eine Mandantengruppe.
@@ -17,6 +17,11 @@ Plugin für **NetBox 4.6.5 bis 4.6.7** mit zwölf Funktionen:
Die Navigationseinstellungen sind benutzerbezogen. Die aktive Mandanten- oder Gruppenauswahl wird in der jeweiligen Browser-Session gespeichert. Die Navigationseinstellungen sind benutzerbezogen. Die aktive Mandanten- oder Gruppenauswahl wird in der jeweiligen Browser-Session gespeichert.
Ab Version `0.10.1` ignoriert die automatische Mandantenermittlung
mehrwertige Reverse-Relationen. Das behebt unter NetBox 4.6.8 insbesondere den
Fehler `'RelatedManager' object has no attribute '_meta'` beim Öffnen der
Seite zum Anlegen einer VLAN-Gruppe.
## Kompatibilität ## Kompatibilität
- NetBox `>=4.6.5,<4.7` - NetBox `>=4.6.5,<4.7`
+1 -1
View File
@@ -1,6 +1,6 @@
from netbox.plugins import PluginConfig, get_plugin_config from netbox.plugins import PluginConfig, get_plugin_config
__version__ = "0.10.0" __version__ = "0.10.1"
class NetBoxUtilitiesConfig(PluginConfig): class NetBoxUtilitiesConfig(PluginConfig):
+9 -9
View File
@@ -25,16 +25,20 @@ def tenant_id_from_object(obj, depth=0):
if isinstance(obj, Tenant): if isinstance(obj, Tenant):
return obj.pk return obj.pk
field_names = {field.name for field in obj._meta.get_fields()} meta = getattr(obj, "_meta", None)
if "tenant" in field_names and getattr(obj, "tenant_id", None): if meta is None or not hasattr(meta, "get_fields"):
return None
fields = {field.name: field for field in meta.get_fields()}
if "tenant" in fields and getattr(obj, "tenant_id", None):
return obj.tenant_id return obj.tenant_id
for relation in PARENT_RELATIONS: for relation in PARENT_RELATIONS:
if relation not in field_names: field = fields.get(relation)
if field is None or getattr(field, "one_to_many", False) or getattr(field, "many_to_many", False):
continue continue
try: try:
parent = getattr(obj, relation, None) parent = getattr(obj, relation, None)
except (ObjectDoesNotExist, ValueError): except (AttributeError, ObjectDoesNotExist, ValueError):
continue continue
if tenant_id := tenant_id_from_object(parent, depth + 1): if tenant_id := tenant_id_from_object(parent, depth + 1):
return tenant_id return tenant_id
@@ -76,11 +80,7 @@ def _related_objects(form, field_name):
def _is_cable(instance): def _is_cable(instance):
meta = getattr(instance, "_meta", None) meta = getattr(instance, "_meta", None)
return bool( return bool(meta and getattr(meta, "app_label", None) == "dcim" and getattr(meta, "model_name", None) == "cable")
meta
and getattr(meta, "app_label", None) == "dcim"
and getattr(meta, "model_name", None) == "cable"
)
def _instance_cable_terminations(instance): def _instance_cable_terminations(instance):
+1 -1
View File
@@ -449,7 +449,7 @@ class ReorderRackFrontendTest(SimpleTestCase):
self.assertEqual(template_name, "netbox_utilities/reorder_rack.html") self.assertEqual(template_name, "netbox_utilities/reorder_rack.html")
self.assertEqual(context["reorder_devices"][0]["label"], "LEO-Fritzbox") self.assertEqual(context["reorder_devices"][0]["label"], "LEO-Fritzbox")
self.assertEqual(context["reorder_devices"][0]["grid_width"], 6) self.assertEqual(context["reorder_devices"][0]["grid_width"], 6)
self.assertEqual(context["asset_version"], "0.10.0") self.assertEqual(context["asset_version"], "0.10.1")
self.assertIs(context["reorder_rack_width_data"], get_width_data.return_value) self.assertIs(context["reorder_rack_width_data"], get_width_data.return_value)
get_width_data.assert_called_once() get_width_data.assert_called_once()
self.assertIs(get_width_data.call_args.kwargs["rack"], rack) self.assertIs(get_width_data.call_args.kwargs["rack"], rack)
@@ -50,6 +50,17 @@ class FakeQuerySet:
class TenantAutofillTest(SimpleTestCase): class TenantAutofillTest(SimpleTestCase):
def test_netbox_vlan_group_reverse_scopes_do_not_break_autofill(self):
from ipam.models import VLANGroup
self.assertIsNone(tenant_id_from_object(VLANGroup()))
def test_ignores_reverse_relation_managers_on_scoped_objects(self):
reverse_manager = SimpleNamespace()
vlan_group = FakeObject(site=reverse_manager, rack=reverse_manager)
self.assertIsNone(tenant_id_from_object(vlan_group))
def test_finds_tenant_through_parent_relation(self): def test_finds_tenant_through_parent_relation(self):
rack = FakeObject(tenant_id=42) rack = FakeObject(tenant_id=42)
device = FakeObject(rack=rack) device = FakeObject(rack=rack)
@@ -246,7 +246,7 @@ class TopologyViewsRackWidthTest(SimpleTestCase):
self.assertIn('id="netbox-utilities-topology-rack-width-styles"', html) self.assertIn('id="netbox-utilities-topology-rack-width-styles"', html)
self.assertIn('.rack-device[href="/dcim/devices/334/"]', html) self.assertIn('.rack-device[href="/dcim/devices/334/"]', html)
self.assertIn("left: calc(50% + 3px) !important", html) self.assertIn("left: calc(50% + 3px) !important", html)
self.assertIn("netbox_utilities/topology-rack-width.js?v=0.10.0", html) self.assertIn("netbox_utilities/topology-rack-width.js?v=0.10.1", html)
self.assertIn("left:calc(0% + 3px)!important", html) self.assertIn("left:calc(0% + 3px)!important", html)
self.assertIn("left:calc(50% + 3px)!important", html) self.assertIn("left:calc(50% + 3px)!important", html)
self.assertIn("width:calc(50% - 6px)!important", html) self.assertIn("width:calc(50% - 6px)!important", html)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "netbox-utilities" name = "netbox-utilities"
version = "0.10.0" version = "0.10.1"
description = "Navigation, tenant utilities, connection VLANs, partial-width racks, and bulk operations for NetBox 4.6" description = "Navigation, tenant utilities, connection VLANs, partial-width racks, and bulk operations for NetBox 4.6"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"