Browse Source

Introduced SearchTable for improved performance

Jeremy Stretch 8 years ago
parent
commit
a5dc91c175

+ 35 - 17
netbox/circuits/tables.py

@@ -1,7 +1,7 @@
 import django_tables2 as tables
 from django_tables2.utils import Accessor
 
-from utilities.tables import BaseTable, ToggleColumn
+from utilities.tables import BaseTable, SearchTable, ToggleColumn
 
 from .models import Circuit, CircuitType, Provider
 
@@ -19,9 +19,7 @@ CIRCUITTYPE_ACTIONS = """
 
 class ProviderTable(BaseTable):
     pk = ToggleColumn()
-    name = tables.LinkColumn('circuits:provider', args=[Accessor('slug')], verbose_name='Name')
-    asn = tables.Column(verbose_name='ASN')
-    account = tables.Column(verbose_name='Account')
+    name = tables.LinkColumn()
     circuit_count = tables.Column(accessor=Accessor('count_circuits'), verbose_name='Circuits')
 
     class Meta(BaseTable.Meta):
@@ -29,17 +27,25 @@ class ProviderTable(BaseTable):
         fields = ('pk', 'name', 'asn', 'account', 'circuit_count')
 
 
+class ProviderSearchTable(SearchTable):
+    name = tables.LinkColumn()
+
+    class Meta(SearchTable.Meta):
+        model = Provider
+        fields = ('name', 'asn', 'account')
+
+
 #
 # Circuit types
 #
 
 class CircuitTypeTable(BaseTable):
     pk = ToggleColumn()
-    name = tables.LinkColumn(verbose_name='Name')
+    name = tables.LinkColumn()
     circuit_count = tables.Column(verbose_name='Circuits')
-    slug = tables.Column(verbose_name='Slug')
-    actions = tables.TemplateColumn(template_code=CIRCUITTYPE_ACTIONS, attrs={'td': {'class': 'text-right'}},
-                                    verbose_name='')
+    actions = tables.TemplateColumn(
+        template_code=CIRCUITTYPE_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name=''
+    )
 
     class Meta(BaseTable.Meta):
         model = CircuitType
@@ -52,16 +58,28 @@ class CircuitTypeTable(BaseTable):
 
 class CircuitTable(BaseTable):
     pk = ToggleColumn()
-    cid = tables.LinkColumn('circuits:circuit', args=[Accessor('pk')], verbose_name='ID')
-    type = tables.Column(verbose_name='Type')
-    provider = tables.LinkColumn('circuits:provider', args=[Accessor('provider.slug')], verbose_name='Provider')
-    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
-    a_side = tables.LinkColumn('dcim:site', accessor=Accessor('termination_a.site'), orderable=False,
-                               args=[Accessor('termination_a.site.slug')])
-    z_side = tables.LinkColumn('dcim:site', accessor=Accessor('termination_z.site'), orderable=False,
-                               args=[Accessor('termination_z.site.slug')])
-    description = tables.Column(verbose_name='Description')
+    cid = tables.LinkColumn(verbose_name='ID')
+    provider = tables.LinkColumn('circuits:provider', args=[Accessor('provider.slug')])
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
+    a_side = tables.LinkColumn(
+        'dcim:site', accessor=Accessor('termination_a.site'), orderable=False,
+        args=[Accessor('termination_a.site.slug')]
+    )
+    z_side = tables.LinkColumn(
+        'dcim:site', accessor=Accessor('termination_z.site'), orderable=False,
+        args=[Accessor('termination_z.site.slug')]
+    )
 
     class Meta(BaseTable.Meta):
         model = Circuit
         fields = ('pk', 'cid', 'type', 'provider', 'tenant', 'a_side', 'z_side', 'description')
+
+
+class CircuitSearchTable(SearchTable):
+    cid = tables.LinkColumn(verbose_name='ID')
+    provider = tables.LinkColumn('circuits:provider', args=[Accessor('provider.slug')])
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
+
+    class Meta(SearchTable.Meta):
+        model = Circuit
+        fields = ('cid', 'type', 'provider', 'tenant', 'description')

+ 3 - 3
netbox/dcim/filters.py

@@ -8,9 +8,9 @@ from tenancy.models import Tenant
 from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter
 from .models import (
     ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
-    DeviceBayTemplate, DeviceRole, DeviceType, IFACE_FF_LAG, Interface, InterfaceConnection, InterfaceTemplate,
-    Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
-    RackReservation, RackRole, Region, Site, VIRTUAL_IFACE_TYPES,
+    DeviceBayTemplate, DeviceRole, DeviceType, IFACE_FF_LAG, Interface, InterfaceTemplate, Manufacturer, InventoryItem,
+    Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation,
+    RackRole, Region, Site, VIRTUAL_IFACE_TYPES,
 )
 
 

+ 79 - 24
netbox/dcim/tables.py

@@ -1,7 +1,7 @@
 import django_tables2 as tables
 from django_tables2.utils import Accessor
 
-from utilities.tables import BaseTable, ToggleColumn
+from utilities.tables import BaseTable, SearchTable, ToggleColumn
 
 from .models import (
     ConsolePort, ConsolePortTemplate, ConsoleServerPortTemplate, Device, DeviceBayTemplate, DeviceRole, DeviceType,
@@ -136,11 +136,9 @@ class RegionTable(BaseTable):
 
 class SiteTable(BaseTable):
     pk = ToggleColumn()
-    name = tables.LinkColumn('dcim:site', args=[Accessor('slug')], verbose_name='Name')
-    facility = tables.Column(verbose_name='Facility')
-    region = tables.TemplateColumn(template_code=SITE_REGION_LINK, verbose_name='Region')
-    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
-    asn = tables.Column(verbose_name='ASN')
+    name = tables.LinkColumn()
+    region = tables.TemplateColumn(template_code=SITE_REGION_LINK)
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
     rack_count = tables.Column(accessor=Accessor('count_racks'), orderable=False, verbose_name='Racks')
     device_count = tables.Column(accessor=Accessor('count_devices'), orderable=False, verbose_name='Devices')
     prefix_count = tables.Column(accessor=Accessor('count_prefixes'), orderable=False, verbose_name='Prefixes')
@@ -155,6 +153,16 @@ class SiteTable(BaseTable):
         )
 
 
+class SiteSearchTable(SearchTable):
+    name = tables.LinkColumn()
+    region = tables.TemplateColumn(template_code=SITE_REGION_LINK)
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
+
+    class Meta(SearchTable.Meta):
+        model = Site
+        fields = ('name', 'facility', 'region', 'tenant', 'asn')
+
+
 #
 # Rack groups
 #
@@ -197,20 +205,33 @@ class RackRoleTable(BaseTable):
 
 class RackTable(BaseTable):
     pk = ToggleColumn()
-    name = tables.LinkColumn('dcim:rack', args=[Accessor('pk')], verbose_name='Name')
-    site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
+    name = tables.LinkColumn()
+    site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
     group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
-    facility_id = tables.Column(verbose_name='Facility ID')
-    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
-    role = tables.TemplateColumn(RACK_ROLE, verbose_name='Role')
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
+    role = tables.TemplateColumn(RACK_ROLE)
     u_height = tables.TemplateColumn("{{ record.u_height }}U", verbose_name='Height')
-    devices = tables.Column(accessor=Accessor('device_count'), verbose_name='Devices')
+    devices = tables.Column(accessor=Accessor('device_count'))
     get_utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Utilization')
 
     class Meta(BaseTable.Meta):
         model = Rack
-        fields = ('pk', 'name', 'site', 'group', 'facility_id', 'tenant', 'role', 'u_height', 'devices',
-                  'get_utilization')
+        fields = (
+            'pk', 'name', 'site', 'group', 'facility_id', 'tenant', 'role', 'u_height', 'devices', 'get_utilization'
+        )
+
+
+class RackSearchTable(SearchTable):
+    name = tables.LinkColumn()
+    site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
+    group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
+    role = tables.TemplateColumn(RACK_ROLE)
+    u_height = tables.TemplateColumn("{{ record.u_height }}U", verbose_name='Height')
+
+    class Meta(SearchTable.Meta):
+        model = Rack
+        fields = ('name', 'site', 'group', 'facility_id', 'tenant', 'role', 'u_height')
 
 
 class RackImportTable(BaseTable):
@@ -249,9 +270,7 @@ class ManufacturerTable(BaseTable):
 
 class DeviceTypeTable(BaseTable):
     pk = ToggleColumn()
-    manufacturer = tables.Column(verbose_name='Manufacturer')
     model = tables.LinkColumn('dcim:devicetype', args=[Accessor('pk')], verbose_name='Device Type')
-    part_number = tables.Column(verbose_name='Part Number')
     is_full_depth = tables.BooleanColumn(verbose_name='Full Depth')
     is_console_server = tables.BooleanColumn(verbose_name='CS')
     is_pdu = tables.BooleanColumn(verbose_name='PDU')
@@ -267,6 +286,22 @@ class DeviceTypeTable(BaseTable):
         )
 
 
+class DeviceTypeSearchTable(SearchTable):
+    model = tables.LinkColumn('dcim:devicetype', args=[Accessor('pk')], verbose_name='Device Type')
+    is_full_depth = tables.BooleanColumn(verbose_name='Full Depth')
+    is_console_server = tables.BooleanColumn(verbose_name='CS')
+    is_pdu = tables.BooleanColumn(verbose_name='PDU')
+    is_network_device = tables.BooleanColumn(verbose_name='Net')
+    subdevice_role = tables.TemplateColumn(SUBDEVICE_ROLE_TEMPLATE, verbose_name='Subdevice Role')
+
+    class Meta(SearchTable.Meta):
+        model = DeviceType
+        fields = (
+            'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
+            'is_network_device', 'subdevice_role',
+        )
+
+
 #
 # Device type components
 #
@@ -373,22 +408,42 @@ class PlatformTable(BaseTable):
 
 class DeviceTable(BaseTable):
     pk = ToggleColumn()
+    name = tables.TemplateColumn(template_code=DEVICE_LINK)
     status = tables.TemplateColumn(template_code=STATUS_ICON, verbose_name='')
-    name = tables.TemplateColumn(template_code=DEVICE_LINK, verbose_name='Name')
-    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
-    site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
-    rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack')
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
+    site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
+    rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
     device_role = tables.TemplateColumn(DEVICE_ROLE, verbose_name='Role')
-    device_type = tables.LinkColumn('dcim:devicetype', args=[Accessor('device_type.pk')], verbose_name='Type',
-                                    text=lambda record: record.device_type.full_name)
-    primary_ip = tables.TemplateColumn(orderable=False, verbose_name='IP Address',
-                                       template_code=DEVICE_PRIMARY_IP)
+    device_type = tables.LinkColumn(
+        'dcim:devicetype', args=[Accessor('device_type.pk')], verbose_name='Type',
+        text=lambda record: record.device_type.full_name
+    )
+    primary_ip = tables.TemplateColumn(
+        orderable=False, verbose_name='IP Address', template_code=DEVICE_PRIMARY_IP
+    )
 
     class Meta(BaseTable.Meta):
         model = Device
         fields = ('pk', 'name', 'status', 'tenant', 'site', 'rack', 'device_role', 'device_type', 'primary_ip')
 
 
+class DeviceSearchTable(SearchTable):
+    name = tables.TemplateColumn(template_code=DEVICE_LINK)
+    status = tables.TemplateColumn(template_code=STATUS_ICON, verbose_name='')
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
+    site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
+    rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')])
+    device_role = tables.TemplateColumn(DEVICE_ROLE, verbose_name='Role')
+    device_type = tables.LinkColumn(
+        'dcim:devicetype', args=[Accessor('device_type.pk')], verbose_name='Type',
+        text=lambda record: record.device_type.full_name
+    )
+
+    class Meta(SearchTable.Meta):
+        model = Device
+        fields = ('name', 'status', 'tenant', 'site', 'rack', 'device_role', 'device_type')
+
+
 class DeviceImportTable(BaseTable):
     name = tables.TemplateColumn(template_code=DEVICE_LINK, verbose_name='Name')
     tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')

+ 86 - 36
netbox/ipam/tables.py

@@ -1,7 +1,7 @@
 import django_tables2 as tables
 from django_tables2.utils import Accessor
 
-from utilities.tables import BaseTable, ToggleColumn
+from utilities.tables import BaseTable, SearchTable, ToggleColumn
 
 from .models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VLANGroup, VRF
 
@@ -133,16 +133,25 @@ TENANT_LINK = """
 
 class VRFTable(BaseTable):
     pk = ToggleColumn()
-    name = tables.LinkColumn('ipam:vrf', args=[Accessor('pk')], verbose_name='Name')
+    name = tables.LinkColumn()
     rd = tables.Column(verbose_name='RD')
-    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
-    description = tables.Column(verbose_name='Description')
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
 
     class Meta(BaseTable.Meta):
         model = VRF
         fields = ('pk', 'name', 'rd', 'tenant', 'description')
 
 
+class VRFSearchTable(SearchTable):
+    name = tables.LinkColumn()
+    rd = tables.Column(verbose_name='RD')
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
+
+    class Meta(SearchTable.Meta):
+        model = VRF
+        fields = ('name', 'rd', 'tenant', 'description')
+
+
 #
 # RIRs
 #
@@ -177,18 +186,25 @@ class RIRTable(BaseTable):
 
 class AggregateTable(BaseTable):
     pk = ToggleColumn()
-    prefix = tables.LinkColumn('ipam:aggregate', args=[Accessor('pk')], verbose_name='Aggregate')
-    rir = tables.Column(verbose_name='RIR')
+    prefix = tables.LinkColumn(verbose_name='Aggregate')
     child_count = tables.Column(verbose_name='Prefixes')
     get_utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Utilization')
     date_added = tables.DateColumn(format="Y-m-d", verbose_name='Added')
-    description = tables.Column(verbose_name='Description')
 
     class Meta(BaseTable.Meta):
         model = Aggregate
         fields = ('pk', 'prefix', 'rir', 'child_count', 'get_utilization', 'date_added', 'description')
 
 
+class AggregateSearchTable(SearchTable):
+    prefix = tables.LinkColumn(verbose_name='Aggregate')
+    date_added = tables.DateColumn(format="Y-m-d", verbose_name='Added')
+
+    class Meta(SearchTable.Meta):
+        model = Aggregate
+        fields = ('prefix', 'rir', 'date_added', 'description')
+
+
 #
 # Roles
 #
@@ -212,14 +228,13 @@ class RoleTable(BaseTable):
 
 class PrefixTable(BaseTable):
     pk = ToggleColumn()
-    status = tables.TemplateColumn(STATUS_LABEL, verbose_name='Status')
-    prefix = tables.TemplateColumn(PREFIX_LINK, verbose_name='Prefix', attrs={'th': {'style': 'padding-left: 17px'}})
+    prefix = tables.TemplateColumn(PREFIX_LINK, attrs={'th': {'style': 'padding-left: 17px'}})
+    status = tables.TemplateColumn(STATUS_LABEL)
     vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
-    tenant = tables.TemplateColumn(TENANT_LINK, verbose_name='Tenant')
-    site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
+    tenant = tables.TemplateColumn(TENANT_LINK)
+    site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
     vlan = tables.LinkColumn('ipam:vlan', args=[Accessor('vlan.pk')], verbose_name='VLAN')
-    role = tables.TemplateColumn(PREFIX_ROLE_LINK, verbose_name='Role')
-    description = tables.Column(verbose_name='Description')
+    role = tables.TemplateColumn(PREFIX_ROLE_LINK)
 
     class Meta(BaseTable.Meta):
         model = Prefix
@@ -230,12 +245,11 @@ class PrefixTable(BaseTable):
 
 
 class PrefixBriefTable(BaseTable):
-    prefix = tables.TemplateColumn(PREFIX_LINK_BRIEF, verbose_name='Prefix')
-    vrf = tables.LinkColumn('ipam:vrf', args=[Accessor('vrf.pk')], default='Global', verbose_name='VRF')
-    site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
-    status = tables.TemplateColumn(STATUS_LABEL, verbose_name='Status')
-    vlan = tables.LinkColumn('ipam:vlan', args=[Accessor('vlan.pk')], verbose_name='VLAN')
-    role = tables.Column(verbose_name='Role')
+    prefix = tables.TemplateColumn(PREFIX_LINK_BRIEF)
+    vrf = tables.LinkColumn('ipam:vrf', args=[Accessor('vrf.pk')], default='Global')
+    site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
+    status = tables.TemplateColumn(STATUS_LABEL)
+    vlan = tables.LinkColumn('ipam:vlan', args=[Accessor('vlan.pk')])
 
     class Meta(BaseTable.Meta):
         model = Prefix
@@ -243,6 +257,20 @@ class PrefixBriefTable(BaseTable):
         orderable = False
 
 
+class PrefixSearchTable(SearchTable):
+    prefix = tables.TemplateColumn(PREFIX_LINK, attrs={'th': {'style': 'padding-left: 17px'}})
+    status = tables.TemplateColumn(STATUS_LABEL)
+    vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
+    tenant = tables.TemplateColumn(TENANT_LINK)
+    site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
+    vlan = tables.LinkColumn('ipam:vlan', args=[Accessor('vlan.pk')], verbose_name='VLAN')
+    role = tables.TemplateColumn(PREFIX_ROLE_LINK)
+
+    class Meta(SearchTable.Meta):
+        model = Prefix
+        fields = ('prefix', 'status', 'vrf', 'tenant', 'site', 'vlan', 'role', 'description')
+
+
 #
 # IPAddresses
 #
@@ -250,13 +278,11 @@ class PrefixBriefTable(BaseTable):
 class IPAddressTable(BaseTable):
     pk = ToggleColumn()
     address = tables.TemplateColumn(IPADDRESS_LINK, verbose_name='IP Address')
-    status = tables.TemplateColumn(STATUS_LABEL, verbose_name='Status')
+    status = tables.TemplateColumn(STATUS_LABEL)
     vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
-    tenant = tables.TemplateColumn(TENANT_LINK, verbose_name='Tenant')
-    device = tables.LinkColumn('dcim:device', args=[Accessor('interface.device.pk')], orderable=False,
-                               verbose_name='Device')
-    interface = tables.Column(orderable=False, verbose_name='Interface')
-    description = tables.Column(verbose_name='Description')
+    tenant = tables.TemplateColumn(TENANT_LINK)
+    device = tables.LinkColumn('dcim:device', args=[Accessor('interface.device.pk')], orderable=False)
+    interface = tables.Column(orderable=False)
 
     class Meta(BaseTable.Meta):
         model = IPAddress
@@ -268,17 +294,30 @@ class IPAddressTable(BaseTable):
 
 class IPAddressBriefTable(BaseTable):
     address = tables.LinkColumn('ipam:ipaddress', args=[Accessor('pk')], verbose_name='IP Address')
-    device = tables.LinkColumn('dcim:device', args=[Accessor('interface.device.pk')], orderable=False,
-                               verbose_name='Device')
-    interface = tables.Column(orderable=False, verbose_name='Interface')
-    nat_inside = tables.LinkColumn('ipam:ipaddress', args=[Accessor('nat_inside.pk')], orderable=False,
-                                   verbose_name='NAT (Inside)')
+    device = tables.LinkColumn('dcim:device', args=[Accessor('interface.device.pk')], orderable=False)
+    interface = tables.Column(orderable=False)
+    nat_inside = tables.LinkColumn(
+        'ipam:ipaddress', args=[Accessor('nat_inside.pk')], orderable=False, verbose_name='NAT (Inside)'
+    )
 
     class Meta(BaseTable.Meta):
         model = IPAddress
         fields = ('address', 'device', 'interface', 'nat_inside')
 
 
+class IPAddressSearchTable(SearchTable):
+    address = tables.TemplateColumn(IPADDRESS_LINK, verbose_name='IP Address')
+    status = tables.TemplateColumn(STATUS_LABEL)
+    vrf = tables.TemplateColumn(VRF_LINK, verbose_name='VRF')
+    tenant = tables.TemplateColumn(TENANT_LINK)
+    device = tables.LinkColumn('dcim:device', args=[Accessor('interface.device.pk')], orderable=False)
+    interface = tables.Column(orderable=False)
+
+    class Meta(SearchTable.Meta):
+        model = IPAddress
+        fields = ('address', 'status', 'vrf', 'tenant', 'device', 'interface', 'description')
+
+
 #
 # VLAN groups
 #
@@ -304,15 +343,26 @@ class VLANGroupTable(BaseTable):
 class VLANTable(BaseTable):
     pk = ToggleColumn()
     vid = tables.LinkColumn('ipam:vlan', args=[Accessor('pk')], verbose_name='ID')
-    site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
+    site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
     group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
-    name = tables.Column(verbose_name='Name')
     prefixes = tables.TemplateColumn(VLAN_PREFIXES, orderable=False, verbose_name='Prefixes')
-    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
-    status = tables.TemplateColumn(STATUS_LABEL, verbose_name='Status')
-    role = tables.TemplateColumn(VLAN_ROLE_LINK, verbose_name='Role')
-    description = tables.Column(verbose_name='Description')
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
+    status = tables.TemplateColumn(STATUS_LABEL)
+    role = tables.TemplateColumn(VLAN_ROLE_LINK)
 
     class Meta(BaseTable.Meta):
         model = VLAN
         fields = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description')
+
+
+class VLANSearchTable(SearchTable):
+    vid = tables.LinkColumn('ipam:vlan', args=[Accessor('pk')], verbose_name='ID')
+    site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
+    group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')])
+    status = tables.TemplateColumn(STATUS_LABEL)
+    role = tables.TemplateColumn(VLAN_ROLE_LINK)
+
+    class Meta(SearchTable.Meta):
+        model = VLAN
+        fields = ('vid', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description')

+ 34 - 35
netbox/netbox/views.py

@@ -4,26 +4,25 @@ from rest_framework.views import APIView
 from rest_framework.response import Response
 from rest_framework.reverse import reverse
 
-from django.db.models import Count
 from django.shortcuts import render
 from django.views.generic import View
 
 from circuits.filters import CircuitFilter, ProviderFilter
 from circuits.models import Circuit, Provider
-from circuits.tables import CircuitTable, ProviderTable
+from circuits.tables import CircuitSearchTable, ProviderSearchTable
 from dcim.filters import DeviceFilter, DeviceTypeFilter, RackFilter, SiteFilter
 from dcim.models import ConsolePort, Device, DeviceType, InterfaceConnection, PowerPort, Rack, Site
-from dcim.tables import DeviceTable, DeviceTypeTable, RackTable, SiteTable
+from dcim.tables import DeviceSearchTable, DeviceTypeSearchTable, RackSearchTable, SiteSearchTable
 from extras.models import UserAction
 from ipam.filters import AggregateFilter, IPAddressFilter, PrefixFilter, VLANFilter, VRFFilter
 from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF
-from ipam.tables import AggregateTable, IPAddressTable, PrefixTable, VLANTable, VRFTable
+from ipam.tables import AggregateSearchTable, IPAddressSearchTable, PrefixSearchTable, VLANSearchTable, VRFSearchTable
 from secrets.filters import SecretFilter
 from secrets.models import Secret
-from secrets.tables import SecretTable
+from secrets.tables import SecretSearchTable
 from tenancy.filters import TenantFilter
 from tenancy.models import Tenant
-from tenancy.tables import TenantTable
+from tenancy.tables import TenantSearchTable
 from .forms import SearchForm
 
 
@@ -31,89 +30,85 @@ SEARCH_MAX_RESULTS = 15
 SEARCH_TYPES = {
     # Circuits
     'provider': {
-        'queryset': Provider.objects.annotate(count_circuits=Count('circuits')),
+        'queryset': Provider.objects.all(),
         'filter': ProviderFilter,
-        'table': ProviderTable,
+        'table': ProviderSearchTable,
         'url': 'circuits:provider_list',
     },
     'circuit': {
-        'queryset': Circuit.objects.select_related('provider', 'type', 'tenant').prefetch_related(
-            'terminations__site'
-        ),
+        'queryset': Circuit.objects.select_related('type', 'provider', 'tenant'),
         'filter': CircuitFilter,
-        'table': CircuitTable,
+        'table': CircuitSearchTable,
         'url': 'circuits:circuit_list',
     },
     # DCIM
     'site': {
         'queryset': Site.objects.select_related('region', 'tenant'),
         'filter': SiteFilter,
-        'table': SiteTable,
+        'table': SiteSearchTable,
         'url': 'dcim:site_list',
     },
     'rack': {
-        'queryset': Rack.objects.select_related('site', 'group', 'tenant', 'role').prefetch_related('devices__device_type').annotate(device_count=Count('devices', distinct=True)),
+        'queryset': Rack.objects.select_related('site', 'group', 'tenant', 'role'),
         'filter': RackFilter,
-        'table': RackTable,
+        'table': RackSearchTable,
         'url': 'dcim:rack_list',
     },
     'devicetype': {
-        'queryset': DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')),
+        'queryset': DeviceType.objects.select_related('manufacturer'),
         'filter': DeviceTypeFilter,
-        'table': DeviceTypeTable,
+        'table': DeviceTypeSearchTable,
         'url': 'dcim:devicetype_list',
     },
     'device': {
-        'queryset': Device.objects.select_related(
-            'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6'
-        ),
+        'queryset': Device.objects.select_related('device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack'),
         'filter': DeviceFilter,
-        'table': DeviceTable,
+        'table': DeviceSearchTable,
         'url': 'dcim:device_list',
     },
     # IPAM
     'vrf': {
         'queryset': VRF.objects.select_related('tenant'),
         'filter': VRFFilter,
-        'table': VRFTable,
+        'table': VRFSearchTable,
         'url': 'ipam:vrf_list',
     },
     'aggregate': {
         'queryset': Aggregate.objects.select_related('rir'),
         'filter': AggregateFilter,
-        'table': AggregateTable,
+        'table': AggregateSearchTable,
         'url': 'ipam:aggregate_list',
     },
     'prefix': {
         'queryset': Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role'),
         'filter': PrefixFilter,
-        'table': PrefixTable,
+        'table': PrefixSearchTable,
         'url': 'ipam:prefix_list',
     },
     'ipaddress': {
         'queryset': IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device'),
         'filter': IPAddressFilter,
-        'table': IPAddressTable,
+        'table': IPAddressSearchTable,
         'url': 'ipam:ipaddress_list',
     },
     'vlan': {
-        'queryset': VLAN.objects.select_related('site', 'group', 'tenant', 'role').prefetch_related('prefixes'),
+        'queryset': VLAN.objects.select_related('site', 'group', 'tenant', 'role'),
         'filter': VLANFilter,
-        'table': VLANTable,
+        'table': VLANSearchTable,
         'url': 'ipam:vlan_list',
     },
     # Secrets
     'secret': {
         'queryset': Secret.objects.select_related('role', 'device'),
         'filter': SecretFilter,
-        'table': SecretTable,
+        'table': SecretSearchTable,
         'url': 'secrets:secret_list',
     },
     # Tenancy
     'tenant': {
         'queryset': Tenant.objects.select_related('group'),
         'filter': TenantFilter,
-        'table': TenantTable,
+        'table': TenantSearchTable,
         'url': 'tenancy:tenant_list',
     },
 }
@@ -180,17 +175,21 @@ class SearchView(View):
                 obj_types = SEARCH_TYPES.keys()
 
             for obj_type in obj_types:
+
                 queryset = SEARCH_TYPES[obj_type]['queryset']
-                filter = SEARCH_TYPES[obj_type]['filter']
+                filter_cls = SEARCH_TYPES[obj_type]['filter']
                 table = SEARCH_TYPES[obj_type]['table']
                 url = SEARCH_TYPES[obj_type]['url']
-                filtered_queryset = filter({'q': form.cleaned_data['q']}, queryset=queryset).qs
-                total_count = filtered_queryset.count()
-                if total_count:
+
+                # Construct the results table for this object type
+                filtered_queryset = filter_cls({'q': form.cleaned_data['q']}, queryset=queryset).qs
+                table = table(filtered_queryset)
+                table.paginate(per_page=SEARCH_MAX_RESULTS)
+
+                if table.page:
                     results.append({
                         'name': queryset.model._meta.verbose_name_plural,
-                        'table': table(filtered_queryset[:SEARCH_MAX_RESULTS]),
-                        'total': total_count,
+                        'table': table,
                         'url': '{}?q={}'.format(reverse(url), form.cleaned_data['q'])
                     })
 

+ 9 - 5
netbox/secrets/tables.py

@@ -1,7 +1,7 @@
 import django_tables2 as tables
 from django_tables2.utils import Accessor
 
-from utilities.tables import BaseTable, ToggleColumn
+from utilities.tables import BaseTable, SearchTable, ToggleColumn
 
 from .models import SecretRole, Secret
 
@@ -36,11 +36,15 @@ class SecretRoleTable(BaseTable):
 
 class SecretTable(BaseTable):
     pk = ToggleColumn()
-    device = tables.LinkColumn('secrets:secret', args=[Accessor('pk')], verbose_name='Device')
-    role = tables.Column(verbose_name='Role')
-    name = tables.Column(verbose_name='Name')
-    last_updated = tables.DateTimeColumn(verbose_name='Last updated')
+    device = tables.LinkColumn()
 
     class Meta(BaseTable.Meta):
         model = Secret
         fields = ('pk', 'device', 'role', 'name', 'last_updated')
+
+
+class SecretSearchTable(SearchTable):
+
+    class Meta(SearchTable.Meta):
+        model = Secret
+        fields = ('device', 'role', 'name', 'last_updated')

+ 18 - 18
netbox/templates/search.html

@@ -1,6 +1,8 @@
 {% extends '_base.html' %}
 {% load form_helpers %}
 
+{% block title %}Search{% endblock %}
+
 {% block content %}
     {% if request.GET.q %}
         {% include 'search_form.html' with search_form=form %}
@@ -8,33 +10,31 @@
             <div class="row">
                 <div class="col-md-10">
                     {% for obj_type in results %}
-                        <h3 id="{{ obj_type.name }}">{{ obj_type.name|title }}</h3>
-                        {% include 'table.html' with table=obj_type.table %}
-                        {% if obj_type.total > obj_type.table.rows|length %}
+                        <h3 id="{{ obj_type.name }}">{{ obj_type.name }}</h3>
+                        {% include 'table.html' with table=obj_type.table hide_paginator=True %}
+                        {% if obj_type.table.page.has_next %}
                             <a href="{{ obj_type.url }}" class="btn btn-primary pull-right">
                                 <span class="fa fa-search" aria-hidden="true"></span>
-                                All {{ obj_type.total }} results
+                                All {{ obj_type.table.page.paginator.count }} results
                             </a>
                         {% endif %}
                     <div class="clearfix"></div>
                     {% endfor %}
                 </div>
                 <div class="col-md-2" style="padding-top: 20px;">
-                    {% if results %}
-                        <div class="panel panel-default">
-                            <div class="panel-heading">
-                                <strong>Search Results</strong>
-                            </div>
-                            <div class="list-group">
-                                {% for obj_type in results %}
-                                    <a href="#{{ obj_type.name }}" class="list-group-item">
-                                        {{ obj_type.name|title }}
-                                        <span class="badge">{{ obj_type.total }}</span>
-                                    </a>
-                                {% endfor %}
-                            </div>
+                    <div class="panel panel-default">
+                        <div class="panel-heading">
+                            <strong>Search Results</strong>
                         </div>
-                    {% endif %}
+                        <div class="list-group">
+                            {% for obj_type in results %}
+                                <a href="#{{ obj_type.name }}" class="list-group-item">
+                                    {{ obj_type.name }}
+                                    <span class="badge">{{ obj_type.table.page.paginator.count }}</span>
+                                </a>
+                            {% endfor %}
+                        </div>
+                    </div>
                 </div>
             </div>
         {% else %}

+ 3 - 1
netbox/templates/table.html

@@ -4,5 +4,7 @@
 {# Extends the stock django_tables2 template to provide custom formatting of the pagination controls #}
 
 {% block pagination %}
-    {% include 'paginator.html' %}
+    {% if not hide_paginator %}
+        {% include 'paginator.html' %}
+    {% endif %}
 {% endblock pagination %}

+ 9 - 4
netbox/tenancy/tables.py

@@ -1,7 +1,7 @@
 import django_tables2 as tables
 from django_tables2.utils import Accessor
 
-from utilities.tables import BaseTable, ToggleColumn
+from utilities.tables import BaseTable, SearchTable, ToggleColumn
 
 from .models import Tenant, TenantGroup
 
@@ -36,10 +36,15 @@ class TenantGroupTable(BaseTable):
 
 class TenantTable(BaseTable):
     pk = ToggleColumn()
-    name = tables.LinkColumn('tenancy:tenant', args=[Accessor('slug')], verbose_name='Name')
-    group = tables.Column(verbose_name='Group')
-    description = tables.Column(verbose_name='Description')
+    name = tables.LinkColumn()
 
     class Meta(BaseTable.Meta):
         model = Tenant
         fields = ('pk', 'name', 'group', 'description')
+
+
+class TenantSearchTable(SearchTable):
+
+    class Meta(SearchTable.Meta):
+        model = Tenant
+        fields = ('name', 'group', 'description')

+ 14 - 1
netbox/utilities/tables.py

@@ -4,7 +4,9 @@ from django.utils.safestring import mark_safe
 
 
 class BaseTable(tables.Table):
-
+    """
+    Default table for object lists
+    """
     def __init__(self, *args, **kwargs):
         super(BaseTable, self).__init__(*args, **kwargs)
 
@@ -18,6 +20,17 @@ class BaseTable(tables.Table):
         }
 
 
+class SearchTable(tables.Table):
+    """
+    Default table for search results
+    """
+    class Meta:
+        attrs = {
+            'class': 'table table-hover',
+        }
+        orderable = False
+
+
 class ToggleColumn(tables.CheckBoxColumn):
 
     def __init__(self, *args, **kwargs):