Merge branch 'global-search' into v2-develop

This commit is contained in:
Jeremy Stretch 2017-03-29 16:45:57 -04:00
commit aefc6ff7b4
18 changed files with 521 additions and 158 deletions

View File

@ -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')

View File

@ -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,
)

View File

@ -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')

View File

@ -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')

40
netbox/netbox/forms.py Normal file
View File

@ -0,0 +1,40 @@
from django import forms
from utilities.forms import BootstrapMixin
OBJ_TYPE_CHOICES = (
('', 'All Objects'),
('Circuits', (
('provider', 'Providers'),
('circuit', 'Circuits'),
)),
('DCIM', (
('site', 'Sites'),
('rack', 'Racks'),
('devicetype', 'Device types'),
('device', 'Devices'),
)),
('IPAM', (
('vrf', 'VRFs'),
('aggregate', 'Aggregates'),
('prefix', 'Prefixes'),
('ipaddress', 'IP addresses'),
('vlan', 'VLANs'),
)),
('Secrets', (
('secret', 'Secrets'),
)),
('Tenancy', (
('tenant', 'Tenants'),
)),
)
class SearchForm(BootstrapMixin, forms.Form):
q = forms.CharField(
label='Query', widget=forms.TextInput(attrs={'style': 'width: 350px'})
)
obj_type = forms.ChoiceField(
choices=OBJ_TYPE_CHOICES, required=False, label='Type'
)

View File

@ -2,7 +2,7 @@ from django.conf import settings
from django.conf.urls import include, url
from django.contrib import admin
from netbox.views import APIRootView, home, handle_500, trigger_500
from netbox.views import APIRootView, home, handle_500, SearchView, trigger_500
from users.views import login, logout
@ -10,8 +10,9 @@ handler500 = handle_500
_patterns = [
# Default page
# Base views
url(r'^$', home, name='home'),
url(r'^search/$', SearchView.as_view(), name='search'),
# Login/logout
url(r'^login/$', login, name='login'),

View File

@ -1,18 +1,117 @@
import sys
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.reverse import reverse
from django.shortcuts import render
from django.views.generic import View
from circuits.models import Provider, Circuit
from dcim.models import Site, Rack, Device, ConsolePort, PowerPort, InterfaceConnection
from circuits.filters import CircuitFilter, ProviderFilter
from circuits.models import Circuit, Provider
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 DeviceSearchTable, DeviceTypeSearchTable, RackSearchTable, SiteSearchTable
from extras.models import UserAction
from ipam.models import Aggregate, Prefix, IPAddress, VLAN, VRF
from ipam.filters import AggregateFilter, IPAddressFilter, PrefixFilter, VLANFilter, VRFFilter
from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF
from ipam.tables import AggregateSearchTable, IPAddressSearchTable, PrefixSearchTable, VLANSearchTable, VRFSearchTable
from secrets.filters import SecretFilter
from secrets.models import Secret
from secrets.tables import SecretSearchTable
from tenancy.filters import TenantFilter
from tenancy.models import Tenant
from tenancy.tables import TenantSearchTable
from .forms import SearchForm
SEARCH_MAX_RESULTS = 15
SEARCH_TYPES = {
# Circuits
'provider': {
'queryset': Provider.objects.all(),
'filter': ProviderFilter,
'table': ProviderSearchTable,
'url': 'circuits:provider_list',
},
'circuit': {
'queryset': Circuit.objects.select_related('type', 'provider', 'tenant'),
'filter': CircuitFilter,
'table': CircuitSearchTable,
'url': 'circuits:circuit_list',
},
# DCIM
'site': {
'queryset': Site.objects.select_related('region', 'tenant'),
'filter': SiteFilter,
'table': SiteSearchTable,
'url': 'dcim:site_list',
},
'rack': {
'queryset': Rack.objects.select_related('site', 'group', 'tenant', 'role'),
'filter': RackFilter,
'table': RackSearchTable,
'url': 'dcim:rack_list',
},
'devicetype': {
'queryset': DeviceType.objects.select_related('manufacturer'),
'filter': DeviceTypeFilter,
'table': DeviceTypeSearchTable,
'url': 'dcim:devicetype_list',
},
'device': {
'queryset': Device.objects.select_related('device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack'),
'filter': DeviceFilter,
'table': DeviceSearchTable,
'url': 'dcim:device_list',
},
# IPAM
'vrf': {
'queryset': VRF.objects.select_related('tenant'),
'filter': VRFFilter,
'table': VRFSearchTable,
'url': 'ipam:vrf_list',
},
'aggregate': {
'queryset': Aggregate.objects.select_related('rir'),
'filter': AggregateFilter,
'table': AggregateSearchTable,
'url': 'ipam:aggregate_list',
},
'prefix': {
'queryset': Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role'),
'filter': PrefixFilter,
'table': PrefixSearchTable,
'url': 'ipam:prefix_list',
},
'ipaddress': {
'queryset': IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device'),
'filter': IPAddressFilter,
'table': IPAddressSearchTable,
'url': 'ipam:ipaddress_list',
},
'vlan': {
'queryset': VLAN.objects.select_related('site', 'group', 'tenant', 'role'),
'filter': VLANFilter,
'table': VLANSearchTable,
'url': 'ipam:vlan_list',
},
# Secrets
'secret': {
'queryset': Secret.objects.select_related('role', 'device'),
'filter': SecretFilter,
'table': SecretSearchTable,
'url': 'secrets:secret_list',
},
# Tenancy
'tenant': {
'queryset': Tenant.objects.select_related('group'),
'filter': TenantFilter,
'table': TenantSearchTable,
'url': 'tenancy:tenant_list',
},
}
def home(request):
@ -47,11 +146,59 @@ def home(request):
}
return render(request, 'home.html', {
'search_form': SearchForm(),
'stats': stats,
'recent_activity': UserAction.objects.select_related('user')[:50]
})
class SearchView(View):
def get(self, request):
# No query
if 'q' not in request.GET:
return render(request, 'search.html', {
'form': SearchForm(),
})
form = SearchForm(request.GET)
results = []
if form.is_valid():
# Searching for a single type of object
if form.cleaned_data['obj_type']:
obj_types = [form.cleaned_data['obj_type']]
# Searching all object types
else:
obj_types = SEARCH_TYPES.keys()
for obj_type in obj_types:
queryset = SEARCH_TYPES[obj_type]['queryset']
filter_cls = SEARCH_TYPES[obj_type]['filter']
table = SEARCH_TYPES[obj_type]['table']
url = SEARCH_TYPES[obj_type]['url']
# 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,
'url': '{}?q={}'.format(reverse(url), form.cleaned_data['q'])
})
return render(request, 'search.html', {
'form': form,
'results': results,
})
class APIRootView(APIView):
_ignore_model_permissions = True
exclude_from_schema = True

View File

@ -92,6 +92,9 @@ tfoot td {
table.attr-table td:nth-child(1) {
width: 25%;
}
.table-headings th {
background-color: #f5f5f5;
}
/* Paginator */
div.paginator {

View File

@ -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')

View File

@ -48,7 +48,7 @@ class SecretRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@method_decorator(login_required, name='dispatch')
class SecretListView(ObjectListView):
queryset = Secret.objects.select_related('role').prefetch_related('device')
queryset = Secret.objects.select_related('role', 'device')
filter = filters.SecretFilter
filter_form = forms.SecretFilterForm
table = tables.SecretTable

View File

@ -2,64 +2,7 @@
{% load render_table from django_tables2 %}
{% block content %}
<div class="row home-search" style="padding: 15px 0px 20px">
<div class="col-sm-6 col-md-3">
<form action="{% url 'dcim:device_list' %}" method="get">
<div class="input-group">
<input type="text" name="q" placeholder="Search devices" class="form-control" />
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">
<span class="fa fa-search" aria-hidden="true"></span>
Devices
</button>
</span>
</div>
</form>
<p></p>
</div>
<div class="col-sm-6 col-md-3">
<form action="{% url 'ipam:prefix_list' %}" method="get">
<div class="input-group">
<input type="text" name="q" placeholder="Search prefixes" class="form-control" />
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">
<span class="fa fa-search" aria-hidden="true"></span>
Prefixes
</button>
</span>
</div>
</form>
<p></p>
</div>
<div class="col-sm-6 col-md-3">
<form action="{% url 'ipam:ipaddress_list' %}" method="get">
<div class="input-group">
<input type="text" name="q" placeholder="Search IPs" class="form-control" />
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">
<span class="fa fa-search" aria-hidden="true"></span>
IPs
</button>
</span>
</div>
</form>
<p></p>
</div>
<div class="col-sm-6 col-md-3">
<form action="{% url 'circuits:circuit_list' %}" method="get">
<div class="input-group">
<input type="text" name="q" placeholder="Search circuits" class="form-control" />
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">
<span class="fa fa-search" aria-hidden="true"></span>
Circuits
</button>
</span>
</div>
</form>
<p></p>
</div>
</div>
{% include 'search_form.html' %}
<div class="row">
<div class="col-sm-6 col-md-4">
<div class="panel panel-default">

View File

@ -20,6 +20,7 @@
{% endblock %}
{% block pagination %}
{% include 'paginator.html' %}
{% if not hide_paginator %}
{% include 'paginator.html' %}
{% endif %}
{% endblock pagination %}

View File

@ -0,0 +1,65 @@
{% extends '_base.html' %}
{% load helpers %}
{% load form_helpers %}
{% block title %}Search{% endblock %}
{% block content %}
{% if request.GET.q %}
{% include 'search_form.html' with search_form=form %}
{% if results %}
<div class="row">
<div class="col-md-10">
{% for obj_type in results %}
<h3 id="{{ obj_type.name|lower }}">{{ obj_type.name|bettertitle }}</h3>
{% include 'panel_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-arrow-right" aria-hidden="true"></span>
See 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;">
<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|lower }}" class="list-group-item">
{{ obj_type.name|bettertitle }}
<span class="badge">{{ obj_type.table.page.paginator.count }}</span>
</a>
{% endfor %}
</div>
</div>
</div>
</div>
{% else %}
<h3 class="text-muted text-center">No results found</h3>
{% endif %}
{% else %}
<div class="row" style="margin-top: 150px;">
<div class="col-sm-4 col-sm-offset-4">
<form action="{% url 'search' %}" method="get" class="form form-horizontal">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Search</strong>
</div>
<div class="panel-body">
{% render_form form %}
</div>
<div class="panel-footer text-right">
<button type="submit" class="btn btn-primary">
<span class="fa fa-search" aria-hidden="true"></span> Search
</button>
</div>
</div>
</form>
</div>
</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,9 @@
<div class="row" style="padding-bottom: 20px">
<div class="col-md-12 text-center">
<form action="{% url 'search' %}" method="get" class="form-inline">
{{ search_form.q }}
{{ search_form.obj_type }}
<button type="submit" class="btn btn-primary">Search</button>
</form>
</div>
</div>

View File

@ -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 %}

View File

@ -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')

View File

@ -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 table-headings',
}
orderable = False
class ToggleColumn(tables.CheckBoxColumn):
def __init__(self, *args, **kwargs):

View File

@ -51,6 +51,13 @@ def startswith(value, arg):
"""
return str(value).startswith(arg)
@register.filter()
def bettertitle(value):
"""
Alternative to the builtin title(); uppercases words without replacing letters that are already uppercase.
"""
return ' '.join([w[0].upper() + w[1:] for w in value.split()])
#
# Tags