diff --git a/docs/release-notes/version-2.9.md b/docs/release-notes/version-2.9.md index 8a65b5fb4..fa672a51e 100644 --- a/docs/release-notes/version-2.9.md +++ b/docs/release-notes/version-2.9.md @@ -13,6 +13,7 @@ ### Bug Fixes * [#5050](https://github.com/netbox-community/netbox/issues/5050) - Fix potential failure on `0016_replicate_interfaces` schema migration from old release +* [#5075](https://github.com/netbox-community/netbox/issues/5075) - Include a VLAN membership view for VM interfaces * [#5105](https://github.com/netbox-community/netbox/issues/5105) - Validation should fail when reassigning a primary IP from device to VM * [#5109](https://github.com/netbox-community/netbox/issues/5109) - Fix representation of custom choice field values for webhook data * [#5108](https://github.com/netbox-community/netbox/issues/5108) - Fix execution of reports via CLI diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index c7955bcfa..a69d235be 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -985,13 +985,20 @@ class VLAN(ChangeLoggedModel, CustomFieldModel): def get_status_class(self): return self.STATUS_CLASS_MAP[self.status] - def get_members(self): - # Return all interfaces assigned to this VLAN + def get_interfaces(self): + # Return all device interfaces assigned to this VLAN return Interface.objects.filter( Q(untagged_vlan_id=self.pk) | Q(tagged_vlans=self.pk) ).distinct() + def get_vminterfaces(self): + # Return all VM interfaces assigned to this VLAN + return VMInterface.objects.filter( + Q(untagged_vlan_id=self.pk) | + Q(tagged_vlans=self.pk) + ).distinct() + @extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks') class Service(ChangeLoggedModel, CustomFieldModel): diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index afdeb5515..ba09475b0 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -4,6 +4,7 @@ from django_tables2.utils import Accessor from dcim.models import Interface from tenancy.tables import COL_TENANT from utilities.tables import BaseTable, BooleanColumn, ButtonsColumn, TagColumn, ToggleColumn +from virtualization.models import VMInterface from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF RIR_UTILIZATION = """ @@ -124,9 +125,11 @@ VLANGROUP_ADD_VLAN = """ {% endwith %} """ -VLAN_MEMBER_UNTAGGED = """ +VLAN_MEMBER_TAGGED = """ {% if record.untagged_vlan_id == vlan.pk %} - + +{% else %} + {% endif %} """ @@ -553,15 +556,15 @@ class VLANDetailTable(VLANTable): default_columns = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description') -class VLANMemberTable(BaseTable): - parent = tables.LinkColumn( - order_by=['device', 'virtual_machine'] - ) +class VLANMembersTable(BaseTable): + """ + Base table for Interface and VMInterface assignments + """ name = tables.LinkColumn( verbose_name='Interface' ) - untagged = tables.TemplateColumn( - template_code=VLAN_MEMBER_UNTAGGED, + tagged = tables.TemplateColumn( + template_code=VLAN_MEMBER_TAGGED, orderable=False ) actions = tables.TemplateColumn( @@ -570,9 +573,21 @@ class VLANMemberTable(BaseTable): verbose_name='' ) + +class VLANDevicesTable(VLANMembersTable): + device = tables.LinkColumn() + class Meta(BaseTable.Meta): model = Interface - fields = ('parent', 'name', 'untagged', 'actions') + fields = ('device', 'name', 'tagged', 'actions') + + +class VLANVirtualMachinesTable(VLANMembersTable): + virtual_machine = tables.LinkColumn() + + class Meta(BaseTable.Meta): + model = VMInterface + fields = ('virtual_machine', 'name', 'tagged', 'actions') class InterfaceVLANTable(BaseTable): diff --git a/netbox/ipam/urls.py b/netbox/ipam/urls.py index b2080c0a8..533335816 100644 --- a/netbox/ipam/urls.py +++ b/netbox/ipam/urls.py @@ -90,7 +90,8 @@ urlpatterns = [ path('vlans/edit/', views.VLANBulkEditView.as_view(), name='vlan_bulk_edit'), path('vlans/delete/', views.VLANBulkDeleteView.as_view(), name='vlan_bulk_delete'), path('vlans//', views.VLANView.as_view(), name='vlan'), - path('vlans//members/', views.VLANMembersView.as_view(), name='vlan_members'), + path('vlans//interfaces/', views.VLANInterfacesView.as_view(), name='vlan_interfaces'), + path('vlans//vm-interfaces/', views.VLANVMInterfacesView.as_view(), name='vlan_vminterfaces'), path('vlans//edit/', views.VLANEditView.as_view(), name='vlan_edit'), path('vlans//delete/', views.VLANDeleteView.as_view(), name='vlan_delete'), path('vlans//changelog/', ObjectChangeLogView.as_view(), name='vlan_changelog', kwargs={'model': VLAN}), diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 1f0e2607e..c3eac0fd7 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -749,15 +749,13 @@ class VLANView(ObjectView): }) -class VLANMembersView(ObjectView): +class VLANInterfacesView(ObjectView): queryset = VLAN.objects.all() def get(self, request, pk): - vlan = get_object_or_404(self.queryset, pk=pk) - members = vlan.get_members().restrict(request.user, 'view').prefetch_related('device', 'virtual_machine') - - members_table = tables.VLANMemberTable(members) + interfaces = vlan.get_interfaces().prefetch_related('device') + members_table = tables.VLANDevicesTable(interfaces) paginate = { 'paginator_class': EnhancedPaginator, @@ -765,10 +763,31 @@ class VLANMembersView(ObjectView): } RequestConfig(request, paginate).configure(members_table) - return render(request, 'ipam/vlan_members.html', { + return render(request, 'ipam/vlan_interfaces.html', { 'vlan': vlan, 'members_table': members_table, - 'active_tab': 'members', + 'active_tab': 'interfaces', + }) + + +class VLANVMInterfacesView(ObjectView): + queryset = VLAN.objects.all() + + def get(self, request, pk): + vlan = get_object_or_404(self.queryset, pk=pk) + interfaces = vlan.get_vminterfaces().prefetch_related('virtual_machine') + members_table = tables.VLANVirtualMachinesTable(interfaces) + + paginate = { + 'paginator_class': EnhancedPaginator, + 'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT) + } + RequestConfig(request, paginate).configure(members_table) + + return render(request, 'ipam/vlan_vminterfaces.html', { + 'vlan': vlan, + 'members_table': members_table, + 'active_tab': 'vminterfaces', }) diff --git a/netbox/templates/ipam/vlan.html b/netbox/templates/ipam/vlan.html index ec1f94b51..d9cb0c66c 100644 --- a/netbox/templates/ipam/vlan.html +++ b/netbox/templates/ipam/vlan.html @@ -52,8 +52,11 @@ - + {% if perms.extras.view_objectchange %}