Просмотр исходного кода

Merge pull request #7297 from netbox-community/7295-tables-cleanup

Closes #7295: General cleanup of table classes
Jeremy Stretch 4 лет назад
Родитель
Сommit
383cdb5340

+ 8 - 0
netbox/circuits/tables.py

@@ -6,6 +6,14 @@ from utilities.tables import BaseTable, ButtonsColumn, ChoiceFieldColumn, Markdo
 from .models import *
 from .models import *
 
 
 
 
+__all__ = (
+    'CircuitTable',
+    'CircuitTypeTable',
+    'ProviderTable',
+    'ProviderNetworkTable',
+)
+
+
 CIRCUITTERMINATION_LINK = """
 CIRCUITTERMINATION_LINK = """
 {% if value.site %}
 {% if value.site %}
   <a href="{{ value.site.get_absolute_url }}">{{ value.site }}</a>
   <a href="{{ value.site.get_absolute_url }}">{{ value.site }}</a>

+ 3 - 12
netbox/circuits/views.py

@@ -34,9 +34,7 @@ class ProviderView(generic.ObjectView):
         ).prefetch_related(
         ).prefetch_related(
             'type', 'tenant', 'terminations__site'
             'type', 'tenant', 'terminations__site'
         )
         )
-
-        circuits_table = tables.CircuitTable(circuits)
-        circuits_table.columns.hide('provider')
+        circuits_table = tables.CircuitTable(circuits, exclude=('provider',))
         paginate_table(circuits_table, request)
         paginate_table(circuits_table, request)
 
 
         return {
         return {
@@ -97,10 +95,7 @@ class ProviderNetworkView(generic.ObjectView):
         ).prefetch_related(
         ).prefetch_related(
             'type', 'tenant', 'terminations__site'
             'type', 'tenant', 'terminations__site'
         )
         )
-
         circuits_table = tables.CircuitTable(circuits)
         circuits_table = tables.CircuitTable(circuits)
-        circuits_table.columns.hide('termination_a')
-        circuits_table.columns.hide('termination_z')
         paginate_table(circuits_table, request)
         paginate_table(circuits_table, request)
 
 
         return {
         return {
@@ -153,12 +148,8 @@ class CircuitTypeView(generic.ObjectView):
     queryset = CircuitType.objects.all()
     queryset = CircuitType.objects.all()
 
 
     def get_extra_context(self, request, instance):
     def get_extra_context(self, request, instance):
-        circuits = Circuit.objects.restrict(request.user, 'view').filter(
-            type=instance
-        )
-
-        circuits_table = tables.CircuitTable(circuits)
-        circuits_table.columns.hide('type')
+        circuits = Circuit.objects.restrict(request.user, 'view').filter(type=instance)
+        circuits_table = tables.CircuitTable(circuits, exclude=('type',))
         paginate_table(circuits_table, request)
         paginate_table(circuits_table, request)
 
 
         return {
         return {

+ 1 - 0
netbox/dcim/tables/devices.py

@@ -18,6 +18,7 @@ from .template_code import (
 )
 )
 
 
 __all__ = (
 __all__ = (
+    'BaseInterfaceTable',
     'ConsolePortTable',
     'ConsolePortTable',
     'ConsoleServerPortTable',
     'ConsoleServerPortTable',
     'DeviceBayTable',
     'DeviceBayTable',

+ 2 - 13
netbox/dcim/tables/racks.py

@@ -10,7 +10,6 @@ from utilities.tables import (
 
 
 __all__ = (
 __all__ = (
     'RackTable',
     'RackTable',
-    'RackDetailTable',
     'RackReservationTable',
     'RackReservationTable',
     'RackRoleTable',
     'RackRoleTable',
 )
 )
@@ -56,17 +55,6 @@ class RackTable(BaseTable):
         template_code="{{ record.u_height }}U",
         template_code="{{ record.u_height }}U",
         verbose_name='Height'
         verbose_name='Height'
     )
     )
-
-    class Meta(BaseTable.Meta):
-        model = Rack
-        fields = (
-            'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'serial', 'asset_tag', 'type',
-            'width', 'u_height',
-        )
-        default_columns = ('pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'u_height')
-
-
-class RackDetailTable(RackTable):
     comments = MarkdownColumn()
     comments = MarkdownColumn()
     device_count = LinkedCountColumn(
     device_count = LinkedCountColumn(
         viewname='dcim:device_list',
         viewname='dcim:device_list',
@@ -85,7 +73,8 @@ class RackDetailTable(RackTable):
         url_name='dcim:rack_list'
         url_name='dcim:rack_list'
     )
     )
 
 
-    class Meta(RackTable.Meta):
+    class Meta(BaseTable.Meta):
+        model = Rack
         fields = (
         fields = (
             'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'serial', 'asset_tag', 'type',
             'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'serial', 'asset_tag', 'type',
             'width', 'u_height', 'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'tags',
             'width', 'u_height', 'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'tags',

+ 8 - 16
netbox/dcim/views.py

@@ -131,8 +131,7 @@ class RegionView(generic.ObjectView):
         sites = Site.objects.restrict(request.user, 'view').filter(
         sites = Site.objects.restrict(request.user, 'view').filter(
             region=instance
             region=instance
         )
         )
-        sites_table = tables.SiteTable(sites)
-        sites_table.columns.hide('region')
+        sites_table = tables.SiteTable(sites, exclude=('region',))
         paginate_table(sites_table, request)
         paginate_table(sites_table, request)
 
 
         return {
         return {
@@ -216,8 +215,7 @@ class SiteGroupView(generic.ObjectView):
         sites = Site.objects.restrict(request.user, 'view').filter(
         sites = Site.objects.restrict(request.user, 'view').filter(
             group=instance
             group=instance
         )
         )
-        sites_table = tables.SiteTable(sites)
-        sites_table.columns.hide('group')
+        sites_table = tables.SiteTable(sites, exclude=('group',))
         paginate_table(sites_table, request)
         paginate_table(sites_table, request)
 
 
         return {
         return {
@@ -453,8 +451,7 @@ class RackRoleView(generic.ObjectView):
             role=instance
             role=instance
         )
         )
 
 
-        racks_table = tables.RackTable(racks)
-        racks_table.columns.hide('role')
+        racks_table = tables.RackTable(racks, exclude=('role', 'get_utilization', 'get_power_utilization'))
         paginate_table(racks_table, request)
         paginate_table(racks_table, request)
 
 
         return {
         return {
@@ -505,7 +502,7 @@ class RackListView(generic.ObjectListView):
     )
     )
     filterset = filtersets.RackFilterSet
     filterset = filtersets.RackFilterSet
     filterset_form = forms.RackFilterForm
     filterset_form = forms.RackFilterForm
-    table = tables.RackDetailTable
+    table = tables.RackTable
 
 
 
 
 class RackElevationListView(generic.ObjectListView):
 class RackElevationListView(generic.ObjectListView):
@@ -704,8 +701,7 @@ class ManufacturerView(generic.ObjectView):
             manufacturer=instance
             manufacturer=instance
         )
         )
 
 
-        devicetypes_table = tables.DeviceTypeTable(devicetypes)
-        devicetypes_table.columns.hide('manufacturer')
+        devicetypes_table = tables.DeviceTypeTable(devicetypes, exclude=('manufacturer',))
         paginate_table(devicetypes_table, request)
         paginate_table(devicetypes_table, request)
 
 
         return {
         return {
@@ -1165,9 +1161,7 @@ class DeviceRoleView(generic.ObjectView):
         devices = Device.objects.restrict(request.user, 'view').filter(
         devices = Device.objects.restrict(request.user, 'view').filter(
             device_role=instance
             device_role=instance
         )
         )
-
-        devices_table = tables.DeviceTable(devices)
-        devices_table.columns.hide('device_role')
+        devices_table = tables.DeviceTable(devices, exclude=('device_role',))
         paginate_table(devices_table, request)
         paginate_table(devices_table, request)
 
 
         return {
         return {
@@ -1231,9 +1225,7 @@ class PlatformView(generic.ObjectView):
         devices = Device.objects.restrict(request.user, 'view').filter(
         devices = Device.objects.restrict(request.user, 'view').filter(
             platform=instance
             platform=instance
         )
         )
-
-        devices_table = tables.DeviceTable(devices)
-        devices_table.columns.hide('platform')
+        devices_table = tables.DeviceTable(devices, exclude=('platform',))
         paginate_table(devices_table, request)
         paginate_table(devices_table, request)
 
 
         return {
         return {
@@ -1878,9 +1870,9 @@ class InterfaceView(generic.ObjectView):
         child_interfaces = Interface.objects.restrict(request.user, 'view').filter(parent=instance)
         child_interfaces = Interface.objects.restrict(request.user, 'view').filter(parent=instance)
         child_interfaces_tables = tables.InterfaceTable(
         child_interfaces_tables = tables.InterfaceTable(
             child_interfaces,
             child_interfaces,
+            exclude=('device', 'parent'),
             orderable=False
             orderable=False
         )
         )
-        child_interfaces_tables.columns.hide('device')
 
 
         # Get assigned VLANs and annotate whether each is tagged or untagged
         # Get assigned VLANs and annotate whether each is tagged or untagged
         vlans = []
         vlans = []

+ 13 - 0
netbox/extras/tables.py

@@ -7,6 +7,19 @@ from utilities.tables import (
 )
 )
 from .models import *
 from .models import *
 
 
+__all__ = (
+    'ConfigContextTable',
+    'CustomFieldTable',
+    'CustomLinkTable',
+    'ExportTemplateTable',
+    'JournalEntryTable',
+    'ObjectChangeTable',
+    'ObjectJournalTable',
+    'TaggedItemTable',
+    'TagTable',
+    'WebhookTable',
+)
+
 CONFIGCONTEXT_ACTIONS = """
 CONFIGCONTEXT_ACTIONS = """
 {% if perms.extras.change_configcontext %}
 {% if perms.extras.change_configcontext %}
     <a href="{% url 'extras:configcontext_edit' pk=record.pk %}" class="btn btn-sm btn-warning"><i class="mdi mdi-pencil" aria-hidden="true"></i></a>
     <a href="{% url 'extras:configcontext_edit' pk=record.pk %}" class="btn btn-sm btn-warning"><i class="mdi mdi-pencil" aria-hidden="true"></i></a>

+ 4 - 0
netbox/ipam/tables/__init__.py

@@ -0,0 +1,4 @@
+from .ip import *
+from .services import *
+from .vlans import *
+from .vrfs import *

+ 27 - 322
netbox/ipam/tables.py → netbox/ipam/tables/ip.py

@@ -2,14 +2,23 @@ import django_tables2 as tables
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
 from django_tables2.utils import Accessor
 from django_tables2.utils import Accessor
 
 
-from dcim.models import Interface
 from tenancy.tables import TenantColumn
 from tenancy.tables import TenantColumn
 from utilities.tables import (
 from utilities.tables import (
-    BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ContentTypeColumn, LinkedCountColumn, TagColumn,
+    BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, LinkedCountColumn, TagColumn,
     ToggleColumn, UtilizationColumn,
     ToggleColumn, UtilizationColumn,
 )
 )
-from virtualization.models import VMInterface
-from .models import *
+from ipam.models import *
+
+__all__ = (
+    'AggregateTable',
+    'InterfaceIPAddressTable',
+    'IPAddressAssignTable',
+    'IPAddressTable',
+    'IPRangeTable',
+    'PrefixTable',
+    'RIRTable',
+    'RoleTable',
+)
 
 
 AVAILABLE_LABEL = mark_safe('<span class="badge bg-success">Available</span>')
 AVAILABLE_LABEL = mark_safe('<span class="badge bg-success">Available</span>')
 
 
@@ -66,114 +75,6 @@ VRF_LINK = """
 {% endif %}
 {% endif %}
 """
 """
 
 
-VRF_TARGETS = """
-{% for rt in value.all %}
-    <a href="{{ rt.get_absolute_url }}">{{ rt }}</a>{% if not forloop.last %}<br />{% endif %}
-{% empty %}
-    &mdash;
-{% endfor %}
-"""
-
-VLAN_LINK = """
-{% if record.pk %}
-    <a href="{{ record.get_absolute_url }}">{{ record.vid }}</a>
-{% elif perms.ipam.add_vlan %}
-    <a href="{% url 'ipam:vlan_add' %}?vid={{ record.vid }}{% if record.vlan_group %}&group={{ record.vlan_group.pk }}{% endif %}" class="btn btn-sm btn-success">{{ record.available }} VLAN{{ record.available|pluralize }} available</a>
-{% else %}
-    {{ record.available }} VLAN{{ record.available|pluralize }} available
-{% endif %}
-"""
-
-VLAN_PREFIXES = """
-{% for prefix in record.prefixes.all %}
-    <a href="{% url 'ipam:prefix' pk=prefix.pk %}">{{ prefix }}</a>{% if not forloop.last %}<br />{% endif %}
-{% empty %}
-    &mdash;
-{% endfor %}
-"""
-
-VLAN_ROLE_LINK = """
-{% if record.role %}
-    <a href="{% url 'ipam:vlan_list' %}?role={{ record.role.slug }}">{{ record.role }}</a>
-{% else %}
-    &mdash;
-{% endif %}
-"""
-
-VLANGROUP_ADD_VLAN = """
-{% with next_vid=record.get_next_available_vid %}
-    {% if next_vid and perms.ipam.add_vlan %}
-        <a href="{% url 'ipam:vlan_add' %}?group={{ record.pk }}&vid={{ next_vid }}" title="Add VLAN" class="btn btn-sm btn-success">
-            <i class="mdi mdi-plus-thick" aria-hidden="true"></i>
-        </a>
-    {% endif %}
-{% endwith %}
-"""
-
-VLAN_MEMBER_TAGGED = """
-{% if record.untagged_vlan_id == object.pk %}
-    <span class="text-danger"><i class="mdi mdi-close-thick"></i></span>
-{% else %}
-    <span class="text-success"><i class="mdi mdi-check-bold"></i></span>
-{% endif %}
-"""
-
-
-#
-# VRFs
-#
-
-class VRFTable(BaseTable):
-    pk = ToggleColumn()
-    name = tables.Column(
-        linkify=True
-    )
-    rd = tables.Column(
-        verbose_name='RD'
-    )
-    tenant = TenantColumn()
-    enforce_unique = BooleanColumn(
-        verbose_name='Unique'
-    )
-    import_targets = tables.TemplateColumn(
-        template_code=VRF_TARGETS,
-        orderable=False
-    )
-    export_targets = tables.TemplateColumn(
-        template_code=VRF_TARGETS,
-        orderable=False
-    )
-    tags = TagColumn(
-        url_name='ipam:vrf_list'
-    )
-
-    class Meta(BaseTable.Meta):
-        model = VRF
-        fields = (
-            'pk', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tags',
-        )
-        default_columns = ('pk', 'name', 'rd', 'tenant', 'description')
-
-
-#
-# Route targets
-#
-
-class RouteTargetTable(BaseTable):
-    pk = ToggleColumn()
-    name = tables.Column(
-        linkify=True
-    )
-    tenant = TenantColumn()
-    tags = TagColumn(
-        url_name='ipam:vrf_list'
-    )
-
-    class Meta(BaseTable.Meta):
-        model = RouteTarget
-        fields = ('pk', 'name', 'tenant', 'description', 'tags')
-        default_columns = ('pk', 'name', 'tenant', 'description')
-
 
 
 #
 #
 # RIRs
 # RIRs
@@ -215,13 +116,6 @@ class AggregateTable(BaseTable):
         format="Y-m-d",
         format="Y-m-d",
         verbose_name='Added'
         verbose_name='Added'
     )
     )
-
-    class Meta(BaseTable.Meta):
-        model = Aggregate
-        fields = ('pk', 'prefix', 'rir', 'tenant', 'date_added', 'description')
-
-
-class AggregateDetailTable(AggregateTable):
     child_count = tables.Column(
     child_count = tables.Column(
         verbose_name='Prefixes'
         verbose_name='Prefixes'
     )
     )
@@ -233,7 +127,8 @@ class AggregateDetailTable(AggregateTable):
         url_name='ipam:aggregate_list'
         url_name='ipam:aggregate_list'
     )
     )
 
 
-    class Meta(AggregateTable.Meta):
+    class Meta(BaseTable.Meta):
+        model = Aggregate
         fields = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description', 'tags')
         fields = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description', 'tags')
         default_columns = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description')
         default_columns = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description')
 
 
@@ -332,20 +227,6 @@ class PrefixTable(BaseTable):
     mark_utilized = BooleanColumn(
     mark_utilized = BooleanColumn(
         verbose_name='Marked Utilized'
         verbose_name='Marked Utilized'
     )
     )
-
-    class Meta(BaseTable.Meta):
-        model = Prefix
-        fields = (
-            'pk', 'prefix', 'prefix_flat', 'status', 'depth', 'children', 'vrf', 'tenant', 'site', 'vlan', 'role',
-            'is_pool', 'mark_utilized', 'description',
-        )
-        default_columns = ('pk', 'prefix', 'status', 'vrf', 'tenant', 'site', 'vlan', 'role', 'description')
-        row_attrs = {
-            'class': lambda record: 'success' if not record.pk else '',
-        }
-
-
-class PrefixDetailTable(PrefixTable):
     utilization = PrefixUtilizationColumn(
     utilization = PrefixUtilizationColumn(
         accessor='get_utilization',
         accessor='get_utilization',
         orderable=False
         orderable=False
@@ -354,7 +235,8 @@ class PrefixDetailTable(PrefixTable):
         url_name='ipam:prefix_list'
         url_name='ipam:prefix_list'
     )
     )
 
 
-    class Meta(PrefixTable.Meta):
+    class Meta(BaseTable.Meta):
+        model = Prefix
         fields = (
         fields = (
             'pk', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role',
             'pk', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role',
             'is_pool', 'mark_utilized', 'description', 'tags',
             'is_pool', 'mark_utilized', 'description', 'tags',
@@ -362,6 +244,9 @@ class PrefixDetailTable(PrefixTable):
         default_columns = (
         default_columns = (
             'pk', 'prefix', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description',
             'pk', 'prefix', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description',
         )
         )
+        row_attrs = {
+            'class': lambda record: 'success' if not record.pk else '',
+        }
 
 
 
 
 #
 #
@@ -427,25 +312,11 @@ class IPAddressTable(BaseTable):
         orderable=False,
         orderable=False,
         verbose_name='Device/VM'
         verbose_name='Device/VM'
     )
     )
-
-    class Meta(BaseTable.Meta):
-        model = IPAddress
-        fields = (
-            'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned_object', 'assigned_object_parent', 'dns_name',
-            'description',
-        )
-        row_attrs = {
-            'class': lambda record: 'success' if not isinstance(record, IPAddress) else '',
-        }
-
-
-class IPAddressDetailTable(IPAddressTable):
     nat_inside = tables.Column(
     nat_inside = tables.Column(
         linkify=True,
         linkify=True,
         orderable=False,
         orderable=False,
         verbose_name='NAT (Inside)'
         verbose_name='NAT (Inside)'
     )
     )
-    tenant = TenantColumn()
     assigned = BooleanColumn(
     assigned = BooleanColumn(
         accessor='assigned_object_id',
         accessor='assigned_object_id',
         verbose_name='Assigned'
         verbose_name='Assigned'
@@ -454,14 +325,18 @@ class IPAddressDetailTable(IPAddressTable):
         url_name='ipam:ipaddress_list'
         url_name='ipam:ipaddress_list'
     )
     )
 
 
-    class Meta(IPAddressTable.Meta):
+    class Meta(BaseTable.Meta):
+        model = IPAddress
         fields = (
         fields = (
-            'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'assigned', 'dns_name',
-            'description', 'tags',
+            'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'assigned', 'dns_name', 'description',
+            'tags',
         )
         )
         default_columns = (
         default_columns = (
             'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned', 'dns_name', 'description',
             'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned', 'dns_name', 'description',
         )
         )
+        row_attrs = {
+            'class': lambda record: 'success' if not isinstance(record, IPAddress) else '',
+        }
 
 
 
 
 class IPAddressAssignTable(BaseTable):
 class IPAddressAssignTable(BaseTable):
@@ -501,173 +376,3 @@ class InterfaceIPAddressTable(BaseTable):
     class Meta(BaseTable.Meta):
     class Meta(BaseTable.Meta):
         model = IPAddress
         model = IPAddress
         fields = ('address', 'vrf', 'status', 'role', 'tenant', 'description')
         fields = ('address', 'vrf', 'status', 'role', 'tenant', 'description')
-
-
-#
-# VLAN groups
-#
-
-class VLANGroupTable(BaseTable):
-    pk = ToggleColumn()
-    name = tables.Column(linkify=True)
-    scope_type = ContentTypeColumn()
-    scope = tables.Column(
-        linkify=True,
-        orderable=False
-    )
-    vlan_count = LinkedCountColumn(
-        viewname='ipam:vlan_list',
-        url_params={'group_id': 'pk'},
-        verbose_name='VLANs'
-    )
-    actions = ButtonsColumn(
-        model=VLANGroup,
-        prepend_template=VLANGROUP_ADD_VLAN
-    )
-
-    class Meta(BaseTable.Meta):
-        model = VLANGroup
-        fields = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'slug', 'description', 'actions')
-        default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'description', 'actions')
-
-
-#
-# VLANs
-#
-
-class VLANTable(BaseTable):
-    pk = ToggleColumn()
-    vid = tables.TemplateColumn(
-        template_code=VLAN_LINK,
-        verbose_name='ID'
-    )
-    site = tables.Column(
-        linkify=True
-    )
-    group = tables.Column(
-        linkify=True
-    )
-    tenant = TenantColumn()
-    status = ChoiceFieldColumn(
-        default=AVAILABLE_LABEL
-    )
-    role = tables.TemplateColumn(
-        template_code=VLAN_ROLE_LINK
-    )
-
-    class Meta(BaseTable.Meta):
-        model = VLAN
-        fields = ('pk', 'vid', 'name', 'site', 'group', 'tenant', 'status', 'role', 'description')
-        row_attrs = {
-            'class': lambda record: 'success' if not isinstance(record, VLAN) else '',
-        }
-
-
-class VLANDetailTable(VLANTable):
-    prefixes = tables.TemplateColumn(
-        template_code=VLAN_PREFIXES,
-        orderable=False,
-        verbose_name='Prefixes'
-    )
-    tenant = TenantColumn()
-    tags = TagColumn(
-        url_name='ipam:vlan_list'
-    )
-
-    class Meta(VLANTable.Meta):
-        fields = ('pk', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description', 'tags')
-        default_columns = ('pk', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description')
-
-
-class VLANMembersTable(BaseTable):
-    """
-    Base table for Interface and VMInterface assignments
-    """
-    name = tables.Column(
-        linkify=True,
-        verbose_name='Interface'
-    )
-    tagged = tables.TemplateColumn(
-        template_code=VLAN_MEMBER_TAGGED,
-        orderable=False
-    )
-
-
-class VLANDevicesTable(VLANMembersTable):
-    device = tables.Column(
-        linkify=True
-    )
-    actions = ButtonsColumn(Interface, buttons=['edit'])
-
-    class Meta(BaseTable.Meta):
-        model = Interface
-        fields = ('device', 'name', 'tagged', 'actions')
-
-
-class VLANVirtualMachinesTable(VLANMembersTable):
-    virtual_machine = tables.Column(
-        linkify=True
-    )
-    actions = ButtonsColumn(VMInterface, buttons=['edit'])
-
-    class Meta(BaseTable.Meta):
-        model = VMInterface
-        fields = ('virtual_machine', 'name', 'tagged', 'actions')
-
-
-class InterfaceVLANTable(BaseTable):
-    """
-    List VLANs assigned to a specific Interface.
-    """
-    vid = tables.Column(
-        linkify=True,
-        verbose_name='ID'
-    )
-    tagged = BooleanColumn()
-    site = tables.Column(
-        linkify=True
-    )
-    group = tables.Column(
-        accessor=Accessor('group__name'),
-        verbose_name='Group'
-    )
-    tenant = TenantColumn()
-    status = ChoiceFieldColumn()
-    role = tables.TemplateColumn(
-        template_code=VLAN_ROLE_LINK
-    )
-
-    class Meta(BaseTable.Meta):
-        model = VLAN
-        fields = ('vid', 'tagged', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description')
-
-    def __init__(self, interface, *args, **kwargs):
-        self.interface = interface
-        super().__init__(*args, **kwargs)
-
-
-#
-# Services
-#
-
-class ServiceTable(BaseTable):
-    pk = ToggleColumn()
-    name = tables.Column(
-        linkify=True
-    )
-    parent = tables.Column(
-        linkify=True,
-        order_by=('device', 'virtual_machine')
-    )
-    ports = tables.TemplateColumn(
-        template_code='{{ record.port_list }}',
-        verbose_name='Ports'
-    )
-    tags = TagColumn(
-        url_name='ipam:service_list'
-    )
-
-    class Meta(BaseTable.Meta):
-        model = Service
-        fields = ('pk', 'name', 'parent', 'protocol', 'ports', 'ipaddresses', 'description', 'tags')
-        default_columns = ('pk', 'name', 'parent', 'protocol', 'ports', 'description')

+ 35 - 0
netbox/ipam/tables/services.py

@@ -0,0 +1,35 @@
+import django_tables2 as tables
+
+from utilities.tables import BaseTable, TagColumn, ToggleColumn
+from ipam.models import *
+
+__all__ = (
+    'ServiceTable',
+)
+
+
+#
+# Services
+#
+
+class ServiceTable(BaseTable):
+    pk = ToggleColumn()
+    name = tables.Column(
+        linkify=True
+    )
+    parent = tables.Column(
+        linkify=True,
+        order_by=('device', 'virtual_machine')
+    )
+    ports = tables.TemplateColumn(
+        template_code='{{ record.port_list }}',
+        verbose_name='Ports'
+    )
+    tags = TagColumn(
+        url_name='ipam:service_list'
+    )
+
+    class Meta(BaseTable.Meta):
+        model = Service
+        fields = ('pk', 'name', 'parent', 'protocol', 'ports', 'ipaddresses', 'description', 'tags')
+        default_columns = ('pk', 'name', 'parent', 'protocol', 'ports', 'description')

+ 203 - 0
netbox/ipam/tables/vlans.py

@@ -0,0 +1,203 @@
+import django_tables2 as tables
+from django.utils.safestring import mark_safe
+from django_tables2.utils import Accessor
+
+from dcim.models import Interface
+from tenancy.tables import TenantColumn
+from utilities.tables import (
+    BaseTable, BooleanColumn, ButtonsColumn, ChoiceFieldColumn, ContentTypeColumn, LinkedCountColumn, TagColumn,
+    ToggleColumn,
+)
+from virtualization.models import VMInterface
+from ipam.models import *
+
+__all__ = (
+    'InterfaceVLANTable',
+    'VLANDevicesTable',
+    'VLANGroupTable',
+    'VLANMembersTable',
+    'VLANTable',
+    'VLANVirtualMachinesTable',
+)
+
+AVAILABLE_LABEL = mark_safe('<span class="badge bg-success">Available</span>')
+
+VLAN_LINK = """
+{% if record.pk %}
+    <a href="{{ record.get_absolute_url }}">{{ record.vid }}</a>
+{% elif perms.ipam.add_vlan %}
+    <a href="{% url 'ipam:vlan_add' %}?vid={{ record.vid }}{% if record.vlan_group %}&group={{ record.vlan_group.pk }}{% endif %}" class="btn btn-sm btn-success">{{ record.available }} VLAN{{ record.available|pluralize }} available</a>
+{% else %}
+    {{ record.available }} VLAN{{ record.available|pluralize }} available
+{% endif %}
+"""
+
+VLAN_PREFIXES = """
+{% for prefix in record.prefixes.all %}
+    <a href="{% url 'ipam:prefix' pk=prefix.pk %}">{{ prefix }}</a>{% if not forloop.last %}<br />{% endif %}
+{% empty %}
+    &mdash;
+{% endfor %}
+"""
+
+VLAN_ROLE_LINK = """
+{% if record.role %}
+    <a href="{% url 'ipam:vlan_list' %}?role={{ record.role.slug }}">{{ record.role }}</a>
+{% else %}
+    &mdash;
+{% endif %}
+"""
+
+VLANGROUP_ADD_VLAN = """
+{% with next_vid=record.get_next_available_vid %}
+    {% if next_vid and perms.ipam.add_vlan %}
+        <a href="{% url 'ipam:vlan_add' %}?group={{ record.pk }}&vid={{ next_vid }}" title="Add VLAN" class="btn btn-sm btn-success">
+            <i class="mdi mdi-plus-thick" aria-hidden="true"></i>
+        </a>
+    {% endif %}
+{% endwith %}
+"""
+
+VLAN_MEMBER_TAGGED = """
+{% if record.untagged_vlan_id == object.pk %}
+    <span class="text-danger"><i class="mdi mdi-close-thick"></i></span>
+{% else %}
+    <span class="text-success"><i class="mdi mdi-check-bold"></i></span>
+{% endif %}
+"""
+
+
+#
+# VLAN groups
+#
+
+class VLANGroupTable(BaseTable):
+    pk = ToggleColumn()
+    name = tables.Column(linkify=True)
+    scope_type = ContentTypeColumn()
+    scope = tables.Column(
+        linkify=True,
+        orderable=False
+    )
+    vlan_count = LinkedCountColumn(
+        viewname='ipam:vlan_list',
+        url_params={'group_id': 'pk'},
+        verbose_name='VLANs'
+    )
+    actions = ButtonsColumn(
+        model=VLANGroup,
+        prepend_template=VLANGROUP_ADD_VLAN
+    )
+
+    class Meta(BaseTable.Meta):
+        model = VLANGroup
+        fields = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'slug', 'description', 'actions')
+        default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'description', 'actions')
+
+
+#
+# VLANs
+#
+
+class VLANTable(BaseTable):
+    pk = ToggleColumn()
+    vid = tables.TemplateColumn(
+        template_code=VLAN_LINK,
+        verbose_name='ID'
+    )
+    site = tables.Column(
+        linkify=True
+    )
+    group = tables.Column(
+        linkify=True
+    )
+    tenant = TenantColumn()
+    status = ChoiceFieldColumn(
+        default=AVAILABLE_LABEL
+    )
+    role = tables.TemplateColumn(
+        template_code=VLAN_ROLE_LINK
+    )
+    prefixes = tables.TemplateColumn(
+        template_code=VLAN_PREFIXES,
+        orderable=False,
+        verbose_name='Prefixes'
+    )
+    tags = TagColumn(
+        url_name='ipam:vlan_list'
+    )
+
+    class Meta(BaseTable.Meta):
+        model = VLAN
+        fields = ('pk', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description', 'tags')
+        default_columns = ('pk', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description')
+        row_attrs = {
+            'class': lambda record: 'success' if not isinstance(record, VLAN) else '',
+        }
+
+
+class VLANMembersTable(BaseTable):
+    """
+    Base table for Interface and VMInterface assignments
+    """
+    name = tables.Column(
+        linkify=True,
+        verbose_name='Interface'
+    )
+    tagged = tables.TemplateColumn(
+        template_code=VLAN_MEMBER_TAGGED,
+        orderable=False
+    )
+
+
+class VLANDevicesTable(VLANMembersTable):
+    device = tables.Column(
+        linkify=True
+    )
+    actions = ButtonsColumn(Interface, buttons=['edit'])
+
+    class Meta(BaseTable.Meta):
+        model = Interface
+        fields = ('device', 'name', 'tagged', 'actions')
+
+
+class VLANVirtualMachinesTable(VLANMembersTable):
+    virtual_machine = tables.Column(
+        linkify=True
+    )
+    actions = ButtonsColumn(VMInterface, buttons=['edit'])
+
+    class Meta(BaseTable.Meta):
+        model = VMInterface
+        fields = ('virtual_machine', 'name', 'tagged', 'actions')
+
+
+class InterfaceVLANTable(BaseTable):
+    """
+    List VLANs assigned to a specific Interface.
+    """
+    vid = tables.Column(
+        linkify=True,
+        verbose_name='ID'
+    )
+    tagged = BooleanColumn()
+    site = tables.Column(
+        linkify=True
+    )
+    group = tables.Column(
+        accessor=Accessor('group__name'),
+        verbose_name='Group'
+    )
+    tenant = TenantColumn()
+    status = ChoiceFieldColumn()
+    role = tables.TemplateColumn(
+        template_code=VLAN_ROLE_LINK
+    )
+
+    class Meta(BaseTable.Meta):
+        model = VLAN
+        fields = ('vid', 'tagged', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description')
+
+    def __init__(self, interface, *args, **kwargs):
+        self.interface = interface
+        super().__init__(*args, **kwargs)

+ 74 - 0
netbox/ipam/tables/vrfs.py

@@ -0,0 +1,74 @@
+import django_tables2 as tables
+
+from tenancy.tables import TenantColumn
+from utilities.tables import BaseTable, BooleanColumn, TagColumn, ToggleColumn
+from ipam.models import *
+
+__all__ = (
+    'RouteTargetTable',
+    'VRFTable',
+)
+
+VRF_TARGETS = """
+{% for rt in value.all %}
+    <a href="{{ rt.get_absolute_url }}">{{ rt }}</a>{% if not forloop.last %}<br />{% endif %}
+{% empty %}
+    &mdash;
+{% endfor %}
+"""
+
+
+#
+# VRFs
+#
+
+class VRFTable(BaseTable):
+    pk = ToggleColumn()
+    name = tables.Column(
+        linkify=True
+    )
+    rd = tables.Column(
+        verbose_name='RD'
+    )
+    tenant = TenantColumn()
+    enforce_unique = BooleanColumn(
+        verbose_name='Unique'
+    )
+    import_targets = tables.TemplateColumn(
+        template_code=VRF_TARGETS,
+        orderable=False
+    )
+    export_targets = tables.TemplateColumn(
+        template_code=VRF_TARGETS,
+        orderable=False
+    )
+    tags = TagColumn(
+        url_name='ipam:vrf_list'
+    )
+
+    class Meta(BaseTable.Meta):
+        model = VRF
+        fields = (
+            'pk', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tags',
+        )
+        default_columns = ('pk', 'name', 'rd', 'tenant', 'description')
+
+
+#
+# Route targets
+#
+
+class RouteTargetTable(BaseTable):
+    pk = ToggleColumn()
+    name = tables.Column(
+        linkify=True
+    )
+    tenant = TenantColumn()
+    tags = TagColumn(
+        url_name='ipam:vrf_list'
+    )
+
+    class Meta(BaseTable.Meta):
+        model = RouteTarget
+        fields = ('pk', 'name', 'tenant', 'description', 'tags')
+        default_columns = ('pk', 'name', 'tenant', 'description')

+ 25 - 22
netbox/ipam/views.py

@@ -155,9 +155,7 @@ class RIRView(generic.ObjectView):
         aggregates = Aggregate.objects.restrict(request.user, 'view').filter(
         aggregates = Aggregate.objects.restrict(request.user, 'view').filter(
             rir=instance
             rir=instance
         )
         )
-
-        aggregates_table = tables.AggregateTable(aggregates)
-        aggregates_table.columns.hide('rir')
+        aggregates_table = tables.AggregateTable(aggregates, exclude=('rir', 'utilization'))
         paginate_table(aggregates_table, request)
         paginate_table(aggregates_table, request)
 
 
         return {
         return {
@@ -207,7 +205,7 @@ class AggregateListView(generic.ObjectListView):
     )
     )
     filterset = filtersets.AggregateFilterSet
     filterset = filtersets.AggregateFilterSet
     filterset_form = forms.AggregateFilterForm
     filterset_form = forms.AggregateFilterForm
-    table = tables.AggregateDetailTable
+    table = tables.AggregateTable
 
 
 
 
 class AggregateView(generic.ObjectView):
 class AggregateView(generic.ObjectView):
@@ -227,7 +225,7 @@ class AggregateView(generic.ObjectView):
         if request.GET.get('show_available', 'true') == 'true':
         if request.GET.get('show_available', 'true') == 'true':
             child_prefixes = add_available_prefixes(instance.prefix, child_prefixes)
             child_prefixes = add_available_prefixes(instance.prefix, child_prefixes)
 
 
-        prefix_table = tables.PrefixDetailTable(child_prefixes)
+        prefix_table = tables.PrefixTable(child_prefixes, exclude=('utilization',))
         if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
         if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
             prefix_table.columns.show('pk')
             prefix_table.columns.show('pk')
         paginate_table(prefix_table, request)
         paginate_table(prefix_table, request)
@@ -296,8 +294,7 @@ class RoleView(generic.ObjectView):
             role=instance
             role=instance
         )
         )
 
 
-        prefixes_table = tables.PrefixTable(prefixes)
-        prefixes_table.columns.hide('role')
+        prefixes_table = tables.PrefixTable(prefixes, exclude=('role', 'utilization'))
         paginate_table(prefixes_table, request)
         paginate_table(prefixes_table, request)
 
 
         return {
         return {
@@ -340,7 +337,7 @@ class PrefixListView(generic.ObjectListView):
     queryset = Prefix.objects.all()
     queryset = Prefix.objects.all()
     filterset = filtersets.PrefixFilterSet
     filterset = filtersets.PrefixFilterSet
     filterset_form = forms.PrefixFilterForm
     filterset_form = forms.PrefixFilterForm
-    table = tables.PrefixDetailTable
+    table = tables.PrefixTable
     template_name = 'ipam/prefix_list.html'
     template_name = 'ipam/prefix_list.html'
 
 
 
 
@@ -363,8 +360,11 @@ class PrefixView(generic.ObjectView):
         ).prefetch_related(
         ).prefetch_related(
             'site', 'role'
             'site', 'role'
         )
         )
-        parent_prefix_table = tables.PrefixTable(list(parent_prefixes), orderable=False)
-        parent_prefix_table.exclude = ('vrf',)
+        parent_prefix_table = tables.PrefixTable(
+            list(parent_prefixes),
+            exclude=('vrf', 'utilization'),
+            orderable=False
+        )
 
 
         # Duplicate prefixes table
         # Duplicate prefixes table
         duplicate_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
         duplicate_prefixes = Prefix.objects.restrict(request.user, 'view').filter(
@@ -374,8 +374,11 @@ class PrefixView(generic.ObjectView):
         ).prefetch_related(
         ).prefetch_related(
             'site', 'role'
             'site', 'role'
         )
         )
-        duplicate_prefix_table = tables.PrefixTable(list(duplicate_prefixes), orderable=False)
-        duplicate_prefix_table.exclude = ('vrf',)
+        duplicate_prefix_table = tables.PrefixTable(
+            list(duplicate_prefixes),
+            exclude=('vrf', 'utilization'),
+            orderable=False
+        )
 
 
         return {
         return {
             'aggregate': aggregate,
             'aggregate': aggregate,
@@ -398,7 +401,7 @@ class PrefixPrefixesView(generic.ObjectView):
         if child_prefixes and request.GET.get('show_available', 'true') == 'true':
         if child_prefixes and request.GET.get('show_available', 'true') == 'true':
             child_prefixes = add_available_prefixes(instance.prefix, child_prefixes)
             child_prefixes = add_available_prefixes(instance.prefix, child_prefixes)
 
 
-        table = tables.PrefixDetailTable(child_prefixes, user=request.user)
+        table = tables.PrefixTable(child_prefixes, user=request.user, exclude=('utilization',))
         if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
         if request.user.has_perm('ipam.change_prefix') or request.user.has_perm('ipam.delete_prefix'):
             table.columns.show('pk')
             table.columns.show('pk')
         paginate_table(table, request)
         paginate_table(table, request)
@@ -601,7 +604,7 @@ class IPAddressListView(generic.ObjectListView):
     queryset = IPAddress.objects.all()
     queryset = IPAddress.objects.all()
     filterset = filtersets.IPAddressFilterSet
     filterset = filtersets.IPAddressFilterSet
     filterset_form = forms.IPAddressFilterForm
     filterset_form = forms.IPAddressFilterForm
-    table = tables.IPAddressDetailTable
+    table = tables.IPAddressTable
 
 
 
 
 class IPAddressView(generic.ObjectView):
 class IPAddressView(generic.ObjectView):
@@ -615,8 +618,11 @@ class IPAddressView(generic.ObjectView):
         ).prefetch_related(
         ).prefetch_related(
             'site', 'role'
             'site', 'role'
         )
         )
-        parent_prefixes_table = tables.PrefixTable(list(parent_prefixes), orderable=False)
-        parent_prefixes_table.exclude = ('vrf',)
+        parent_prefixes_table = tables.PrefixTable(
+            list(parent_prefixes),
+            exclude=('vrf', 'utilization'),
+            orderable=False
+        )
 
 
         # Duplicate IPs table
         # Duplicate IPs table
         duplicate_ips = IPAddress.objects.restrict(request.user, 'view').filter(
         duplicate_ips = IPAddress.objects.restrict(request.user, 'view').filter(
@@ -767,11 +773,9 @@ class VLANGroupView(generic.ObjectView):
         vlans_count = vlans.count()
         vlans_count = vlans.count()
         vlans = add_available_vlans(vlans, vlan_group=instance)
         vlans = add_available_vlans(vlans, vlan_group=instance)
 
 
-        vlans_table = tables.VLANDetailTable(vlans)
+        vlans_table = tables.VLANTable(vlans, exclude=('site', 'group', 'prefixes'))
         if request.user.has_perm('ipam.change_vlan') or request.user.has_perm('ipam.delete_vlan'):
         if request.user.has_perm('ipam.change_vlan') or request.user.has_perm('ipam.delete_vlan'):
             vlans_table.columns.show('pk')
             vlans_table.columns.show('pk')
-        vlans_table.columns.hide('site')
-        vlans_table.columns.hide('group')
         paginate_table(vlans_table, request)
         paginate_table(vlans_table, request)
 
 
         # Compile permissions list for rendering the object table
         # Compile permissions list for rendering the object table
@@ -828,7 +832,7 @@ class VLANListView(generic.ObjectListView):
     queryset = VLAN.objects.all()
     queryset = VLAN.objects.all()
     filterset = filtersets.VLANFilterSet
     filterset = filtersets.VLANFilterSet
     filterset_form = forms.VLANFilterForm
     filterset_form = forms.VLANFilterForm
-    table = tables.VLANDetailTable
+    table = tables.VLANTable
 
 
 
 
 class VLANView(generic.ObjectView):
 class VLANView(generic.ObjectView):
@@ -838,8 +842,7 @@ class VLANView(generic.ObjectView):
         prefixes = Prefix.objects.restrict(request.user, 'view').filter(vlan=instance).prefetch_related(
         prefixes = Prefix.objects.restrict(request.user, 'view').filter(vlan=instance).prefetch_related(
             'vrf', 'site', 'role'
             'vrf', 'site', 'role'
         )
         )
-        prefix_table = tables.PrefixTable(list(prefixes), orderable=False)
-        prefix_table.exclude = ('vlan',)
+        prefix_table = tables.PrefixTable(list(prefixes), exclude=('vlan', 'utilization'), orderable=False)
 
 
         return {
         return {
             'prefix_table': prefix_table,
             'prefix_table': prefix_table,

+ 2 - 2
netbox/netbox/constants.py

@@ -21,7 +21,7 @@ from tenancy.tables import TenantTable
 from utilities.utils import count_related
 from utilities.utils import count_related
 from virtualization.filtersets import ClusterFilterSet, VirtualMachineFilterSet
 from virtualization.filtersets import ClusterFilterSet, VirtualMachineFilterSet
 from virtualization.models import Cluster, VirtualMachine
 from virtualization.models import Cluster, VirtualMachine
-from virtualization.tables import ClusterTable, VirtualMachineDetailTable
+from virtualization.tables import ClusterTable, VirtualMachineTable
 
 
 SEARCH_MAX_RESULTS = 15
 SEARCH_MAX_RESULTS = 15
 SEARCH_TYPES = OrderedDict((
 SEARCH_TYPES = OrderedDict((
@@ -130,7 +130,7 @@ SEARCH_TYPES = OrderedDict((
             'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6',
             'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6',
         ),
         ),
         'filterset': VirtualMachineFilterSet,
         'filterset': VirtualMachineFilterSet,
-        'table': VirtualMachineDetailTable,
+        'table': VirtualMachineTable,
         'url': 'virtualization:virtualmachine_list',
         'url': 'virtualization:virtualmachine_list',
     }),
     }),
     # IPAM
     # IPAM

+ 2 - 2
netbox/templates/ipam/prefix/prefixes.html

@@ -15,9 +15,9 @@
 {% block content %}
 {% block content %}
   <div class="row">
   <div class="row">
     <div class="col col-md-12">
     <div class="col col-md-12">
-      {% include 'inc/table_controls.html' with table_modal="PrefixDetailTable_config" %}
+      {% include 'inc/table_controls.html' with table_modal="PrefixTable_config" %}
       {% include 'utilities/obj_table.html' with heading='Child Prefixes' bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' parent=prefix %}
       {% include 'utilities/obj_table.html' with heading='Child Prefixes' bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' parent=prefix %}
     </div>
     </div>
   </div>
   </div>
-  {% table_config_form table table_name="PrefixDetailTable" %}
+  {% table_config_form table table_name="PrefixTable" %}
 {% endblock %}
 {% endblock %}

+ 6 - 0
netbox/tenancy/tables.py

@@ -5,6 +5,12 @@ from utilities.tables import (
 )
 )
 from .models import Tenant, TenantGroup
 from .models import Tenant, TenantGroup
 
 
+__all__ = (
+    'TenantColumn',
+    'TenantGroupTable',
+    'TenantTable',
+)
+
 
 
 #
 #
 # Table columns
 # Table columns

+ 1 - 3
netbox/tenancy/views.py

@@ -32,9 +32,7 @@ class TenantGroupView(generic.ObjectView):
         tenants = Tenant.objects.restrict(request.user, 'view').filter(
         tenants = Tenant.objects.restrict(request.user, 'view').filter(
             group=instance
             group=instance
         )
         )
-
-        tenants_table = tables.TenantTable(tenants)
-        tenants_table.columns.hide('group')
+        tenants_table = tables.TenantTable(tenants, exclude=('group',))
         paginate_table(tenants_table, request)
         paginate_table(tenants_table, request)
 
 
         return {
         return {

+ 8 - 21
netbox/virtualization/tables.py

@@ -12,12 +12,13 @@ __all__ = (
     'ClusterTable',
     'ClusterTable',
     'ClusterGroupTable',
     'ClusterGroupTable',
     'ClusterTypeTable',
     'ClusterTypeTable',
-    'VirtualMachineDetailTable',
     'VirtualMachineTable',
     'VirtualMachineTable',
     'VirtualMachineVMInterfaceTable',
     'VirtualMachineVMInterfaceTable',
     'VMInterfaceTable',
     'VMInterfaceTable',
 )
 )
 
 
+PRIMARY_IP_ORDERING = ('primary_ip4', 'primary_ip6') if settings.PREFER_IPV4 else ('primary_ip6', 'primary_ip4')
+
 VMINTERFACE_BUTTONS = """
 VMINTERFACE_BUTTONS = """
 {% if perms.ipam.add_ipaddress %}
 {% if perms.ipam.add_ipaddress %}
     <a href="{% url 'ipam:ipaddress_add' %}?vminterface={{ record.pk }}&return_url={{ virtualmachine.get_absolute_url }}" class="btn btn-sm btn-success" title="Add IP Address">
     <a href="{% url 'ipam:ipaddress_add' %}?vminterface={{ record.pk }}&return_url={{ virtualmachine.get_absolute_url }}" class="btn btn-sm btn-success" title="Add IP Address">
@@ -118,13 +119,7 @@ class VirtualMachineTable(BaseTable):
     )
     )
     role = ColoredLabelColumn()
     role = ColoredLabelColumn()
     tenant = TenantColumn()
     tenant = TenantColumn()
-
-    class Meta(BaseTable.Meta):
-        model = VirtualMachine
-        fields = ('pk', 'name', 'status', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk')
-
-
-class VirtualMachineDetailTable(VirtualMachineTable):
+    comments = MarkdownColumn()
     primary_ip4 = tables.Column(
     primary_ip4 = tables.Column(
         linkify=True,
         linkify=True,
         verbose_name='IPv4 Address'
         verbose_name='IPv4 Address'
@@ -133,19 +128,11 @@ class VirtualMachineDetailTable(VirtualMachineTable):
         linkify=True,
         linkify=True,
         verbose_name='IPv6 Address'
         verbose_name='IPv6 Address'
     )
     )
-    if settings.PREFER_IPV4:
-        primary_ip = tables.Column(
-            linkify=True,
-            order_by=('primary_ip4', 'primary_ip6'),
-            verbose_name='IP Address'
-        )
-    else:
-        primary_ip = tables.Column(
-            linkify=True,
-            order_by=('primary_ip6', 'primary_ip4'),
-            verbose_name='IP Address'
-        )
-    comments = MarkdownColumn()
+    primary_ip = tables.Column(
+        linkify=True,
+        order_by=PRIMARY_IP_ORDERING,
+        verbose_name='IP Address'
+    )
     tags = TagColumn(
     tags = TagColumn(
         url_name='virtualization:virtualmachine_list'
         url_name='virtualization:virtualmachine_list'
     )
     )

+ 9 - 9
netbox/virtualization/views.py

@@ -39,9 +39,7 @@ class ClusterTypeView(generic.ObjectView):
             device_count=count_related(Device, 'cluster'),
             device_count=count_related(Device, 'cluster'),
             vm_count=count_related(VirtualMachine, 'cluster')
             vm_count=count_related(VirtualMachine, 'cluster')
         )
         )
-
-        clusters_table = tables.ClusterTable(clusters)
-        clusters_table.columns.hide('type')
+        clusters_table = tables.ClusterTable(clusters, exclude=('type',))
         paginate_table(clusters_table, request)
         paginate_table(clusters_table, request)
 
 
         return {
         return {
@@ -103,9 +101,7 @@ class ClusterGroupView(generic.ObjectView):
             device_count=count_related(Device, 'cluster'),
             device_count=count_related(Device, 'cluster'),
             vm_count=count_related(VirtualMachine, 'cluster')
             vm_count=count_related(VirtualMachine, 'cluster')
         )
         )
-
-        clusters_table = tables.ClusterTable(clusters)
-        clusters_table.columns.hide('group')
+        clusters_table = tables.ClusterTable(clusters, exclude=('group',))
         paginate_table(clusters_table, request)
         paginate_table(clusters_table, request)
 
 
         return {
         return {
@@ -171,7 +167,11 @@ class ClusterVirtualMachinesView(generic.ObjectView):
 
 
     def get_extra_context(self, request, instance):
     def get_extra_context(self, request, instance):
         virtualmachines = VirtualMachine.objects.restrict(request.user, 'view').filter(cluster=instance)
         virtualmachines = VirtualMachine.objects.restrict(request.user, 'view').filter(cluster=instance)
-        virtualmachines_table = tables.VirtualMachineTable(virtualmachines, orderable=False)
+        virtualmachines_table = tables.VirtualMachineTable(
+            virtualmachines,
+            exclude=('cluster',),
+            orderable=False
+        )
 
 
         return {
         return {
             'virtualmachines_table': virtualmachines_table,
             'virtualmachines_table': virtualmachines_table,
@@ -315,7 +315,7 @@ class VirtualMachineListView(generic.ObjectListView):
     queryset = VirtualMachine.objects.all()
     queryset = VirtualMachine.objects.all()
     filterset = filtersets.VirtualMachineFilterSet
     filterset = filtersets.VirtualMachineFilterSet
     filterset_form = forms.VirtualMachineFilterForm
     filterset_form = forms.VirtualMachineFilterForm
-    table = tables.VirtualMachineDetailTable
+    table = tables.VirtualMachineTable
     template_name = 'virtualization/virtualmachine_list.html'
     template_name = 'virtualization/virtualmachine_list.html'
 
 
 
 
@@ -430,9 +430,9 @@ class VMInterfaceView(generic.ObjectView):
         child_interfaces = VMInterface.objects.restrict(request.user, 'view').filter(parent=instance)
         child_interfaces = VMInterface.objects.restrict(request.user, 'view').filter(parent=instance)
         child_interfaces_tables = tables.VMInterfaceTable(
         child_interfaces_tables = tables.VMInterfaceTable(
             child_interfaces,
             child_interfaces,
+            exclude=('virtual_machine',),
             orderable=False
             orderable=False
         )
         )
-        child_interfaces_tables.columns.hide('virtual_machine')
 
 
         # Get assigned VLANs and annotate whether each is tagged or untagged
         # Get assigned VLANs and annotate whether each is tagged or untagged
         vlans = []
         vlans = []