8927 refactor search

This commit is contained in:
Arthur 2022-09-27 08:34:29 -07:00 committed by jeremystretch
parent 4fd183573f
commit 085883a5ff
11 changed files with 205 additions and 338 deletions

View File

@ -7,32 +7,29 @@ from utilities.utils import count_related
class ProviderIndex(SearchMixin): class ProviderIndex(SearchMixin):
def __init__(self): model = Provider
self.model = Provider queryset = Provider.objects.annotate(count_circuits=count_related(Circuit, 'provider'))
self.queryset = Provider.objects.annotate(count_circuits=count_related(Circuit, 'provider')) filterset = circuits.filtersets.ProviderFilterSet
self.filterset = circuits.filtersets.ProviderFilterSet table = circuits.tables.ProviderTable
self.table = circuits.tables.ProviderTable url = 'circuits:provider_list'
self.url = 'circuits:provider_list'
class CircuitIndex(SearchMixin): class CircuitIndex(SearchMixin):
def __init__(self): model = Circuit
self.model = Circuit queryset = Circuit.objects.prefetch_related(
self.queryset = Circuit.objects.prefetch_related( 'type', 'provider', 'tenant', 'tenant__group', 'terminations__site'
'type', 'provider', 'tenant', 'tenant__group', 'terminations__site' )
) filterset = circuits.filtersets.CircuitFilterSet
self.filterset = circuits.filtersets.CircuitFilterSet table = circuits.tables.CircuitTable
self.table = circuits.tables.CircuitTable url = 'circuits:circuit_list'
self.url = 'circuits:circuit_list'
class ProviderNetworkIndex(SearchMixin): class ProviderNetworkIndex(SearchMixin):
def __init__(self): model = ProviderNetwork
self.model = ProviderNetwork queryset = ProviderNetwork.objects.prefetch_related('provider')
self.queryset = ProviderNetwork.objects.prefetch_related('provider') filterset = circuits.filtersets.ProviderNetworkFilterSet
self.filterset = circuits.filtersets.ProviderNetworkFilterSet table = circuits.tables.ProviderNetworkTable
self.table = circuits.tables.ProviderNetworkTable url = 'circuits:providernetwork_list'
self.url = 'circuits:providernetwork_list'
CIRCUIT_SEARCH_TYPES = { CIRCUIT_SEARCH_TYPES = {

View File

@ -20,140 +20,118 @@ from utilities.utils import count_related
class SiteIndex(SearchMixin): class SiteIndex(SearchMixin):
model = Site
def __init__(self): queryset = Site.objects.prefetch_related('region', 'tenant', 'tenant__group')
self.model = Site filterset = dcim.filtersets.SiteFilterSet
self.queryset = Site.objects.prefetch_related('region', 'tenant', 'tenant__group') table = dcim.tables.SiteTable
self.filterset = dcim.filtersets.SiteFilterSet url = 'dcim:site_list'
self.table = dcim.tables.SiteTable
self.url = 'dcim:site_list'
class RackIndex(SearchMixin): class RackIndex(SearchMixin):
model = Rack
def __init__(self): queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'tenant__group', 'role').annotate(
self.model = Rack device_count=count_related(Device, 'rack')
self.queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'tenant__group', 'role').annotate( )
device_count=count_related(Device, 'rack') filterset = dcim.filtersets.RackFilterSet
) table = dcim.tables.RackTable
self.filterset = dcim.filtersets.RackFilterSet url = 'dcim:rack_list'
self.table = dcim.tables.RackTable
self.url = 'dcim:rack_list'
class RackReservationIndex(SearchMixin): class RackReservationIndex(SearchMixin):
model = RackReservation
def __init__(self): queryset = RackReservation.objects.prefetch_related('rack', 'user')
self.model = RackReservation filterset = dcim.filtersets.RackReservationFilterSet
self.queryset = RackReservation.objects.prefetch_related('rack', 'user') table = dcim.tables.RackReservationTable
self.filterset = dcim.filtersets.RackReservationFilterSet url = 'dcim:rackreservation_list'
self.table = dcim.tables.RackReservationTable
self.url = 'dcim:rackreservation_list'
class LocationIndex(SearchMixin): class LocationIndex(SearchMixin):
model = Site
def __init__(self): queryset = Location.objects.add_related_count(
self.model = Site Location.objects.add_related_count(Location.objects.all(), Device, 'location', 'device_count', cumulative=True),
self.queryset = Location.objects.add_related_count( Rack,
Location.objects.add_related_count(Location.objects.all(), Device, 'location', 'device_count', cumulative=True), 'location',
Rack, 'rack_count',
'location', cumulative=True,
'rack_count', ).prefetch_related('site')
cumulative=True, filterset = dcim.filtersets.LocationFilterSet
).prefetch_related('site') table = dcim.tables.LocationTable
self.filterset = dcim.filtersets.LocationFilterSet url = 'dcim:location_list'
self.table = dcim.tables.LocationTable
self.url = 'dcim:location_list'
class DeviceTypeIndex(SearchMixin): class DeviceTypeIndex(SearchMixin):
model = DeviceType
def __init__(self): queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
self.model = DeviceType instance_count=count_related(Device, 'device_type')
self.queryset = DeviceType.objects.prefetch_related('manufacturer').annotate( )
instance_count=count_related(Device, 'device_type') filterset = dcim.filtersets.DeviceTypeFilterSet
) table = dcim.tables.DeviceTypeTable
self.filterset = dcim.filtersets.DeviceTypeFilterSet url = 'dcim:devicetype_list'
self.table = dcim.tables.DeviceTypeTable
self.url = 'dcim:devicetype_list'
class DeviceIndex(SearchMixin): class DeviceIndex(SearchMixin):
model = Device
def __init__(self): queryset = Device.objects.prefetch_related(
self.model = DeviceIndex 'device_type__manufacturer',
self.queryset = Device.objects.prefetch_related( 'device_role',
'device_type__manufacturer', 'tenant',
'device_role', 'tenant__group',
'tenant', 'site',
'tenant__group', 'rack',
'site', 'primary_ip4',
'rack', 'primary_ip6',
'primary_ip4', )
'primary_ip6', filterset = dcim.filtersets.DeviceFilterSet
) table = dcim.tables.DeviceTable
self.filterset = dcim.filtersets.DeviceFilterSet url = 'dcim:device_list'
self.table = dcim.tables.DeviceTable
self.url = 'dcim:device_list'
class ModuleTypeIndex(SearchMixin): class ModuleTypeIndex(SearchMixin):
model = ModuleType
def __init__(self): queryset = ModuleType.objects.prefetch_related('manufacturer').annotate(
self.model = ModuleType instance_count=count_related(Module, 'module_type')
self.queryset = ModuleType.objects.prefetch_related('manufacturer').annotate( )
instance_count=count_related(Module, 'module_type') filterset = dcim.filtersets.ModuleTypeFilterSet
) table = dcim.tables.ModuleTypeTable
self.filterset = dcim.filtersets.ModuleTypeFilterSet url = 'dcim:moduletype_list'
self.table = dcim.tables.ModuleTypeTable
self.url = 'dcim:moduletype_list'
class ModuleIndex(SearchMixin): class ModuleIndex(SearchMixin):
model = Module
def __init__(self): queryset = Module.objects.prefetch_related(
self.model = Module 'module_type__manufacturer',
self.queryset = Module.objects.prefetch_related( 'device',
'module_type__manufacturer', 'module_bay',
'device', )
'module_bay', filterset = dcim.filtersets.ModuleFilterSet
) table = dcim.tables.ModuleTable
self.filterset = dcim.filtersets.ModuleFilterSet url = 'dcim:module_list'
self.table = dcim.tables.ModuleTable
self.url = 'dcim:module_list'
class VirtualChassisIndex(SearchMixin): class VirtualChassisIndex(SearchMixin):
model = VirtualChassis
def __init__(self): queryset = VirtualChassis.objects.prefetch_related('master').annotate(
self.model = VirtualChassis member_count=count_related(Device, 'virtual_chassis')
self.queryset = VirtualChassis.objects.prefetch_related('master').annotate( )
member_count=count_related(Device, 'virtual_chassis') filterset = dcim.filtersets.VirtualChassisFilterSet
) table = dcim.tables.VirtualChassisTable
self.filterset = dcim.filtersets.VirtualChassisFilterSet url = 'dcim:virtualchassis_list'
self.table = dcim.tables.VirtualChassisTable
self.url = 'dcim:virtualchassis_list'
class CableIndex(SearchMixin): class CableIndex(SearchMixin):
model = Cable
def __init__(self): queryset = Cable.objects.all()
self.model = Cable filterset = dcim.filtersets.CableFilterSet
self.queryset = Cable.objects.all() table = dcim.tables.CableTable
self.filterset = dcim.filtersets.CableFilterSet url = 'dcim:cable_list'
self.table = dcim.tables.CableTable
self.url = 'dcim:cable_list'
class PowerFeedIndex(SearchMixin): class PowerFeedIndex(SearchMixin):
model = PowerFeed
def __init__(self): queryset = PowerFeed.objects.all()
self.model = PowerFeed filterset = dcim.filtersets.PowerFeedFilterSet
self.queryset = PowerFeed.objects.all() table = dcim.tables.PowerFeedTable
self.filterset = dcim.filtersets.PowerFeedFilterSet url = 'dcim:powerfeed_list'
self.table = dcim.tables.PowerFeedTable
self.url = 'dcim:powerfeed_list'
DCIM_SEARCH_TYPES = { DCIM_SEARCH_TYPES = {

View File

@ -7,12 +7,11 @@ from utilities.utils import count_related
class JournalEntryIndex(SearchMixin): class JournalEntryIndex(SearchMixin):
def __init__(self): model = JournalEntry
self.model = JournalEntry queryset = JournalEntry.objects.prefetch_related('assigned_object', 'created_by')
self.queryset = JournalEntry.objects.prefetch_related('assigned_object', 'created_by') filterset = extras.filtersets.JournalEntryFilterSet
self.filterset = extras.filtersets.JournalEntryFilterSet table = extras.tables.JournalEntryTable
self.table = extras.tables.JournalEntryTable url = 'extras:journalentry_list'
self.url = 'extras:journalentry_list'
JOURNAL_SEARCH_TYPES = { JOURNAL_SEARCH_TYPES = {

View File

@ -7,68 +7,61 @@ from utilities.utils import count_related
class VRFIndex(SearchMixin): class VRFIndex(SearchMixin):
def __init__(self): model = VRF
self.model = VRF queryset = VRF.objects.prefetch_related('tenant', 'tenant__group')
self.queryset = VRF.objects.prefetch_related('tenant', 'tenant__group') filterset = ipam.filtersets.VRFFilterSet
self.filterset = ipam.filtersets.VRFFilterSet table = ipam.tables.VRFTable
self.table = ipam.tables.VRFTable url = 'ipam:vrf_list'
self.url = 'ipam:vrf_list'
class AggregateIndex(SearchMixin): class AggregateIndex(SearchMixin):
def __init__(self): model = Aggregate
self.model = Aggregate queryset = Aggregate.objects.prefetch_related('rir')
self.queryset = Aggregate.objects.prefetch_related('rir') filterset = ipam.filtersets.AggregateFilterSet
self.filterset = ipam.filtersets.AggregateFilterSet table = ipam.tables.AggregateTable
self.table = ipam.tables.AggregateTable url = 'ipam:aggregate_list'
self.url = 'ipam:aggregate_list'
class PrefixIndex(SearchMixin): class PrefixIndex(SearchMixin):
def __init__(self): model = Prefix
self.model = Prefix queryset = Prefix.objects.prefetch_related(
self.queryset = Prefix.objects.prefetch_related( 'site', 'vrf__tenant', 'tenant', 'tenant__group', 'vlan', 'role'
'site', 'vrf__tenant', 'tenant', 'tenant__group', 'vlan', 'role' )
) filterset = ipam.filtersets.PrefixFilterSet
self.filterset = ipam.filtersets.PrefixFilterSet table = ipam.tables.PrefixTable
self.table = ipam.tables.PrefixTable url = 'ipam:prefix_list'
self.url = 'ipam:prefix_list'
class IPAddressIndex(SearchMixin): class IPAddressIndex(SearchMixin):
def __init__(self): model = IPAddress
self.model = IPAddress queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant', 'tenant__group')
self.queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant', 'tenant__group') filterset = ipam.filtersets.IPAddressFilterSet
self.filterset = ipam.filtersets.IPAddressFilterSet table = ipam.tables.IPAddressTable
self.table = ipam.tables.IPAddressTable url = 'ipam:ipaddress_list'
self.url = 'ipam:ipaddress_list'
class VLANIndex(SearchMixin): class VLANIndex(SearchMixin):
def __init__(self): model = VLAN
self.model = VLAN queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'tenant__group', 'role')
self.queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'tenant__group', 'role') filterset = ipam.filtersets.VLANFilterSet
self.filterset = ipam.filtersets.VLANFilterSet table = ipam.tables.VLANTable
self.table = ipam.tables.VLANTable url = 'ipam:vlan_list'
self.url = 'ipam:vlan_list'
class ASNIndex(SearchMixin): class ASNIndex(SearchMixin):
def __init__(self): model = ASN
self.model = ASN queryset = ASN.objects.prefetch_related('rir', 'tenant', 'tenant__group')
self.queryset = ASN.objects.prefetch_related('rir', 'tenant', 'tenant__group') filterset = ipam.filtersets.ASNFilterSet
self.filterset = ipam.filtersets.ASNFilterSet table = ipam.tables.ASNTable
self.table = ipam.tables.ASNTable url = 'ipam:asn_list'
self.url = 'ipam:asn_list'
class ServiceIndex(SearchMixin): class ServiceIndex(SearchMixin):
def __init__(self): model = Service
self.model = Service queryset = Service.objects.prefetch_related('device', 'virtual_machine')
self.queryset = Service.objects.prefetch_related('device', 'virtual_machine') filterset = ipam.filtersets.ServiceFilterSet
self.filterset = ipam.filtersets.ServiceFilterSet table = ipam.tables.ServiceTable
self.table = ipam.tables.ServiceTable url = 'ipam:service_list'
self.url = 'ipam:service_list'
IPAM_SEARCH_TYPES = { IPAM_SEARCH_TYPES = {

View File

@ -1,7 +1,7 @@
from django import forms from django import forms
from utilities.forms import BootstrapMixin from utilities.forms import BootstrapMixin
from netbox.search import SEARCH_TYPE_HIERARCHY from search.backends import default_search_engine
from .base import * from .base import *
@ -9,7 +9,7 @@ from .base import *
def build_search_choices(): def build_search_choices():
result = list() result = list()
result.append(('', 'All Objects')) result.append(('', 'All Objects'))
for category, items in SEARCH_TYPE_HIERARCHY.items(): for category, items in default_search_engine.get_registry().items():
subcategories = list() subcategories = list()
for slug, obj in items.items(): for slug, obj in items.items():
name = obj.queryset.model._meta.verbose_name_plural name = obj.queryset.model._meta.verbose_name_plural

View File

@ -1,30 +0,0 @@
from circuits.search_indexes import CIRCUIT_SEARCH_TYPES
from dcim.search_indexes import DCIM_SEARCH_TYPES
from extras.search_indexes import JOURNAL_SEARCH_TYPES
from ipam.search_indexes import IPAM_SEARCH_TYPES
from tenancy.search_indexes import TENANCY_SEARCH_TYPES
from virtualization.search_indexes import VIRTUALIZATION_SEARCH_TYPES
from wireless.search_indexes import WIRELESS_SEARCH_TYPES
SEARCH_TYPE_HIERARCHY = {
'Circuits': CIRCUIT_SEARCH_TYPES,
'DCIM': DCIM_SEARCH_TYPES,
'IPAM': IPAM_SEARCH_TYPES,
'Tenancy': TENANCY_SEARCH_TYPES,
'Virtualization': VIRTUALIZATION_SEARCH_TYPES,
'Wireless': WIRELESS_SEARCH_TYPES,
'Journal': JOURNAL_SEARCH_TYPES,
}
def build_search_types():
result = dict()
for app_types in SEARCH_TYPE_HIERARCHY.values():
for name, items in app_types.items():
result[name] = items
return result
SEARCH_TYPES = build_search_types()

View File

@ -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 import SEARCH_TYPES from search.backends import default_search_engine
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

View File

@ -1,3 +1,5 @@
import inspect
import sys
from django.apps import AppConfig from django.apps import AppConfig
from django.apps import apps from django.apps import apps
@ -27,7 +29,18 @@ class SearchConfig(AppConfig):
verbose_name = "search" verbose_name = "search"
def ready(self): def ready(self):
from .backends import default_search_engine
from .hierarchy import SEARCH_TYPES
for name, module in SEARCH_TYPES.items():
default_search_engine.register(name, module)
for name, module in get_app_modules(): for name, module in get_app_modules():
submodule_name = "search_indexes" submodule_name = "search_indexes"
if module_has_submodule(module, submodule_name): if module_has_submodule(module, submodule_name):
print(f"{name}.{submodule_name}") module_name = f"{name}.{submodule_name}"
for cls_name, cls_obj in inspect.getmembers(sys.modules[module_name]):
if inspect.isclass(cls_obj) and getattr(cls_obj, "search_index", False) and getattr(cls_obj, "model", None):
cls_name = cls_obj.model.__name__.lower()
if not default_search_engine.is_registered(cls_name, cls_obj):
default_search_engine.register(cls_name, cls_obj)

View File

@ -10,30 +10,6 @@ from django.db.models.signals import post_save, pre_delete
_backends_cache = {} _backends_cache = {}
def get_backend(backend_name=None):
"""Initializes and returns the search backend."""
global _backends_cache
if not backend_name:
backend_name = getattr(settings, "SEARCH_BACKEND", "search.backends.PostgresIcontainsSearchBackend")
# Try to use the cached backend.
if backend_name in _backends_cache:
return _backends_cache[backend_name]
# Load the backend class.
backend_module_name, backend_cls_name = backend_name.rsplit(".", 1)
backend_module = import_module(backend_module_name)
try:
backend_cls = getattr(backend_module, backend_cls_name)
except AttributeError:
raise ImproperlyConfigured(f"Could not find a class named {backend_module_name} in {backend_cls_name}")
# Initialize the backend.
backend = backend_cls()
_backends_cache[backend_name] = backend
return backend
class SearchEngineError(Exception): class SearchEngineError(Exception):
"""Something went wrong with a search engine.""" """Something went wrong with a search engine."""
@ -63,11 +39,11 @@ class SearchBackend(object):
# Store a reference to this engine. # Store a reference to this engine.
self.__class__._created_engines[engine_slug] = self self.__class__._created_engines[engine_slug] = self
def is_registered(self, model): def is_registered(self, key, model):
"""Checks whether the given model is registered with this search engine.""" """Checks whether the given model is registered with this search engine."""
return model in self._registered_models return key in self._registered_models
def register(self, model): def register(self, key, model):
""" """
Registers the given model with this search engine. Registers the given model with this search engine.
@ -75,14 +51,19 @@ class SearchBackend(object):
RegistrationError will be raised. RegistrationError will be raised.
""" """
# Check for existing registration. # Check for existing registration.
if self.is_registered(model): if self.is_registered(key, model):
raise RegistrationError(f"{model} is already registered with this search engine") raise RegistrationError(f"{model} is already registered with this search engine")
self._registered_models[key] = model
# Connect to the signalling framework. # Connect to the signalling framework.
if self._use_hooks(): if self._use_hooks():
post_save.connect(self._post_save_receiver, model) post_save.connect(self._post_save_receiver, model)
pre_delete.connect(self._pre_delete_receiver, model) pre_delete.connect(self._pre_delete_receiver, model)
def get_registry(self):
return self._registered_models
# Signalling hooks. # Signalling hooks.
def _use_hooks(self): def _use_hooks(self):
@ -132,6 +113,30 @@ class PostgresIcontainsSearchBackend(SearchBackend):
return results return results
def get_backend(backend_name=None):
"""Initializes and returns the search backend."""
global _backends_cache
if not backend_name:
backend_name = getattr(settings, "SEARCH_BACKEND", "search.backends.PostgresIcontainsSearchBackend")
# Try to use the cached backend.
if backend_name in _backends_cache:
return _backends_cache[backend_name]
# Load the backend class.
backend_module_name, backend_cls_name = backend_name.rsplit(".", 1)
backend_module = import_module(backend_module_name)
try:
backend_cls = getattr(backend_module, backend_cls_name)
except AttributeError:
raise ImproperlyConfigured(f"Could not find a class named {backend_module_name} in {backend_cls_name}")
# Initialize the backend.
backend = backend_cls("default")
_backends_cache[backend_name] = backend
return backend
# The main search methods. # The main search methods.
default_search_engine = SearchBackend("default") default_search_engine = get_backend()
search = default_search_engine.search search = default_search_engine.search

View File

@ -6,90 +6,4 @@ class SearchMixin(object):
""" """
Base class for building search indexes. Base class for building search indexes.
""" """
search_index = True
def __init__(self, model=None, queryset=None, filterset=None, table=None, url=None):
self.model = model
self.queryset = queryset
self.filterset = filterset
self.table = table
self.url = url
def get_model(self):
"""
Should return the ``Model`` class (not an instance) that the rest of the
``SearchIndex`` should use.
This method is required & you must override it to return the correct class.
"""
if self.model is not None:
model = self.model
else:
raise ImproperlyConfigured(
f"{self.__class__.__name__}s is missing a Model. Set model in init or override"
f"{self.__class__.__name__}s.get_model()."
)
return model
def get_queryset(self):
"""
Should return the ``QuerySet`` class (not an instance) that the rest of the
``SearchIndex`` should use.
This method is required & you must override it to return the correct class.
"""
if self.queryset is not None:
queryset = self.queryset
else:
raise ImproperlyConfigured(
f"{self.__class__.__name__}s is missing a QuerySet. Set queryset in init or override "
f"{self.__class__.__name__}s.get_queryset()."
)
return queryset
def get_filterset(self):
"""
Should return the ``FilterSet`` class (not an instance) that the rest of the
``SearchIndex`` should use.
This method is required & you must override it to return the correct class.
"""
if self.filterset is not None:
filterset = self.filterset
else:
raise ImproperlyConfigured(
f"{self.__class__.__name__}s is missing a FilterSet. Set filterset in init or override "
f"{self.__class__.__name__}s.get_filterset()."
)
return filterset
def get_table(self):
"""
Should return the ``Table`` class (not an instance) that the rest of the
``SearchIndex`` should use.
This method is required & you must override it to return the correct class.
"""
if self.table is not None:
table = self.table
else:
raise ImproperlyConfigured(
f"{self.__class__.__name__}s is missing a Table. Set table in init or override "
f"{self.__class__.__name__}s.get_table()."
)
return table
def get_url(self):
"""
Should return the ``URL`` class (not an instance) that the rest of the
``SearchIndex`` should use.
This method is required & you must override it to return the correct class.
"""
if self.url is not None:
url = self.url
else:
raise ImproperlyConfigured(
f"{self.__class__.__name__}s is missing a URL. Set url in init or override "
f"{self.__class__.__name__}s.get_url()."
)
return url

View File

@ -8,23 +8,21 @@ from wireless.models import WirelessLAN, WirelessLink
class WirelessLANIndex(SearchMixin): class WirelessLANIndex(SearchMixin):
def __init__(self): model = WirelessLAN
self.model = WirelessLAN queryset = WirelessLAN.objects.prefetch_related('group', 'vlan').annotate(
self.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
self.filterset = wireless.filtersets.WirelessLANFilterSet table = wireless.tables.WirelessLANTable
self.table = wireless.tables.WirelessLANTable url = 'wireless:wirelesslan_list'
self.url = 'wireless:wirelesslan_list'
class WirelessLinkIndex(SearchMixin): class WirelessLinkIndex(SearchMixin):
def __init__(self): model = WirelessLink
self.model = WirelessLink queryset = WirelessLink.objects.prefetch_related('interface_a__device', 'interface_b__device')
self.queryset = WirelessLink.objects.prefetch_related('interface_a__device', 'interface_b__device') filterset = wireless.filtersets.WirelessLinkFilterSet
self.filterset = wireless.filtersets.WirelessLinkFilterSet table = wireless.tables.WirelessLinkTable
self.table = wireless.tables.WirelessLinkTable url = 'wireless:wirelesslink_list'
self.url = 'wireless:wirelesslink_list'
WIRELESS_SEARCH_TYPES = { WIRELESS_SEARCH_TYPES = {