mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-24 16:26:09 -06:00
Initial work on new search backend
This commit is contained in:
parent
bd79a27e4d
commit
80bf6e7b2c
@ -1,34 +1,66 @@
|
|||||||
import circuits.filtersets
|
from circuits import filtersets, models
|
||||||
import circuits.tables
|
|
||||||
from circuits.models import Circuit, Provider, ProviderNetwork
|
|
||||||
from netbox.search import SearchIndex, register_search
|
from netbox.search import SearchIndex, register_search
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
@register_search()
|
||||||
class ProviderIndex(SearchIndex):
|
class CircuitIndex(SearchIndex):
|
||||||
model = Provider
|
model = models.Circuit
|
||||||
queryset = Provider.objects.annotate(count_circuits=count_related(Circuit, 'provider'))
|
fields = (
|
||||||
filterset = circuits.filtersets.ProviderFilterSet
|
('cid', 100),
|
||||||
table = circuits.tables.ProviderTable
|
('description', 500),
|
||||||
url = 'circuits:provider_list'
|
('comments', 1000),
|
||||||
|
)
|
||||||
|
queryset = models.Circuit.objects.prefetch_related(
|
||||||
|
'type', 'provider', 'tenant', 'tenant__group', 'terminations__site'
|
||||||
|
)
|
||||||
|
filterset = filtersets.CircuitFilterSet
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
@register_search()
|
||||||
class CircuitIndex(SearchIndex):
|
class CircuitTerminationIndex(SearchIndex):
|
||||||
model = Circuit
|
model = models.CircuitTermination
|
||||||
queryset = Circuit.objects.prefetch_related(
|
fields = (
|
||||||
'type', 'provider', 'tenant', 'tenant__group', 'terminations__site'
|
('xconnect_id', 300),
|
||||||
|
('pp_info', 300),
|
||||||
|
('description', 500),
|
||||||
|
('port_speed', 2000),
|
||||||
|
('upstream_speed', 2000),
|
||||||
)
|
)
|
||||||
filterset = circuits.filtersets.CircuitFilterSet
|
|
||||||
table = circuits.tables.CircuitTable
|
|
||||||
url = 'circuits:circuit_list'
|
@register_search()
|
||||||
|
class CircuitTypeIndex(SearchIndex):
|
||||||
|
model = models.CircuitType
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('slug', 100),
|
||||||
|
('description', 500),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_search()
|
||||||
|
class ProviderIndex(SearchIndex):
|
||||||
|
model = models.Provider
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('account', 200),
|
||||||
|
('comments', 1000),
|
||||||
|
)
|
||||||
|
queryset = models.Provider.objects.annotate(
|
||||||
|
count_circuits=count_related(models.Circuit, 'provider')
|
||||||
|
)
|
||||||
|
filterset = filtersets.ProviderFilterSet
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
@register_search()
|
||||||
class ProviderNetworkIndex(SearchIndex):
|
class ProviderNetworkIndex(SearchIndex):
|
||||||
model = ProviderNetwork
|
model = models.ProviderNetwork
|
||||||
queryset = ProviderNetwork.objects.prefetch_related('provider')
|
fields = (
|
||||||
filterset = circuits.filtersets.ProviderNetworkFilterSet
|
('name', 100),
|
||||||
table = circuits.tables.ProviderNetworkTable
|
('service_id', 200),
|
||||||
url = 'circuits:providernetwork_list'
|
('description', 500),
|
||||||
|
('comments', 1000),
|
||||||
|
)
|
||||||
|
queryset = models.ProviderNetwork.objects.prefetch_related('provider')
|
||||||
|
filterset = filtersets.ProviderNetworkFilterSet
|
||||||
|
@ -1,81 +1,28 @@
|
|||||||
import dcim.filtersets
|
from dcim import filtersets, models
|
||||||
import dcim.tables
|
|
||||||
from dcim.models import (
|
|
||||||
Cable,
|
|
||||||
Device,
|
|
||||||
DeviceType,
|
|
||||||
Location,
|
|
||||||
Module,
|
|
||||||
ModuleType,
|
|
||||||
PowerFeed,
|
|
||||||
Rack,
|
|
||||||
RackReservation,
|
|
||||||
Site,
|
|
||||||
VirtualChassis,
|
|
||||||
)
|
|
||||||
from netbox.search import SearchIndex, register_search
|
from netbox.search import SearchIndex, register_search
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
@register_search()
|
||||||
class SiteIndex(SearchIndex):
|
class CableIndex(SearchIndex):
|
||||||
model = Site
|
model = models.Cable
|
||||||
queryset = Site.objects.prefetch_related('region', 'tenant', 'tenant__group')
|
fields = (
|
||||||
filterset = dcim.filtersets.SiteFilterSet
|
('label', 100),
|
||||||
table = dcim.tables.SiteTable
|
|
||||||
url = 'dcim:site_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
|
||||||
class RackIndex(SearchIndex):
|
|
||||||
model = Rack
|
|
||||||
queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'tenant__group', 'role').annotate(
|
|
||||||
device_count=count_related(Device, 'rack')
|
|
||||||
)
|
)
|
||||||
filterset = dcim.filtersets.RackFilterSet
|
queryset = models.Cable.objects.all()
|
||||||
table = dcim.tables.RackTable
|
filterset = filtersets.CableFilterSet
|
||||||
url = 'dcim:rack_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
|
||||||
class RackReservationIndex(SearchIndex):
|
|
||||||
model = RackReservation
|
|
||||||
queryset = RackReservation.objects.prefetch_related('rack', 'user')
|
|
||||||
filterset = dcim.filtersets.RackReservationFilterSet
|
|
||||||
table = dcim.tables.RackReservationTable
|
|
||||||
url = 'dcim:rackreservation_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
|
||||||
class LocationIndex(SearchIndex):
|
|
||||||
model = Location
|
|
||||||
queryset = Location.objects.add_related_count(
|
|
||||||
Location.objects.add_related_count(Location.objects.all(), Device, 'location', 'device_count', cumulative=True),
|
|
||||||
Rack,
|
|
||||||
'location',
|
|
||||||
'rack_count',
|
|
||||||
cumulative=True,
|
|
||||||
).prefetch_related('site')
|
|
||||||
filterset = dcim.filtersets.LocationFilterSet
|
|
||||||
table = dcim.tables.LocationTable
|
|
||||||
url = 'dcim:location_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
|
||||||
class DeviceTypeIndex(SearchIndex):
|
|
||||||
model = DeviceType
|
|
||||||
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
|
||||||
instance_count=count_related(Device, 'device_type')
|
|
||||||
)
|
|
||||||
filterset = dcim.filtersets.DeviceTypeFilterSet
|
|
||||||
table = dcim.tables.DeviceTypeTable
|
|
||||||
url = 'dcim:devicetype_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
@register_search()
|
||||||
class DeviceIndex(SearchIndex):
|
class DeviceIndex(SearchIndex):
|
||||||
model = Device
|
model = models.Device
|
||||||
queryset = Device.objects.prefetch_related(
|
fields = (
|
||||||
|
('asset_tag', 50),
|
||||||
|
('serial', 60),
|
||||||
|
('name', 100),
|
||||||
|
('comments', 1000),
|
||||||
|
)
|
||||||
|
queryset = models.Device.objects.prefetch_related(
|
||||||
'device_type__manufacturer',
|
'device_type__manufacturer',
|
||||||
'device_role',
|
'device_role',
|
||||||
'tenant',
|
'tenant',
|
||||||
@ -85,59 +32,162 @@ class DeviceIndex(SearchIndex):
|
|||||||
'primary_ip4',
|
'primary_ip4',
|
||||||
'primary_ip6',
|
'primary_ip6',
|
||||||
)
|
)
|
||||||
filterset = dcim.filtersets.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = dcim.tables.DeviceTable
|
|
||||||
url = 'dcim:device_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
@register_search()
|
||||||
class ModuleTypeIndex(SearchIndex):
|
class DeviceRoleIndex(SearchIndex):
|
||||||
model = ModuleType
|
model = models.DeviceRole
|
||||||
queryset = ModuleType.objects.prefetch_related('manufacturer').annotate(
|
fields = (
|
||||||
instance_count=count_related(Module, 'module_type')
|
('name', 100),
|
||||||
|
('slug', 100),
|
||||||
|
('description', 500),
|
||||||
)
|
)
|
||||||
filterset = dcim.filtersets.ModuleTypeFilterSet
|
|
||||||
table = dcim.tables.ModuleTypeTable
|
|
||||||
url = 'dcim:moduletype_list'
|
@register_search()
|
||||||
|
class DeviceTypeIndex(SearchIndex):
|
||||||
|
model = models.DeviceType
|
||||||
|
fields = (
|
||||||
|
('model', 100),
|
||||||
|
('part_number', 200),
|
||||||
|
('comments', 1000),
|
||||||
|
)
|
||||||
|
queryset = models.DeviceType.objects.prefetch_related('manufacturer').annotate(
|
||||||
|
instance_count=count_related(models.Device, 'device_type')
|
||||||
|
)
|
||||||
|
filterset = filtersets.DeviceTypeFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
@register_search()
|
||||||
|
class LocationIndex(SearchIndex):
|
||||||
|
model = models.Location
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('slug', 100),
|
||||||
|
('description', 500),
|
||||||
|
)
|
||||||
|
queryset = models.Location.objects.add_related_count(
|
||||||
|
models.Location.objects.add_related_count(
|
||||||
|
models.Location.objects.all(), models.Device, 'location', 'device_count', cumulative=True
|
||||||
|
),
|
||||||
|
models.Rack,
|
||||||
|
'location',
|
||||||
|
'rack_count',
|
||||||
|
cumulative=True,
|
||||||
|
).prefetch_related('site')
|
||||||
|
filterset = filtersets.LocationFilterSet
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
@register_search()
|
||||||
class ModuleIndex(SearchIndex):
|
class ModuleIndex(SearchIndex):
|
||||||
model = Module
|
model = models.Module
|
||||||
queryset = Module.objects.prefetch_related(
|
fields = (
|
||||||
|
('asset_tag', 50),
|
||||||
|
('serial', 60),
|
||||||
|
('comments', 1000),
|
||||||
|
)
|
||||||
|
queryset = models.Module.objects.prefetch_related(
|
||||||
'module_type__manufacturer',
|
'module_type__manufacturer',
|
||||||
'device',
|
'device',
|
||||||
'module_bay',
|
'module_bay',
|
||||||
)
|
)
|
||||||
filterset = dcim.filtersets.ModuleFilterSet
|
filterset = filtersets.ModuleFilterSet
|
||||||
table = dcim.tables.ModuleTable
|
|
||||||
url = 'dcim:module_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
@register_search()
|
||||||
class VirtualChassisIndex(SearchIndex):
|
class ModuleTypeIndex(SearchIndex):
|
||||||
model = VirtualChassis
|
model = models.ModuleType
|
||||||
queryset = VirtualChassis.objects.prefetch_related('master').annotate(
|
fields = (
|
||||||
member_count=count_related(Device, 'virtual_chassis')
|
('model', 100),
|
||||||
|
('part_number', 200),
|
||||||
|
('comments', 1000),
|
||||||
)
|
)
|
||||||
filterset = dcim.filtersets.VirtualChassisFilterSet
|
queryset = models.ModuleType.objects.prefetch_related('manufacturer').annotate(
|
||||||
table = dcim.tables.VirtualChassisTable
|
instance_count=count_related(models.Module, 'module_type')
|
||||||
url = 'dcim:virtualchassis_list'
|
)
|
||||||
|
filterset = filtersets.ModuleTypeFilterSet
|
||||||
|
|
||||||
@register_search()
|
|
||||||
class CableIndex(SearchIndex):
|
|
||||||
model = Cable
|
|
||||||
queryset = Cable.objects.all()
|
|
||||||
filterset = dcim.filtersets.CableFilterSet
|
|
||||||
table = dcim.tables.CableTable
|
|
||||||
url = 'dcim:cable_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
@register_search()
|
||||||
class PowerFeedIndex(SearchIndex):
|
class PowerFeedIndex(SearchIndex):
|
||||||
model = PowerFeed
|
model = models.PowerFeed
|
||||||
queryset = PowerFeed.objects.all()
|
fields = (
|
||||||
filterset = dcim.filtersets.PowerFeedFilterSet
|
('name', 100),
|
||||||
table = dcim.tables.PowerFeedTable
|
('comments', 1000),
|
||||||
url = 'dcim:powerfeed_list'
|
)
|
||||||
|
queryset = models.PowerFeed.objects.all()
|
||||||
|
filterset = filtersets.PowerFeedFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
@register_search()
|
||||||
|
class RackIndex(SearchIndex):
|
||||||
|
model = models.Rack
|
||||||
|
fields = (
|
||||||
|
('asset_tag', 50),
|
||||||
|
('serial', 60),
|
||||||
|
('name', 100),
|
||||||
|
('facility_id', 100),
|
||||||
|
('comments', 1000),
|
||||||
|
)
|
||||||
|
queryset = models.Rack.objects.prefetch_related('site', 'location', 'tenant', 'tenant__group', 'role').annotate(
|
||||||
|
device_count=count_related(models.Device, 'rack')
|
||||||
|
)
|
||||||
|
filterset = filtersets.RackFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
@register_search()
|
||||||
|
class RackReservationIndex(SearchIndex):
|
||||||
|
model = models.RackReservation
|
||||||
|
fields = (
|
||||||
|
('description', 500),
|
||||||
|
)
|
||||||
|
queryset = models.RackReservation.objects.prefetch_related('rack', 'user')
|
||||||
|
filterset = filtersets.RackReservationFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
@register_search()
|
||||||
|
class RegionIndex(SearchIndex):
|
||||||
|
model = models.Region
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('slug', 100),
|
||||||
|
('description', 500)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_search()
|
||||||
|
class SiteIndex(SearchIndex):
|
||||||
|
model = models.Site
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('facility', 100),
|
||||||
|
('description', 500),
|
||||||
|
('physical_address', 1000),
|
||||||
|
('shipping_address', 1000),
|
||||||
|
)
|
||||||
|
queryset = models.Site.objects.prefetch_related('region', 'tenant', 'tenant__group')
|
||||||
|
filterset = filtersets.SiteFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
@register_search()
|
||||||
|
class SiteGroupIndex(SearchIndex):
|
||||||
|
model = models.SiteGroup
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('slug', 100),
|
||||||
|
('description', 500)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_search()
|
||||||
|
class VirtualChassisIndex(SearchIndex):
|
||||||
|
model = models.VirtualChassis
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('domain', 300)
|
||||||
|
)
|
||||||
|
queryset = models.VirtualChassis.objects.prefetch_related('master').annotate(
|
||||||
|
member_count=count_related(models.Device, 'virtual_chassis')
|
||||||
|
)
|
||||||
|
filterset = filtersets.VirtualChassisFilterSet
|
||||||
|
25
netbox/extras/management/commands/reindex.py
Normal file
25
netbox/extras/management/commands/reindex.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from extras.models import CachedValue
|
||||||
|
from extras.registry import registry
|
||||||
|
from netbox.search.backends import search_backend
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""Reindex cached search values"""
|
||||||
|
help = 'Reindex cached search values.'
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
|
||||||
|
self.stdout.write('Clearing cached values...', ending="\n")
|
||||||
|
CachedValue.objects.all().delete()
|
||||||
|
|
||||||
|
for app_label, models in registry['search'].items():
|
||||||
|
for name, idx in models.items():
|
||||||
|
self.stdout.write(f'Reindexing {app_label}.{name}...', ending="\n")
|
||||||
|
model = idx.model
|
||||||
|
for instance in model.objects.all():
|
||||||
|
search_backend.cache(model, instance)
|
||||||
|
|
||||||
|
cache_size = CachedValue.objects.count()
|
||||||
|
self.stdout.write(f'Done. Generated {cache_size} cached values', ending="\n")
|
31
netbox/extras/migrations/0079_search.py
Normal file
31
netbox/extras/migrations/0079_search.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 4.1.1 on 2022-10-10 18:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
('extras', '0078_unique_constraints'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CachedValue',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||||
|
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('object_id', models.PositiveBigIntegerField()),
|
||||||
|
('field', models.CharField(max_length=200)),
|
||||||
|
('type', models.CharField(max_length=30)),
|
||||||
|
('value', models.TextField()),
|
||||||
|
('weight', models.PositiveSmallIntegerField(default=1000)),
|
||||||
|
('object_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.contenttype')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('weight', 'pk'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -2,9 +2,11 @@ from .change_logging import ObjectChange
|
|||||||
from .configcontexts import ConfigContext, ConfigContextModel
|
from .configcontexts import ConfigContext, ConfigContextModel
|
||||||
from .customfields import CustomField
|
from .customfields import CustomField
|
||||||
from .models import *
|
from .models import *
|
||||||
|
from .search import *
|
||||||
from .tags import Tag, TaggedItem
|
from .tags import Tag, TaggedItem
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
'CachedValue',
|
||||||
'ConfigContext',
|
'ConfigContext',
|
||||||
'ConfigContextModel',
|
'ConfigContextModel',
|
||||||
'ConfigRevision',
|
'ConfigRevision',
|
||||||
|
40
netbox/extras/models/search.py
Normal file
40
netbox/extras/models/search.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'CachedValue',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CachedValue(models.Model):
|
||||||
|
timestamp = models.DateTimeField(
|
||||||
|
auto_now_add=True,
|
||||||
|
editable=False
|
||||||
|
)
|
||||||
|
object_type = models.ForeignKey(
|
||||||
|
to=ContentType,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='+'
|
||||||
|
)
|
||||||
|
object_id = models.PositiveBigIntegerField()
|
||||||
|
object = GenericForeignKey(
|
||||||
|
ct_field='object_type',
|
||||||
|
fk_field='object_id'
|
||||||
|
)
|
||||||
|
field = models.CharField(
|
||||||
|
max_length=200
|
||||||
|
)
|
||||||
|
type = models.CharField(
|
||||||
|
max_length=30
|
||||||
|
)
|
||||||
|
value = models.TextField()
|
||||||
|
weight = models.PositiveSmallIntegerField(
|
||||||
|
default=1000
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('weight', 'pk')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.object_type} {self.object_id}: {self.field}={self.value}'
|
@ -7,8 +7,9 @@ from netbox.search import SearchIndex, register_search
|
|||||||
@register_search()
|
@register_search()
|
||||||
class JournalEntryIndex(SearchIndex):
|
class JournalEntryIndex(SearchIndex):
|
||||||
model = JournalEntry
|
model = JournalEntry
|
||||||
|
fields = (
|
||||||
|
('comments', 1000),
|
||||||
|
)
|
||||||
queryset = JournalEntry.objects.prefetch_related('assigned_object', 'created_by')
|
queryset = JournalEntry.objects.prefetch_related('assigned_object', 'created_by')
|
||||||
filterset = extras.filtersets.JournalEntryFilterSet
|
filterset = extras.filtersets.JournalEntryFilterSet
|
||||||
table = extras.tables.JournalEntryTable
|
|
||||||
url = 'extras:journalentry_list'
|
|
||||||
category = 'Journal'
|
category = 'Journal'
|
||||||
|
@ -5,7 +5,6 @@ from .models import DummyModel
|
|||||||
class DummyModelIndex(SearchIndex):
|
class DummyModelIndex(SearchIndex):
|
||||||
model = DummyModel
|
model = DummyModel
|
||||||
queryset = DummyModel.objects.all()
|
queryset = DummyModel.objects.all()
|
||||||
url = 'plugins:dummy_plugin:dummy_models'
|
|
||||||
|
|
||||||
|
|
||||||
indexes = (
|
indexes = (
|
||||||
|
@ -4,66 +4,84 @@ from ipam.models import ASN, VLAN, VRF, Aggregate, IPAddress, Prefix, Service
|
|||||||
from netbox.search import SearchIndex, register_search
|
from netbox.search import SearchIndex, register_search
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
|
||||||
class VRFIndex(SearchIndex):
|
|
||||||
model = VRF
|
|
||||||
queryset = VRF.objects.prefetch_related('tenant', 'tenant__group')
|
|
||||||
filterset = ipam.filtersets.VRFFilterSet
|
|
||||||
table = ipam.tables.VRFTable
|
|
||||||
url = 'ipam:vrf_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
@register_search()
|
||||||
class AggregateIndex(SearchIndex):
|
class AggregateIndex(SearchIndex):
|
||||||
model = Aggregate
|
model = Aggregate
|
||||||
|
fields = (
|
||||||
|
('prefix', 100),
|
||||||
|
('description', 500),
|
||||||
|
('date_added', 2000),
|
||||||
|
)
|
||||||
queryset = Aggregate.objects.prefetch_related('rir')
|
queryset = Aggregate.objects.prefetch_related('rir')
|
||||||
filterset = ipam.filtersets.AggregateFilterSet
|
filterset = ipam.filtersets.AggregateFilterSet
|
||||||
table = ipam.tables.AggregateTable
|
|
||||||
url = 'ipam:aggregate_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
|
||||||
class PrefixIndex(SearchIndex):
|
|
||||||
model = Prefix
|
|
||||||
queryset = Prefix.objects.prefetch_related(
|
|
||||||
'site', 'vrf__tenant', 'tenant', 'tenant__group', 'vlan', 'role'
|
|
||||||
)
|
|
||||||
filterset = ipam.filtersets.PrefixFilterSet
|
|
||||||
table = ipam.tables.PrefixTable
|
|
||||||
url = 'ipam:prefix_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
|
||||||
class IPAddressIndex(SearchIndex):
|
|
||||||
model = IPAddress
|
|
||||||
queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant', 'tenant__group')
|
|
||||||
filterset = ipam.filtersets.IPAddressFilterSet
|
|
||||||
table = ipam.tables.IPAddressTable
|
|
||||||
url = 'ipam:ipaddress_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
|
||||||
class VLANIndex(SearchIndex):
|
|
||||||
model = VLAN
|
|
||||||
queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'tenant__group', 'role')
|
|
||||||
filterset = ipam.filtersets.VLANFilterSet
|
|
||||||
table = ipam.tables.VLANTable
|
|
||||||
url = 'ipam:vlan_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
@register_search()
|
||||||
class ASNIndex(SearchIndex):
|
class ASNIndex(SearchIndex):
|
||||||
model = ASN
|
model = ASN
|
||||||
|
fields = (
|
||||||
|
('asn', 100),
|
||||||
|
('description', 500),
|
||||||
|
)
|
||||||
queryset = ASN.objects.prefetch_related('rir', 'tenant', 'tenant__group')
|
queryset = ASN.objects.prefetch_related('rir', 'tenant', 'tenant__group')
|
||||||
filterset = ipam.filtersets.ASNFilterSet
|
filterset = ipam.filtersets.ASNFilterSet
|
||||||
table = ipam.tables.ASNTable
|
|
||||||
url = 'ipam:asn_list'
|
|
||||||
|
@register_search()
|
||||||
|
class IPAddressIndex(SearchIndex):
|
||||||
|
model = IPAddress
|
||||||
|
fields = (
|
||||||
|
('address', 100),
|
||||||
|
('dns_name', 300),
|
||||||
|
('description', 500),
|
||||||
|
)
|
||||||
|
queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant', 'tenant__group')
|
||||||
|
filterset = ipam.filtersets.IPAddressFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
@register_search()
|
||||||
|
class PrefixIndex(SearchIndex):
|
||||||
|
model = Prefix
|
||||||
|
fields = (
|
||||||
|
('prefix', 100),
|
||||||
|
('description', 500),
|
||||||
|
)
|
||||||
|
queryset = Prefix.objects.prefetch_related(
|
||||||
|
'site', 'vrf__tenant', 'tenant', 'tenant__group', 'vlan', 'role'
|
||||||
|
)
|
||||||
|
filterset = ipam.filtersets.PrefixFilterSet
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
@register_search()
|
||||||
class ServiceIndex(SearchIndex):
|
class ServiceIndex(SearchIndex):
|
||||||
model = Service
|
model = Service
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('description', 500),
|
||||||
|
)
|
||||||
queryset = Service.objects.prefetch_related('device', 'virtual_machine')
|
queryset = Service.objects.prefetch_related('device', 'virtual_machine')
|
||||||
filterset = ipam.filtersets.ServiceFilterSet
|
filterset = ipam.filtersets.ServiceFilterSet
|
||||||
table = ipam.tables.ServiceTable
|
|
||||||
url = 'ipam:service_list'
|
|
||||||
|
@register_search()
|
||||||
|
class VLANIndex(SearchIndex):
|
||||||
|
model = VLAN
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('vid', 100),
|
||||||
|
('description', 500),
|
||||||
|
)
|
||||||
|
queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'tenant__group', 'role')
|
||||||
|
filterset = ipam.filtersets.VLANFilterSet
|
||||||
|
|
||||||
|
|
||||||
|
@register_search()
|
||||||
|
class VRFIndex(SearchIndex):
|
||||||
|
model = VRF
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('rd', 200),
|
||||||
|
('description', 500),
|
||||||
|
)
|
||||||
|
queryset = VRF.objects.prefetch_related('tenant', 'tenant__group')
|
||||||
|
filterset = ipam.filtersets.VRFFilterSet
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from netbox.search.backends import default_search_engine
|
from netbox.search.backends import search_backend
|
||||||
from utilities.forms import BootstrapMixin
|
from utilities.forms import BootstrapMixin
|
||||||
|
|
||||||
from .base import *
|
from .base import *
|
||||||
@ -26,13 +26,13 @@ class SearchForm(BootstrapMixin, forms.Form):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields["obj_type"] = forms.ChoiceField(
|
self.fields["obj_type"] = forms.ChoiceField(
|
||||||
choices=default_search_engine.get_search_choices(),
|
choices=search_backend.get_search_choices(),
|
||||||
required=False,
|
required=False,
|
||||||
label='Type'
|
label='Type'
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_options(self):
|
def get_options(self):
|
||||||
if not self.options:
|
if not self.options:
|
||||||
self.options = build_options(default_search_engine.get_search_choices())
|
self.options = build_options(search_backend.get_search_choices())
|
||||||
|
|
||||||
return self.options
|
return self.options
|
||||||
|
@ -9,6 +9,7 @@ from django.db import models
|
|||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
from extras.choices import CustomFieldVisibilityChoices, ObjectChangeActionChoices
|
from extras.choices import CustomFieldVisibilityChoices, ObjectChangeActionChoices
|
||||||
|
from extras.registry import registry
|
||||||
from extras.utils import is_taggable, register_features
|
from extras.utils import is_taggable, register_features
|
||||||
from netbox.signals import post_clean
|
from netbox.signals import post_clean
|
||||||
from utilities.json import CustomFieldJSONEncoder
|
from utilities.json import CustomFieldJSONEncoder
|
||||||
|
@ -9,6 +9,7 @@ class SearchIndex:
|
|||||||
model: The model class for which this index is used.
|
model: The model class for which this index is used.
|
||||||
"""
|
"""
|
||||||
model = None
|
model = None
|
||||||
|
fields = ()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_category(cls):
|
def get_category(cls):
|
||||||
@ -19,6 +20,13 @@ class SearchIndex:
|
|||||||
return cls.category
|
return cls.category
|
||||||
return cls.model._meta.app_config.verbose_name
|
return cls.model._meta.app_config.verbose_name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_cache(cls, instance):
|
||||||
|
return [
|
||||||
|
(field, str(getattr(instance, field)), weight)
|
||||||
|
for field, weight in cls.fields
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def register_search():
|
def register_search():
|
||||||
def _wrapper(cls):
|
def _wrapper(cls):
|
||||||
|
@ -2,9 +2,11 @@ from collections import defaultdict
|
|||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.urls import reverse
|
from django.db.models.signals import post_save
|
||||||
|
|
||||||
|
from extras.models import CachedValue
|
||||||
from extras.registry import registry
|
from extras.registry import registry
|
||||||
from netbox.constants import SEARCH_MAX_RESULTS
|
from netbox.constants import SEARCH_MAX_RESULTS
|
||||||
|
|
||||||
@ -12,6 +14,13 @@ from netbox.constants import SEARCH_MAX_RESULTS
|
|||||||
_backends_cache = {}
|
_backends_cache = {}
|
||||||
|
|
||||||
|
|
||||||
|
def get_indexer(model):
|
||||||
|
app_label = model._meta.app_label
|
||||||
|
model_name = model._meta.model_name
|
||||||
|
|
||||||
|
return registry['search'][app_label][model_name]
|
||||||
|
|
||||||
|
|
||||||
class SearchEngineError(Exception):
|
class SearchEngineError(Exception):
|
||||||
"""Something went wrong with a search engine."""
|
"""Something went wrong with a search engine."""
|
||||||
pass
|
pass
|
||||||
@ -21,6 +30,11 @@ class SearchBackend:
|
|||||||
"""A search engine capable of performing multi-table searches."""
|
"""A search engine capable of performing multi-table searches."""
|
||||||
_search_choice_options = tuple()
|
_search_choice_options = tuple()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
# Connect cache handler to the model post-save signal
|
||||||
|
post_save.connect(self.cache)
|
||||||
|
|
||||||
def get_registry(self):
|
def get_registry(self):
|
||||||
r = {}
|
r = {}
|
||||||
for app_label, models in registry['search'].items():
|
for app_label, models in registry['search'].items():
|
||||||
@ -53,7 +67,8 @@ class SearchBackend:
|
|||||||
"""Execute a search query for the given value."""
|
"""Execute a search query for the given value."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def cache(self, instance):
|
@staticmethod
|
||||||
|
def cache(sender, instance, **kwargs):
|
||||||
"""Create or update the cached copy of an instance."""
|
"""Create or update the cached copy of an instance."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@ -70,7 +85,6 @@ class FilterSetSearchBackend(SearchBackend):
|
|||||||
for obj_type in search_registry.keys():
|
for obj_type in search_registry.keys():
|
||||||
|
|
||||||
queryset = search_registry[obj_type].queryset
|
queryset = search_registry[obj_type].queryset
|
||||||
url = search_registry[obj_type].url
|
|
||||||
|
|
||||||
# Restrict the queryset for the current user
|
# Restrict the queryset for the current user
|
||||||
if hasattr(queryset, 'restrict'):
|
if hasattr(queryset, 'restrict'):
|
||||||
@ -81,30 +95,51 @@ class FilterSetSearchBackend(SearchBackend):
|
|||||||
# This backend requires a FilterSet class for the model
|
# This backend requires a FilterSet class for the model
|
||||||
continue
|
continue
|
||||||
|
|
||||||
table = getattr(search_registry[obj_type], 'table', None)
|
queryset = filterset({'q': value}, queryset=queryset).qs[:SEARCH_MAX_RESULTS]
|
||||||
if not table:
|
|
||||||
# This backend requires a Table class for the model
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Construct the results table for this object type
|
results.extend([
|
||||||
filtered_queryset = filterset({'q': value}, queryset=queryset).qs
|
{'object': obj}
|
||||||
table = table(filtered_queryset, orderable=False)
|
for obj in queryset
|
||||||
table.paginate(per_page=SEARCH_MAX_RESULTS)
|
])
|
||||||
|
|
||||||
if table.page:
|
|
||||||
results.append({
|
|
||||||
'name': queryset.model._meta.verbose_name_plural,
|
|
||||||
'table': table,
|
|
||||||
'url': f"{reverse(url)}?q={value}"
|
|
||||||
})
|
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def cache(self, instance):
|
@staticmethod
|
||||||
|
def cache(sender, instance, **kwargs):
|
||||||
# This backend does not utilize a cache
|
# This backend does not utilize a cache
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CachedValueSearchBackend(SearchBackend):
|
||||||
|
|
||||||
|
def search(self, request, value, **kwargs):
|
||||||
|
return CachedValue.objects.filter(value__icontains=value)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def cache(sender, instance, **kwargs):
|
||||||
|
try:
|
||||||
|
indexer = get_indexer(instance)
|
||||||
|
except KeyError:
|
||||||
|
return
|
||||||
|
|
||||||
|
data = indexer.to_cache(instance)
|
||||||
|
|
||||||
|
for field, value, weight in data:
|
||||||
|
if not value:
|
||||||
|
continue
|
||||||
|
ct = ContentType.objects.get_for_model(instance)
|
||||||
|
CachedValue.objects.update_or_create(
|
||||||
|
defaults={
|
||||||
|
'value': value,
|
||||||
|
'weight': weight,
|
||||||
|
},
|
||||||
|
object_type=ct,
|
||||||
|
object_id=instance.pk,
|
||||||
|
field=field,
|
||||||
|
type='text' # TODO
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_backend():
|
def get_backend():
|
||||||
"""Initializes and returns the configured search backend."""
|
"""Initializes and returns the configured search backend."""
|
||||||
backend_name = settings.SEARCH_BACKEND
|
backend_name = settings.SEARCH_BACKEND
|
||||||
@ -121,5 +156,4 @@ def get_backend():
|
|||||||
return backend_cls()
|
return backend_cls()
|
||||||
|
|
||||||
|
|
||||||
default_search_engine = get_backend()
|
search_backend = get_backend()
|
||||||
search = default_search_engine.search
|
|
||||||
|
@ -23,7 +23,7 @@ from extras.tables import ObjectChangeTable
|
|||||||
from ipam.models import Aggregate, IPAddress, IPRange, Prefix, VLAN, VRF
|
from ipam.models import Aggregate, IPAddress, IPRange, Prefix, VLAN, VRF
|
||||||
from netbox.constants import SEARCH_MAX_RESULTS
|
from netbox.constants import SEARCH_MAX_RESULTS
|
||||||
from netbox.forms import SearchForm
|
from netbox.forms import SearchForm
|
||||||
from netbox.search.backends import default_search_engine
|
from netbox.search.backends import search_backend
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from virtualization.models import Cluster, VirtualMachine
|
from virtualization.models import Cluster, VirtualMachine
|
||||||
from wireless.models import WirelessLAN, WirelessLink
|
from wireless.models import WirelessLAN, WirelessLink
|
||||||
@ -153,14 +153,14 @@ class SearchView(View):
|
|||||||
results = []
|
results = []
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
search_registry = default_search_engine.get_registry()
|
search_registry = search_backend.get_registry()
|
||||||
# If an object type has been specified, redirect to the dedicated view for it
|
# If an object type has been specified, redirect to the dedicated view for it
|
||||||
if form.cleaned_data['obj_type']:
|
if form.cleaned_data['obj_type']:
|
||||||
object_type = form.cleaned_data['obj_type']
|
object_type = form.cleaned_data['obj_type']
|
||||||
url = reverse(search_registry[object_type].url)
|
url = reverse(search_registry[object_type].url)
|
||||||
return redirect(f"{url}?q={form.cleaned_data['q']}")
|
return redirect(f"{url}?q={form.cleaned_data['q']}")
|
||||||
|
|
||||||
results = default_search_engine.search(request, form.cleaned_data['q'])
|
results = search_backend.search(request, form.cleaned_data['q'])
|
||||||
|
|
||||||
return render(request, 'search.html', {
|
return render(request, 'search.html', {
|
||||||
'form': form,
|
'form': form,
|
||||||
|
@ -20,44 +20,29 @@
|
|||||||
{% if request.GET.q %}
|
{% if request.GET.q %}
|
||||||
{% if results %}
|
{% if results %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-md-9">
|
<div class="col">
|
||||||
{% for obj_type in results %}
|
<div class="card">
|
||||||
<div class="card">
|
<div class="card-body table-responsive">
|
||||||
<h5 class="card-header" id="{{ obj_type.name|lower }}">{{ obj_type.name|bettertitle }}</h5>
|
<table class="table table-hover">
|
||||||
<div class="card-body table-responsive">
|
<tr>
|
||||||
{% render_table obj_type.table 'inc/table.html' %}
|
<th>Type</th>
|
||||||
</div>
|
<th>Object</th>
|
||||||
<div class="card-footer text-end">
|
<th>Field</th>
|
||||||
<a href="{{ obj_type.url }}" class="btn btn-sm btn-primary my-1">
|
<th>Value</th>
|
||||||
<i class="mdi mdi-arrow-right-bold" aria-hidden="true"></i>
|
</tr>
|
||||||
{% if obj_type.table.page.has_next %}
|
{% for result in results %}
|
||||||
See All {{ obj_type.table.page.paginator.count }} Results
|
<tr>
|
||||||
{% else %}
|
<td>{{ result.object|content_type }}</td>
|
||||||
Refine Search
|
<td>
|
||||||
{% endif %}
|
<a href="{{ result.object.get_absolute_url }}">{{ result.object }}</a>
|
||||||
</a>
|
</td>
|
||||||
</div>
|
<td>{{ result.field|placeholder }}</td>
|
||||||
</div>
|
<td>{{ result.value|placeholder }}</td>
|
||||||
{% endfor %}
|
</tr>
|
||||||
</div>
|
{% endfor %}
|
||||||
<div class="col col-md-3">
|
</table>
|
||||||
<div class="card">
|
|
||||||
<h5 class="card-header">
|
|
||||||
Search Results
|
|
||||||
</h5>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="list-group list-group-flush">
|
|
||||||
{% for obj_type in results %}
|
|
||||||
<a href="#{{ obj_type.name|lower }}" class="list-group-item">
|
|
||||||
<div class="float-end">
|
|
||||||
{% badge obj_type.table.page.paginator.count %}
|
|
||||||
</div>
|
|
||||||
{{ obj_type.name|bettertitle }}
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -5,21 +5,32 @@ from tenancy.models import Contact, ContactAssignment, Tenant
|
|||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
|
||||||
class TenantIndex(SearchIndex):
|
|
||||||
model = Tenant
|
|
||||||
queryset = Tenant.objects.prefetch_related('group')
|
|
||||||
filterset = tenancy.filtersets.TenantFilterSet
|
|
||||||
table = tenancy.tables.TenantTable
|
|
||||||
url = 'tenancy:tenant_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
@register_search()
|
||||||
class ContactIndex(SearchIndex):
|
class ContactIndex(SearchIndex):
|
||||||
model = Contact
|
model = Contact
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('title', 200),
|
||||||
|
('phone', 200),
|
||||||
|
('email', 200),
|
||||||
|
('address', 200),
|
||||||
|
('link', 300),
|
||||||
|
('comments', 1000),
|
||||||
|
)
|
||||||
queryset = Contact.objects.prefetch_related('group', 'assignments').annotate(
|
queryset = Contact.objects.prefetch_related('group', 'assignments').annotate(
|
||||||
assignment_count=count_related(ContactAssignment, 'contact')
|
assignment_count=count_related(ContactAssignment, 'contact')
|
||||||
)
|
)
|
||||||
filterset = tenancy.filtersets.ContactFilterSet
|
filterset = tenancy.filtersets.ContactFilterSet
|
||||||
table = tenancy.tables.ContactTable
|
|
||||||
url = 'tenancy:contact_list'
|
|
||||||
|
@register_search()
|
||||||
|
class TenantIndex(SearchIndex):
|
||||||
|
model = Tenant
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('slug', 100),
|
||||||
|
('description', 500),
|
||||||
|
('comments', 1000),
|
||||||
|
)
|
||||||
|
queryset = Tenant.objects.prefetch_related('group')
|
||||||
|
filterset = tenancy.filtersets.TenantFilterSet
|
||||||
|
@ -9,17 +9,23 @@ from virtualization.models import Cluster, VirtualMachine
|
|||||||
@register_search()
|
@register_search()
|
||||||
class ClusterIndex(SearchIndex):
|
class ClusterIndex(SearchIndex):
|
||||||
model = Cluster
|
model = Cluster
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('comments', 1000),
|
||||||
|
)
|
||||||
queryset = Cluster.objects.prefetch_related('type', 'group').annotate(
|
queryset = Cluster.objects.prefetch_related('type', 'group').annotate(
|
||||||
device_count=count_related(Device, 'cluster'), vm_count=count_related(VirtualMachine, 'cluster')
|
device_count=count_related(Device, 'cluster'), vm_count=count_related(VirtualMachine, 'cluster')
|
||||||
)
|
)
|
||||||
filterset = virtualization.filtersets.ClusterFilterSet
|
filterset = virtualization.filtersets.ClusterFilterSet
|
||||||
table = virtualization.tables.ClusterTable
|
|
||||||
url = 'virtualization:cluster_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
@register_search()
|
||||||
class VirtualMachineIndex(SearchIndex):
|
class VirtualMachineIndex(SearchIndex):
|
||||||
model = VirtualMachine
|
model = VirtualMachine
|
||||||
|
fields = (
|
||||||
|
('name', 100),
|
||||||
|
('comments', 1000),
|
||||||
|
)
|
||||||
queryset = VirtualMachine.objects.prefetch_related(
|
queryset = VirtualMachine.objects.prefetch_related(
|
||||||
'cluster',
|
'cluster',
|
||||||
'tenant',
|
'tenant',
|
||||||
@ -29,5 +35,3 @@ class VirtualMachineIndex(SearchIndex):
|
|||||||
'primary_ip6',
|
'primary_ip6',
|
||||||
)
|
)
|
||||||
filterset = virtualization.filtersets.VirtualMachineFilterSet
|
filterset = virtualization.filtersets.VirtualMachineFilterSet
|
||||||
table = virtualization.tables.VirtualMachineTable
|
|
||||||
url = 'virtualization:virtualmachine_list'
|
|
||||||
|
@ -9,18 +9,24 @@ from wireless.models import WirelessLAN, WirelessLink
|
|||||||
@register_search()
|
@register_search()
|
||||||
class WirelessLANIndex(SearchIndex):
|
class WirelessLANIndex(SearchIndex):
|
||||||
model = WirelessLAN
|
model = WirelessLAN
|
||||||
|
fields = (
|
||||||
|
('ssid', 100),
|
||||||
|
('description', 500),
|
||||||
|
('auth_psk', 1000),
|
||||||
|
)
|
||||||
queryset = WirelessLAN.objects.prefetch_related('group', 'vlan').annotate(
|
queryset = WirelessLAN.objects.prefetch_related('group', 'vlan').annotate(
|
||||||
interface_count=count_related(Interface, 'wireless_lans')
|
interface_count=count_related(Interface, 'wireless_lans')
|
||||||
)
|
)
|
||||||
filterset = wireless.filtersets.WirelessLANFilterSet
|
filterset = wireless.filtersets.WirelessLANFilterSet
|
||||||
table = wireless.tables.WirelessLANTable
|
|
||||||
url = 'wireless:wirelesslan_list'
|
|
||||||
|
|
||||||
|
|
||||||
@register_search()
|
@register_search()
|
||||||
class WirelessLinkIndex(SearchIndex):
|
class WirelessLinkIndex(SearchIndex):
|
||||||
model = WirelessLink
|
model = WirelessLink
|
||||||
|
fields = (
|
||||||
|
('ssid', 100),
|
||||||
|
('description', 500),
|
||||||
|
('auth_psk', 1000),
|
||||||
|
)
|
||||||
queryset = WirelessLink.objects.prefetch_related('interface_a__device', 'interface_b__device')
|
queryset = WirelessLink.objects.prefetch_related('interface_a__device', 'interface_b__device')
|
||||||
filterset = wireless.filtersets.WirelessLinkFilterSet
|
filterset = wireless.filtersets.WirelessLinkFilterSet
|
||||||
table = wireless.tables.WirelessLinkTable
|
|
||||||
url = 'wireless:wirelesslink_list'
|
|
||||||
|
Loading…
Reference in New Issue
Block a user