From f8213bd95b1a006854508ef59b35eb86f3fc7c58 Mon Sep 17 00:00:00 2001 From: Jan Krupa Date: Tue, 30 Sep 2025 08:22:48 +0000 Subject: [PATCH 1/7] feat: Add BGP sessions tab to IP address view - Implement IPAddressBGPSessionsView to display BGP sessions associated with an IP address - Add tab with badge showing session count and permission check --- netbox_bgp/template_content.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/netbox_bgp/template_content.py b/netbox_bgp/template_content.py index ba76c8d..2183699 100644 --- a/netbox_bgp/template_content.py +++ b/netbox_bgp/template_content.py @@ -35,6 +35,37 @@ def get_children(self, request, parent): return BGPSession.objects.filter(device=parent) +@register_model_view(IPAddress, name="bgp-sessions", path="bgp-sessions") +class IPAddressBGPSessionsView(generic.ObjectChildrenView): + """View to display BGP sessions associated with an IP address.""" + + queryset = IPAddress.objects.all() + child_model = BGPSession + filterset = BGPSessionFilterSet + table = BGPSessionTable + template_name = "generic/object_children.html" + hide_if_empty = False + + @staticmethod + def _get_ip_bgp_sessions(ip_address: IPAddress) -> QuerySet[BGPSession]: + """Helper to get BGP sessions related to an IP address.""" + return BGPSession.objects.filter( + Q(local_address=ip_address) | Q(remote_address=ip_address) + ).distinct() + + tab = ViewTab( + label="BGP Sessions", + badge=lambda obj: IPAddressBGPSessionsView._get_ip_bgp_sessions(obj).count(), + permission="netbox_bgp.view_bgpsession", + ) + + def get_children( + self, request: HttpRequest, parent: IPAddress + ) -> QuerySet[BGPSession]: + """Get BGP sessions where the IP address is either the local or remote address.""" + return IPAddressBGPSessionsView._get_ip_bgp_sessions(parent) + + class DeviceBGPSession(PluginTemplateExtension): models = ("dcim.device",) From f18e3f0c32d56cef79feabcea3d23d1517363779 Mon Sep 17 00:00:00 2001 From: Jan Krupa Date: Tue, 30 Sep 2025 08:55:00 +0000 Subject: [PATCH 2/7] feat: Add tab option for device BGP sessions display - Update README.md to document the new "tab" value for device_ext_page config, enabling BGP sessions as a tab on device view pages - Implement DeviceBGPSessionsView in template_content.py as an ObjectChildrenView for tab-based display - Conditionally register the view only when device_ext_page is set to "tab", otherwise use PluginTemplateExtension for inline display (left/right/full_width) - Add necessary imports for Device model and settings to support the new functionality --- netbox_bgp/template_content.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/netbox_bgp/template_content.py b/netbox_bgp/template_content.py index 2183699..f4dd873 100644 --- a/netbox_bgp/template_content.py +++ b/netbox_bgp/template_content.py @@ -66,6 +66,38 @@ def get_children( return IPAddressBGPSessionsView._get_ip_bgp_sessions(parent) +# Register only when device_ext_page is set to 'tab'; +class DeviceBGPSessionsView(generic.ObjectChildrenView): + """View to display BGP sessions associated with a device.""" + + queryset = Device.objects.all() + child_model = BGPSession + filterset = BGPSessionFilterSet + table = BGPSessionTable + template_name = "generic/object_children.html" + hide_if_empty = False + + tab = ViewTab( + label="BGP Sessions", + badge=lambda obj: BGPSession.objects.filter(device=obj).count(), + permission="netbox_bgp.view_bgpsession", + ) + + def get_children( + self, request: HttpRequest, parent: Device + ) -> QuerySet[BGPSession]: + """Get BGP sessions for the device.""" + return BGPSession.objects.filter(device=parent) + + +# Register the BGP sessions tab view only if device_ext_page is set to 'tab'; +# otherwise, use the PluginTemplateExtension for inline display (left, right, full_width). +if settings.PLUGINS_CONFIG.get("netbox_bgp", {}).get("device_ext_page") == "tab": + DeviceBGPSessionsView = register_model_view( + Device, name="bgp-sessions", path="bgp-sessions" + )(DeviceBGPSessionsView) + + class DeviceBGPSession(PluginTemplateExtension): models = ("dcim.device",) From a413307c8dcb23c21fce31efbd1e68089809f932 Mon Sep 17 00:00:00 2001 From: Jan Krupa Date: Tue, 30 Sep 2025 09:12:40 +0000 Subject: [PATCH 3/7] feat: Add BGP sessions tab to site view - Implement SiteBGPSessionsView in netbox_bgp/template_content.py as an ObjectChildrenView for displaying BGP sessions associated with a site --- netbox_bgp/template_content.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/netbox_bgp/template_content.py b/netbox_bgp/template_content.py index f4dd873..8c8c7ae 100644 --- a/netbox_bgp/template_content.py +++ b/netbox_bgp/template_content.py @@ -1,4 +1,4 @@ -from dcim.models import Device +from dcim.models import Device, Site from django.conf import settings from netbox.plugins import PluginTemplateExtension from netbox.views.generic import ObjectChildrenView @@ -66,6 +66,28 @@ def get_children( return IPAddressBGPSessionsView._get_ip_bgp_sessions(parent) +@register_model_view(Site, name="bgp-sessions", path="bgp-sessions") +class SiteBGPSessionsView(generic.ObjectChildrenView): + """View to display BGP sessions associated with a site.""" + + queryset = Site.objects.all() + child_model = BGPSession + filterset = BGPSessionFilterSet + table = BGPSessionTable + template_name = "generic/object_children.html" + hide_if_empty = False + + tab = ViewTab( + label="BGP Sessions", + badge=lambda obj: BGPSession.objects.filter(site=obj).count(), + permission="netbox_bgp.view_bgpsession", + ) + + def get_children(self, request: HttpRequest, parent: Site) -> QuerySet[BGPSession]: + """Get BGP sessions for the site.""" + return BGPSession.objects.filter(site=parent) + + # Register only when device_ext_page is set to 'tab'; class DeviceBGPSessionsView(generic.ObjectChildrenView): """View to display BGP sessions associated with a device.""" From 0bf8f6e1da5094f683f3e24458b1b4234ee85e60 Mon Sep 17 00:00:00 2001 From: Jan Krupa Date: Tue, 30 Sep 2025 09:18:10 +0000 Subject: [PATCH 4/7] feat: Add BGP sessions tab to tenant view - Implement TenantBGPSessionsView in [netbox_bgp/template_content.py]template_content.py ) as an ObjectChildrenView for displaying BGP sessions associated with a tenant --- netbox_bgp/template_content.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/netbox_bgp/template_content.py b/netbox_bgp/template_content.py index 8c8c7ae..9e48ce3 100644 --- a/netbox_bgp/template_content.py +++ b/netbox_bgp/template_content.py @@ -88,6 +88,30 @@ def get_children(self, request: HttpRequest, parent: Site) -> QuerySet[BGPSessio return BGPSession.objects.filter(site=parent) +@register_model_view(Tenant, name="bgp-sessions", path="bgp-sessions") +class TenantBGPSessionsView(generic.ObjectChildrenView): + """View to display BGP sessions associated with a tenant.""" + + queryset = Tenant.objects.all() + child_model = BGPSession + filterset = BGPSessionFilterSet + table = BGPSessionTable + template_name = "generic/object_children.html" + hide_if_empty = False + + tab = ViewTab( + label="BGP Sessions", + badge=lambda obj: BGPSession.objects.filter(tenant=obj).count(), + permission="netbox_bgp.view_bgpsession", + ) + + def get_children( + self, request: HttpRequest, parent: Tenant + ) -> QuerySet[BGPSession]: + """Get BGP sessions for the tenant.""" + return BGPSession.objects.filter(tenant=parent) + + # Register only when device_ext_page is set to 'tab'; class DeviceBGPSessionsView(generic.ObjectChildrenView): """View to display BGP sessions associated with a device.""" From 6c18a285d25960c27312fab7c3d643c82848d080 Mon Sep 17 00:00:00 2001 From: Jan Krupa Date: Tue, 30 Sep 2025 09:21:03 +0000 Subject: [PATCH 5/7] feat: Add BGP sessions tab to virtual machine view - Implement VirtualMachineBGPSessionsView in [netbox_bgp/template_content.py]template_content.py ) as an ObjectChildrenView for displaying BGP sessions associated with a virtual machine --- netbox_bgp/template_content.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/netbox_bgp/template_content.py b/netbox_bgp/template_content.py index 9e48ce3..18846dc 100644 --- a/netbox_bgp/template_content.py +++ b/netbox_bgp/template_content.py @@ -3,6 +3,7 @@ from netbox.plugins import PluginTemplateExtension from netbox.views.generic import ObjectChildrenView from utilities.views import ViewTab, register_model_view +from virtualization.models import VirtualMachine from .filtersets import BGPSessionFilterSet from .models import BGPSession @@ -112,6 +113,30 @@ def get_children( return BGPSession.objects.filter(tenant=parent) +@register_model_view(VirtualMachine, name="bgp-sessions", path="bgp-sessions") +class VirtualMachineBGPSessionsView(generic.ObjectChildrenView): + """View to display BGP sessions associated with a virtual machine.""" + + queryset = VirtualMachine.objects.all() + child_model = BGPSession + filterset = BGPSessionFilterSet + table = BGPSessionTable + template_name = "generic/object_children.html" + hide_if_empty = False + + tab = ViewTab( + label="BGP Sessions", + badge=lambda obj: BGPSession.objects.filter(virtualmachine=obj).count(), + permission="netbox_bgp.view_bgpsession", + ) + + def get_children( + self, request: HttpRequest, parent: VirtualMachine + ) -> QuerySet[BGPSession]: + """Get BGP sessions for the virtual machine.""" + return BGPSession.objects.filter(virtualmachine=parent) + + # Register only when device_ext_page is set to 'tab'; class DeviceBGPSessionsView(generic.ObjectChildrenView): """View to display BGP sessions associated with a device.""" From d770996e31760eb118546da7916fb95599bbbac1 Mon Sep 17 00:00:00 2001 From: Jan Krupa Date: Tue, 30 Sep 2025 09:26:35 +0000 Subject: [PATCH 6/7] feat: Add BGP sessions tab to ASN view - Implement ASNBGPSessionsView in [netbox_bgp/template_content.py]template_content.py ) as an ObjectChildrenView for displaying BGP sessions associated with an ASN - Include tab with badge showing count of related BGP sessions (where ASN is local or remote AS) and permission check for viewing --- netbox_bgp/template_content.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/netbox_bgp/template_content.py b/netbox_bgp/template_content.py index 18846dc..a557cd7 100644 --- a/netbox_bgp/template_content.py +++ b/netbox_bgp/template_content.py @@ -137,6 +137,33 @@ def get_children( return BGPSession.objects.filter(virtualmachine=parent) +@register_model_view(ASN, name="bgp-sessions", path="bgp-sessions") +class ASNBGPSessionsView(generic.ObjectChildrenView): + """View to display BGP sessions associated with an ASN.""" + + queryset = ASN.objects.all() + child_model = BGPSession + filterset = BGPSessionFilterSet + table = BGPSessionTable + template_name = "generic/object_children.html" + hide_if_empty = False + + @staticmethod + def _get_asn_bgp_sessions(asn: ASN) -> QuerySet[BGPSession]: + """Helper to get BGP sessions related to an ASN.""" + return BGPSession.objects.filter(Q(local_as=asn) | Q(remote_as=asn)).distinct() + + tab = ViewTab( + label="BGP Sessions", + badge=lambda obj: ASNBGPSessionsView._get_asn_bgp_sessions(obj).count(), + permission="netbox_bgp.view_bgpsession", + ) + + def get_children(self, request: HttpRequest, parent: ASN) -> QuerySet[BGPSession]: + """Get BGP sessions where the ASN is either the local or remote AS.""" + return ASNBGPSessionsView._get_asn_bgp_sessions(parent) + + # Register only when device_ext_page is set to 'tab'; class DeviceBGPSessionsView(generic.ObjectChildrenView): """View to display BGP sessions associated with a device.""" From 77b88aab895bd3860d77bfaae75418e9ce865408 Mon Sep 17 00:00:00 2001 From: Jan Krupa Date: Tue, 30 Sep 2025 10:22:01 +0000 Subject: [PATCH 7/7] feat: Add BGP Sessions tab to Interface detail pages - Add InterfaceBGPSessionsView class with tab registration for displaying BGP sessions associated with interfaces via their IP addresses - Fix GenericForeignKey filtering by using ContentType and assigned_object_type/id fields instead of direct assigned_object lookup - Add necessary imports for Interface and ContentType models --- netbox_bgp/template_content.py | 48 +++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/netbox_bgp/template_content.py b/netbox_bgp/template_content.py index a557cd7..19f4b87 100644 --- a/netbox_bgp/template_content.py +++ b/netbox_bgp/template_content.py @@ -1,7 +1,12 @@ -from dcim.models import Device, Site +from dcim.models import Device, Interface, Site from django.conf import settings +from django.contrib.contenttypes.models import ContentType +from django.db.models import Q, QuerySet +from django.http import HttpRequest +from ipam.models import ASN, IPAddress from netbox.plugins import PluginTemplateExtension -from netbox.views.generic import ObjectChildrenView +from netbox.views import generic +from tenancy.models import Tenant from utilities.views import ViewTab, register_model_view from virtualization.models import VirtualMachine @@ -16,7 +21,7 @@ if config.get("device_ext_page", "") == "tab": @register_model_view(Device, name="bgp_sessions", path="bgp-sessions") - class DeviceBGPSessionTabView(ObjectChildrenView): + class DeviceBGPSessionTabView(generic.ObjectChildrenView): """View to display BGP sessions for this device in a tab.""" queryset = Device.objects.all() @@ -164,6 +169,43 @@ def get_children(self, request: HttpRequest, parent: ASN) -> QuerySet[BGPSession return ASNBGPSessionsView._get_asn_bgp_sessions(parent) +@register_model_view(Interface, name="bgp-sessions", path="bgp-sessions") +class InterfaceBGPSessionsView(generic.ObjectChildrenView): + """View to display BGP sessions associated with an interface.""" + + queryset = Interface.objects.all() + child_model = BGPSession + filterset = BGPSessionFilterSet + table = BGPSessionTable + template_name = "generic/object_children.html" + hide_if_empty = False + + @staticmethod + def _get_interface_bgp_sessions(interface: Interface) -> QuerySet[BGPSession]: + """Helper to get BGP sessions related to an interface via its IP addresses.""" + ct = ContentType.objects.get_for_model(Interface) + ips = IPAddress.objects.filter( + assigned_object_type=ct, assigned_object_id=interface.pk + ) + return BGPSession.objects.filter( + Q(local_address__in=ips) | Q(remote_address__in=ips) + ).distinct() + + tab = ViewTab( + label="BGP Sessions", + badge=lambda obj: InterfaceBGPSessionsView._get_interface_bgp_sessions( + obj + ).count(), + permission="netbox_bgp.view_bgpsession", + ) + + def get_children( + self, request: HttpRequest, parent: Interface + ) -> QuerySet[BGPSession]: + """Get BGP sessions for the interface.""" + return InterfaceBGPSessionsView._get_interface_bgp_sessions(parent) + + # Register only when device_ext_page is set to 'tab'; class DeviceBGPSessionsView(generic.ObjectChildrenView): """View to display BGP sessions associated with a device."""