diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 714d5a5b0..9aea44229 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -6,6 +6,7 @@ from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models +from django.db.models import Q from django.db.models.expressions import RawSQL from django.urls import reverse from django.utils.encoding import python_2_unicode_compatible @@ -616,6 +617,13 @@ class VLAN(CreatedUpdatedModel, CustomFieldModel): def get_status_class(self): return STATUS_CHOICE_CLASSES[self.status] + def get_members(self): + # Return all interfaces assigned to this VLAN + return Interface.objects.filter( + Q(untagged_vlan_id=self.pk) | + Q(tagged_vlans=self.pk) + ) + @python_2_unicode_compatible class Service(CreatedUpdatedModel): diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index bfc65dacc..f0b05b3db 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import django_tables2 as tables from django_tables2.utils import Accessor +from dcim.models import Interface from tenancy.tables import COL_TENANT from utilities.tables import BaseTable, ToggleColumn from .models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VLANGroup, VRF @@ -138,6 +139,18 @@ VLANGROUP_ACTIONS = """ {% endif %} """ +VLAN_MEMBER_UNTAGGED = """ +{% if record.untagged_vlan_id == vlan.pk %} + +{% endif %} +""" + +VLAN_MEMBER_ACTIONS = """ +{% if perms.dcim.change_interface %} + +{% endif %} +""" + TENANT_LINK = """ {% if record.tenant %} {{ record.tenant }} @@ -361,3 +374,21 @@ class VLANDetailTable(VLANTable): class Meta(VLANTable.Meta): fields = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description') + + +class VLANMemberTable(BaseTable): + parent = tables.LinkColumn(order_by=['device', 'virtual_machine']) + name = tables.Column(verbose_name='Interface') + untagged = tables.TemplateColumn( + template_code=VLAN_MEMBER_UNTAGGED, + orderable=False + ) + actions = tables.TemplateColumn( + template_code=VLAN_MEMBER_ACTIONS, + attrs={'td': {'class': 'text-right'}}, + verbose_name='' + ) + + class Meta(BaseTable.Meta): + model = Interface + fields = ('parent', 'name', 'untagged', 'actions') diff --git a/netbox/ipam/urls.py b/netbox/ipam/urls.py index 383b13d8f..aa7c17a5c 100644 --- a/netbox/ipam/urls.py +++ b/netbox/ipam/urls.py @@ -80,6 +80,7 @@ urlpatterns = [ url(r'^vlans/edit/$', views.VLANBulkEditView.as_view(), name='vlan_bulk_edit'), url(r'^vlans/delete/$', views.VLANBulkDeleteView.as_view(), name='vlan_bulk_delete'), url(r'^vlans/(?P\d+)/$', views.VLANView.as_view(), name='vlan'), + url(r'^vlans/(?P\d+)/members/$', views.VLANMembersView.as_view(), name='vlan_members'), url(r'^vlans/(?P\d+)/edit/$', views.VLANEditView.as_view(), name='vlan_edit'), url(r'^vlans/(?P\d+)/delete/$', views.VLANDeleteView.as_view(), name='vlan_delete'), diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index 18e7ff7e5..5c8ce68b6 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -851,6 +851,38 @@ class VLANView(View): }) +class VLANMembersView(View): + + def get(self, request, pk): + + vlan = get_object_or_404(VLAN.objects.all(), pk=pk) + members = vlan.get_members().select_related('device', 'virtual_machine') + + members_table = tables.VLANMemberTable(members) + # if request.user.has_perm('dcim.change_interface'): + # members_table.columns.show('pk') + + paginate = { + 'klass': EnhancedPaginator, + 'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT) + } + RequestConfig(request, paginate).configure(members_table) + + # Compile permissions list for rendering the object table + # permissions = { + # 'add': request.user.has_perm('ipam.add_ipaddress'), + # 'change': request.user.has_perm('ipam.change_ipaddress'), + # 'delete': request.user.has_perm('ipam.delete_ipaddress'), + # } + + return render(request, 'ipam/vlan_members.html', { + 'vlan': vlan, + 'members_table': members_table, + # 'permissions': permissions, + # 'bulk_querystring': 'vrf_id={}&parent={}'.format(prefix.vrf.pk if prefix.vrf else '0', prefix.prefix), + }) + + class VLANCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'ipam.add_vlan' model = VLAN diff --git a/netbox/templates/ipam/inc/vlan_header.html b/netbox/templates/ipam/inc/vlan_header.html new file mode 100644 index 000000000..bf5d4ccdd --- /dev/null +++ b/netbox/templates/ipam/inc/vlan_header.html @@ -0,0 +1,46 @@ +
+
+ +
+
+
+
+ + + + +
+
+
+
+
+ {% if perms.ipam.change_vlan %} + + + Edit this VLAN + + {% endif %} + {% if perms.ipam.delete_vlan %} + + + Delete this VLAN + + {% endif %} +
+

{% block title %}VLAN {{ vlan.display_name }}{% endblock %}

+{% include 'inc/created_updated.html' with obj=vlan %} + diff --git a/netbox/templates/ipam/vlan.html b/netbox/templates/ipam/vlan.html index ae353e823..971c3359f 100644 --- a/netbox/templates/ipam/vlan.html +++ b/netbox/templates/ipam/vlan.html @@ -1,48 +1,7 @@ {% extends '_base.html' %} {% block content %} -
-
- -
-
-
-
- - - - -
-
-
-
-
- {% if perms.ipam.change_vlan %} - - - Edit this VLAN - - {% endif %} - {% if perms.ipam.delete_vlan %} - - - Delete this VLAN - - {% endif %} -
-

{% block title %}VLAN {{ vlan.display_name }}{% endblock %}

-{% include 'inc/created_updated.html' with obj=vlan %} +{% include 'ipam/inc/vlan_header.html' with active_tab='vlan' %}
diff --git a/netbox/templates/ipam/vlan_members.html b/netbox/templates/ipam/vlan_members.html new file mode 100644 index 000000000..27d5d50f7 --- /dev/null +++ b/netbox/templates/ipam/vlan_members.html @@ -0,0 +1,12 @@ +{% extends '_base.html' %} + +{% block title %}{{ vlan }} - Members{% endblock %} + +{% block content %} + {% include 'ipam/inc/vlan_header.html' with active_tab='members' %} +
+
+ {% include 'utilities/obj_table.html' with table=members_table table_template='panel_table.html' heading='VLAN Members' parent=vlan %} +
+
+{% endblock %}