Cleanup & docs

This commit is contained in:
jeremystretch 2022-10-20 10:44:28 -04:00
parent 885e246fce
commit f67b63b539
14 changed files with 107 additions and 86 deletions

View File

@ -1,6 +1,27 @@
# Search # Search
## Field Weight Guidance NetBox v3.4 introduced a new global search mechanism, which employs the `extras.CachedValue` model to store discrete field values from many models in a single table.
## SearchIndex
To enable search support for a model, declare and register a subclass of `netbox.search.SearchIndex` for it. Typically, this will be done within an app's `search.py` module.
```python
from netbox.search import SearchIndex, register_search
@register_search
class MyModelIndex(SearchIndex):
model = MyModel
fields = (
('name', 100),
('description', 500),
('comments', 5000),
)
```
A SearchIndex subclass defines both its model and a list of two-tuples specifying which model fields to be indexed and the weight (precedence) associated with each. Guidance on weight assignment for fields is provided below.
### Field Weight Guidance
| Weight | Field Role | Examples | | Weight | Field Role | Examples |
|--------|--------------------------------------------------|----------------------------------------------------| |--------|--------------------------------------------------|----------------------------------------------------|

View File

@ -11,6 +11,10 @@
### New Features ### New Features
#### New Global Search ([#10560](https://github.com/netbox-community/netbox/issues/10560))
NetBox's global search functionality has been completely overhauled and replaced by a new cache-based lookup.
#### Top-Level Plugin Navigation Menus ([#9071](https://github.com/netbox-community/netbox/issues/9071)) #### Top-Level Plugin Navigation Menus ([#9071](https://github.com/netbox-community/netbox/issues/9071))
A new `PluginMenu` class has been introduced, which enables a plugin to inject a top-level menu in NetBox's navigation menu. This menu can have one or more groups of menu items, just like core items. Backward compatibility with the existing `menu_items` has been maintained. A new `PluginMenu` class has been introduced, which enables a plugin to inject a top-level menu in NetBox's navigation menu. This menu can have one or more groups of menu items, just like core items. Backward compatibility with the existing `menu_items` has been maintained.

View File

@ -2,7 +2,7 @@ from netbox.search import SearchIndex, register_search
from . import models from . import models
@register_search() @register_search
class CircuitIndex(SearchIndex): class CircuitIndex(SearchIndex):
model = models.Circuit model = models.Circuit
fields = ( fields = (
@ -12,7 +12,7 @@ class CircuitIndex(SearchIndex):
) )
@register_search() @register_search
class CircuitTerminationIndex(SearchIndex): class CircuitTerminationIndex(SearchIndex):
model = models.CircuitTermination model = models.CircuitTermination
fields = ( fields = (
@ -24,7 +24,7 @@ class CircuitTerminationIndex(SearchIndex):
) )
@register_search() @register_search
class CircuitTypeIndex(SearchIndex): class CircuitTypeIndex(SearchIndex):
model = models.CircuitType model = models.CircuitType
fields = ( fields = (
@ -34,7 +34,7 @@ class CircuitTypeIndex(SearchIndex):
) )
@register_search() @register_search
class ProviderIndex(SearchIndex): class ProviderIndex(SearchIndex):
model = models.Provider model = models.Provider
fields = ( fields = (
@ -44,7 +44,7 @@ class ProviderIndex(SearchIndex):
) )
@register_search() @register_search
class ProviderNetworkIndex(SearchIndex): class ProviderNetworkIndex(SearchIndex):
model = models.ProviderNetwork model = models.ProviderNetwork
fields = ( fields = (

View File

@ -2,7 +2,7 @@ from netbox.search import SearchIndex, register_search
from . import models from . import models
@register_search() @register_search
class CableIndex(SearchIndex): class CableIndex(SearchIndex):
model = models.Cable model = models.Cable
fields = ( fields = (
@ -10,7 +10,7 @@ class CableIndex(SearchIndex):
) )
@register_search() @register_search
class ConsolePortIndex(SearchIndex): class ConsolePortIndex(SearchIndex):
model = models.ConsolePort model = models.ConsolePort
fields = ( fields = (
@ -21,7 +21,7 @@ class ConsolePortIndex(SearchIndex):
) )
@register_search() @register_search
class ConsoleServerPortIndex(SearchIndex): class ConsoleServerPortIndex(SearchIndex):
model = models.ConsoleServerPort model = models.ConsoleServerPort
fields = ( fields = (
@ -32,7 +32,7 @@ class ConsoleServerPortIndex(SearchIndex):
) )
@register_search() @register_search
class DeviceIndex(SearchIndex): class DeviceIndex(SearchIndex):
model = models.Device model = models.Device
fields = ( fields = (
@ -43,7 +43,7 @@ class DeviceIndex(SearchIndex):
) )
@register_search() @register_search
class DeviceBayIndex(SearchIndex): class DeviceBayIndex(SearchIndex):
model = models.DeviceBay model = models.DeviceBay
fields = ( fields = (
@ -53,7 +53,7 @@ class DeviceBayIndex(SearchIndex):
) )
@register_search() @register_search
class DeviceRoleIndex(SearchIndex): class DeviceRoleIndex(SearchIndex):
model = models.DeviceRole model = models.DeviceRole
fields = ( fields = (
@ -63,7 +63,7 @@ class DeviceRoleIndex(SearchIndex):
) )
@register_search() @register_search
class DeviceTypeIndex(SearchIndex): class DeviceTypeIndex(SearchIndex):
model = models.DeviceType model = models.DeviceType
fields = ( fields = (
@ -73,7 +73,7 @@ class DeviceTypeIndex(SearchIndex):
) )
@register_search() @register_search
class FrontPortIndex(SearchIndex): class FrontPortIndex(SearchIndex):
model = models.FrontPort model = models.FrontPort
fields = ( fields = (
@ -83,7 +83,7 @@ class FrontPortIndex(SearchIndex):
) )
@register_search() @register_search
class InterfaceIndex(SearchIndex): class InterfaceIndex(SearchIndex):
model = models.Interface model = models.Interface
fields = ( fields = (
@ -97,7 +97,7 @@ class InterfaceIndex(SearchIndex):
) )
@register_search() @register_search
class InventoryItemIndex(SearchIndex): class InventoryItemIndex(SearchIndex):
model = models.InventoryItem model = models.InventoryItem
fields = ( fields = (
@ -110,7 +110,7 @@ class InventoryItemIndex(SearchIndex):
) )
@register_search() @register_search
class LocationIndex(SearchIndex): class LocationIndex(SearchIndex):
model = models.Location model = models.Location
fields = ( fields = (
@ -120,7 +120,7 @@ class LocationIndex(SearchIndex):
) )
@register_search() @register_search
class ManufacturerIndex(SearchIndex): class ManufacturerIndex(SearchIndex):
model = models.Manufacturer model = models.Manufacturer
fields = ( fields = (
@ -130,7 +130,7 @@ class ManufacturerIndex(SearchIndex):
) )
@register_search() @register_search
class ModuleIndex(SearchIndex): class ModuleIndex(SearchIndex):
model = models.Module model = models.Module
fields = ( fields = (
@ -140,7 +140,7 @@ class ModuleIndex(SearchIndex):
) )
@register_search() @register_search
class ModuleBayIndex(SearchIndex): class ModuleBayIndex(SearchIndex):
model = models.ModuleBay model = models.ModuleBay
fields = ( fields = (
@ -150,7 +150,7 @@ class ModuleBayIndex(SearchIndex):
) )
@register_search() @register_search
class ModuleTypeIndex(SearchIndex): class ModuleTypeIndex(SearchIndex):
model = models.ModuleType model = models.ModuleType
fields = ( fields = (
@ -160,7 +160,7 @@ class ModuleTypeIndex(SearchIndex):
) )
@register_search() @register_search
class PlatformIndex(SearchIndex): class PlatformIndex(SearchIndex):
model = models.Platform model = models.Platform
fields = ( fields = (
@ -171,7 +171,7 @@ class PlatformIndex(SearchIndex):
) )
@register_search() @register_search
class PowerFeedIndex(SearchIndex): class PowerFeedIndex(SearchIndex):
model = models.PowerFeed model = models.PowerFeed
fields = ( fields = (
@ -180,7 +180,7 @@ class PowerFeedIndex(SearchIndex):
) )
@register_search() @register_search
class PowerOutletIndex(SearchIndex): class PowerOutletIndex(SearchIndex):
model = models.PowerOutlet model = models.PowerOutlet
fields = ( fields = (
@ -190,7 +190,7 @@ class PowerOutletIndex(SearchIndex):
) )
@register_search() @register_search
class PowerPanelIndex(SearchIndex): class PowerPanelIndex(SearchIndex):
model = models.PowerPanel model = models.PowerPanel
fields = ( fields = (
@ -198,7 +198,7 @@ class PowerPanelIndex(SearchIndex):
) )
@register_search() @register_search
class PowerPortIndex(SearchIndex): class PowerPortIndex(SearchIndex):
model = models.PowerPort model = models.PowerPort
fields = ( fields = (
@ -210,7 +210,7 @@ class PowerPortIndex(SearchIndex):
) )
@register_search() @register_search
class RackIndex(SearchIndex): class RackIndex(SearchIndex):
model = models.Rack model = models.Rack
fields = ( fields = (
@ -222,7 +222,7 @@ class RackIndex(SearchIndex):
) )
@register_search() @register_search
class RackReservationIndex(SearchIndex): class RackReservationIndex(SearchIndex):
model = models.RackReservation model = models.RackReservation
fields = ( fields = (
@ -230,7 +230,7 @@ class RackReservationIndex(SearchIndex):
) )
@register_search() @register_search
class RackRoleIndex(SearchIndex): class RackRoleIndex(SearchIndex):
model = models.RackRole model = models.RackRole
fields = ( fields = (
@ -240,7 +240,7 @@ class RackRoleIndex(SearchIndex):
) )
@register_search() @register_search
class RearPortIndex(SearchIndex): class RearPortIndex(SearchIndex):
model = models.RearPort model = models.RearPort
fields = ( fields = (
@ -250,7 +250,7 @@ class RearPortIndex(SearchIndex):
) )
@register_search() @register_search
class RegionIndex(SearchIndex): class RegionIndex(SearchIndex):
model = models.Region model = models.Region
fields = ( fields = (
@ -260,7 +260,7 @@ class RegionIndex(SearchIndex):
) )
@register_search() @register_search
class SiteIndex(SearchIndex): class SiteIndex(SearchIndex):
model = models.Site model = models.Site
fields = ( fields = (
@ -274,7 +274,7 @@ class SiteIndex(SearchIndex):
) )
@register_search() @register_search
class SiteGroupIndex(SearchIndex): class SiteGroupIndex(SearchIndex):
model = models.SiteGroup model = models.SiteGroup
fields = ( fields = (
@ -284,7 +284,7 @@ class SiteGroupIndex(SearchIndex):
) )
@register_search() @register_search
class VirtualChassisIndex(SearchIndex): class VirtualChassisIndex(SearchIndex):
model = models.VirtualChassis model = models.VirtualChassis
fields = ( fields = (

View File

@ -75,7 +75,7 @@ class PluginConfig(AppConfig):
try: try:
search_indexes = import_string(f"{self.__module__}.{self.search_indexes}") search_indexes = import_string(f"{self.__module__}.{self.search_indexes}")
for idx in search_indexes: for idx in search_indexes:
register_search()(idx) register_search(idx)
except ImportError: except ImportError:
pass pass

View File

@ -2,7 +2,7 @@ from netbox.search import SearchIndex, register_search
from . import models from . import models
@register_search() @register_search
class JournalEntryIndex(SearchIndex): class JournalEntryIndex(SearchIndex):
model = models.JournalEntry model = models.JournalEntry
fields = ( fields = (

View File

@ -2,7 +2,7 @@ from . import models
from netbox.search import SearchIndex, register_search from netbox.search import SearchIndex, register_search
@register_search() @register_search
class AggregateIndex(SearchIndex): class AggregateIndex(SearchIndex):
model = models.Aggregate model = models.Aggregate
fields = ( fields = (
@ -12,7 +12,7 @@ class AggregateIndex(SearchIndex):
) )
@register_search() @register_search
class ASNIndex(SearchIndex): class ASNIndex(SearchIndex):
model = models.ASN model = models.ASN
fields = ( fields = (
@ -21,7 +21,7 @@ class ASNIndex(SearchIndex):
) )
@register_search() @register_search
class FHRPGroupIndex(SearchIndex): class FHRPGroupIndex(SearchIndex):
model = models.FHRPGroup model = models.FHRPGroup
fields = ( fields = (
@ -31,7 +31,7 @@ class FHRPGroupIndex(SearchIndex):
) )
@register_search() @register_search
class IPAddressIndex(SearchIndex): class IPAddressIndex(SearchIndex):
model = models.IPAddress model = models.IPAddress
fields = ( fields = (
@ -41,7 +41,7 @@ class IPAddressIndex(SearchIndex):
) )
@register_search() @register_search
class IPRangeIndex(SearchIndex): class IPRangeIndex(SearchIndex):
model = models.IPRange model = models.IPRange
fields = ( fields = (
@ -51,7 +51,7 @@ class IPRangeIndex(SearchIndex):
) )
@register_search() @register_search
class L2VPNIndex(SearchIndex): class L2VPNIndex(SearchIndex):
model = models.L2VPN model = models.L2VPN
fields = ( fields = (
@ -61,7 +61,7 @@ class L2VPNIndex(SearchIndex):
) )
@register_search() @register_search
class PrefixIndex(SearchIndex): class PrefixIndex(SearchIndex):
model = models.Prefix model = models.Prefix
fields = ( fields = (
@ -70,7 +70,7 @@ class PrefixIndex(SearchIndex):
) )
@register_search() @register_search
class RIRIndex(SearchIndex): class RIRIndex(SearchIndex):
model = models.RIR model = models.RIR
fields = ( fields = (
@ -80,7 +80,7 @@ class RIRIndex(SearchIndex):
) )
@register_search() @register_search
class RoleIndex(SearchIndex): class RoleIndex(SearchIndex):
model = models.Role model = models.Role
fields = ( fields = (
@ -90,7 +90,7 @@ class RoleIndex(SearchIndex):
) )
@register_search() @register_search
class RouteTargetIndex(SearchIndex): class RouteTargetIndex(SearchIndex):
model = models.RouteTarget model = models.RouteTarget
fields = ( fields = (
@ -99,7 +99,7 @@ class RouteTargetIndex(SearchIndex):
) )
@register_search() @register_search
class ServiceIndex(SearchIndex): class ServiceIndex(SearchIndex):
model = models.Service model = models.Service
fields = ( fields = (
@ -108,7 +108,7 @@ class ServiceIndex(SearchIndex):
) )
@register_search() @register_search
class VLANIndex(SearchIndex): class VLANIndex(SearchIndex):
model = models.VLAN model = models.VLAN
fields = ( fields = (
@ -118,7 +118,7 @@ class VLANIndex(SearchIndex):
) )
@register_search() @register_search
class VLANGroupIndex(SearchIndex): class VLANGroupIndex(SearchIndex):
model = models.VLANGroup model = models.VLANGroup
fields = ( fields = (
@ -129,7 +129,7 @@ class VLANGroupIndex(SearchIndex):
) )
@register_search() @register_search
class VRFIndex(SearchIndex): class VRFIndex(SearchIndex):
model = models.VRF model = models.VRF
fields = ( fields = (

View File

@ -91,11 +91,10 @@ def get_indexer(model):
return registry['search'][app_label][model_name] return registry['search'][app_label][model_name]
def register_search(): def register_search(cls):
""" """
Decorator for registering a SearchIndex with a particular model. Decorator for registering a SearchIndex with a particular model.
""" """
def _wrapper(cls):
model = cls.model model = cls.model
app_label = model._meta.app_label app_label = model._meta.app_label
model_name = model._meta.model_name model_name = model._meta.model_name
@ -103,5 +102,3 @@ def register_search():
registry['search'][app_label][model_name] = cls registry['search'][app_label][model_name] = cls
return cls return cls
return _wrapper

View File

@ -7,6 +7,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.db.models import F, Window from django.db.models import F, Window
from django.db.models.functions import window from django.db.models.functions import window
from django.db.models.signals import post_delete, post_save from django.db.models.signals import post_delete, post_save
from django.utils.module_loading import import_string
from extras.models import CachedValue, CustomField from extras.models import CachedValue, CustomField
from extras.registry import registry from extras.registry import registry
@ -50,7 +51,7 @@ class SearchBackend:
return self._search_choice_options return self._search_choice_options
def search(self, request, value, object_types=None, lookup=DEFAULT_LOOKUP_TYPE): def search(self, value, user=None, object_types=None, lookup=DEFAULT_LOOKUP_TYPE):
""" """
Search cached object representations for the given value. Search cached object representations for the given value.
""" """
@ -102,9 +103,7 @@ class SearchBackend:
class CachedValueSearchBackend(SearchBackend): class CachedValueSearchBackend(SearchBackend):
def search(self, value, user=None, object_types=None, lookup=None): def search(self, value, user=None, object_types=None, lookup=DEFAULT_LOOKUP_TYPE):
if not lookup:
lookup = DEFAULT_LOOKUP_TYPE
# Define the search parameters # Define the search parameters
params = { params = {
@ -230,16 +229,13 @@ class CachedValueSearchBackend(SearchBackend):
def get_backend(): def get_backend():
"""Initializes and returns the configured search backend.""" """
backend_name = settings.SEARCH_BACKEND Initializes and returns the configured search backend.
"""
# Load the backend class
backend_module_name, backend_cls_name = backend_name.rsplit('.', 1)
backend_module = import_module(backend_module_name)
try: try:
backend_cls = getattr(backend_module, backend_cls_name) backend_cls = import_string(settings.SEARCH_BACKEND)
except AttributeError: except AttributeError:
raise ImproperlyConfigured(f"Could not find a class named {backend_module_name} in {backend_cls_name}") raise ImproperlyConfigured(f"Failed to import configured SEARCH_BACKEND: {settings.SEARCH_BACKEND}")
# Initialize and return the backend instance # Initialize and return the backend instance
return backend_cls() return backend_cls()

View File

@ -222,7 +222,9 @@ class SearchTable(tables.Table):
super().__init__(data, **kwargs) super().__init__(data, **kwargs)
def render_field(self, value, record): def render_field(self, value, record):
if hasattr(record.object, value):
return bettertitle(record.object._meta.get_field(value).verbose_name) return bettertitle(record.object._meta.get_field(value).verbose_name)
return value
def render_value(self, value): def render_value(self, value):
if not self.highlight: if not self.highlight:

View File

@ -167,11 +167,12 @@ class SearchView(View):
app_label, model_name = obj_type.split('.') app_label, model_name = obj_type.split('.')
object_types.append(ContentType.objects.get_by_natural_key(app_label, model_name)) object_types.append(ContentType.objects.get_by_natural_key(app_label, model_name))
lookup = form.cleaned_data['lookup'] or LookupTypes.PARTIAL
results = search_backend.search( results = search_backend.search(
form.cleaned_data['q'], form.cleaned_data['q'],
user=request.user, user=request.user,
object_types=object_types, object_types=object_types,
lookup=form.cleaned_data['lookup'] lookup=lookup
) )
if form.cleaned_data['lookup'] != LookupTypes.EXACT: if form.cleaned_data['lookup'] != LookupTypes.EXACT:

View File

@ -2,7 +2,7 @@ from netbox.search import SearchIndex, register_search
from . import models from . import models
@register_search() @register_search
class ContactIndex(SearchIndex): class ContactIndex(SearchIndex):
model = models.Contact model = models.Contact
fields = ( fields = (
@ -16,7 +16,7 @@ class ContactIndex(SearchIndex):
) )
@register_search() @register_search
class ContactGroupIndex(SearchIndex): class ContactGroupIndex(SearchIndex):
model = models.ContactGroup model = models.ContactGroup
fields = ( fields = (
@ -26,7 +26,7 @@ class ContactGroupIndex(SearchIndex):
) )
@register_search() @register_search
class ContactRoleIndex(SearchIndex): class ContactRoleIndex(SearchIndex):
model = models.ContactRole model = models.ContactRole
fields = ( fields = (
@ -36,7 +36,7 @@ class ContactRoleIndex(SearchIndex):
) )
@register_search() @register_search
class TenantIndex(SearchIndex): class TenantIndex(SearchIndex):
model = models.Tenant model = models.Tenant
fields = ( fields = (
@ -47,7 +47,7 @@ class TenantIndex(SearchIndex):
) )
@register_search() @register_search
class TenantGroupIndex(SearchIndex): class TenantGroupIndex(SearchIndex):
model = models.TenantGroup model = models.TenantGroup
fields = ( fields = (

View File

@ -2,7 +2,7 @@ from netbox.search import SearchIndex, register_search
from . import models from . import models
@register_search() @register_search
class ClusterIndex(SearchIndex): class ClusterIndex(SearchIndex):
model = models.Cluster model = models.Cluster
fields = ( fields = (
@ -11,7 +11,7 @@ class ClusterIndex(SearchIndex):
) )
@register_search() @register_search
class ClusterGroupIndex(SearchIndex): class ClusterGroupIndex(SearchIndex):
model = models.ClusterGroup model = models.ClusterGroup
fields = ( fields = (
@ -21,7 +21,7 @@ class ClusterGroupIndex(SearchIndex):
) )
@register_search() @register_search
class ClusterTypeIndex(SearchIndex): class ClusterTypeIndex(SearchIndex):
model = models.ClusterType model = models.ClusterType
fields = ( fields = (
@ -31,7 +31,7 @@ class ClusterTypeIndex(SearchIndex):
) )
@register_search() @register_search
class VirtualMachineIndex(SearchIndex): class VirtualMachineIndex(SearchIndex):
model = models.VirtualMachine model = models.VirtualMachine
fields = ( fields = (
@ -40,7 +40,7 @@ class VirtualMachineIndex(SearchIndex):
) )
@register_search() @register_search
class VMInterfaceIndex(SearchIndex): class VMInterfaceIndex(SearchIndex):
model = models.VMInterface model = models.VMInterface
fields = ( fields = (

View File

@ -2,7 +2,7 @@ from netbox.search import SearchIndex, register_search
from . import models from . import models
@register_search() @register_search
class WirelessLANIndex(SearchIndex): class WirelessLANIndex(SearchIndex):
model = models.WirelessLAN model = models.WirelessLAN
fields = ( fields = (
@ -12,7 +12,7 @@ class WirelessLANIndex(SearchIndex):
) )
@register_search() @register_search
class WirelessLANGroupIndex(SearchIndex): class WirelessLANGroupIndex(SearchIndex):
model = models.WirelessLANGroup model = models.WirelessLANGroup
fields = ( fields = (
@ -22,7 +22,7 @@ class WirelessLANGroupIndex(SearchIndex):
) )
@register_search() @register_search
class WirelessLinkIndex(SearchIndex): class WirelessLinkIndex(SearchIndex):
model = models.WirelessLink model = models.WirelessLink
fields = ( fields = (