mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
* WIP * Add display_attrs for all indexers * Linkify object attributes * Clean up prefetch logic * Use tooltips for display attributes * Simplify template code * Introduce get_indexer() utility function * Add to examples in docs * Use tooltips to display long strings
This commit is contained in:
@ -17,6 +17,7 @@ class MyModelIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('site', 'device', 'status', 'description')
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
@ -14,8 +14,11 @@ class MyModelIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('site', 'device', 'status', 'description')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Fields listed in `display_attrs` will not be cached for search, but will be displayed alongside the object when it appears in global search results. This is helpful for conveying to the user additional information about an object.
|
||||||
|
|
||||||
To register one or more indexes with NetBox, define a list named `indexes` at the end of this file:
|
To register one or more indexes with NetBox, define a list named `indexes` at the end of this file:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
@ -10,6 +10,7 @@ class CircuitIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('provider', 'provider_account', 'type', 'status', 'tenant', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -22,6 +23,7 @@ class CircuitTerminationIndex(SearchIndex):
|
|||||||
('port_speed', 2000),
|
('port_speed', 2000),
|
||||||
('upstream_speed', 2000),
|
('upstream_speed', 2000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('circuit', 'site', 'provider_network', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -32,6 +34,7 @@ class CircuitTypeIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('description',)
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -42,6 +45,7 @@ class ProviderIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('description',)
|
||||||
|
|
||||||
|
|
||||||
class ProviderAccountIndex(SearchIndex):
|
class ProviderAccountIndex(SearchIndex):
|
||||||
@ -51,6 +55,7 @@ class ProviderAccountIndex(SearchIndex):
|
|||||||
('account', 200),
|
('account', 200),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('provider', 'account', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -62,3 +67,4 @@ class ProviderNetworkIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('provider', 'service_id', 'description')
|
||||||
|
@ -11,6 +11,7 @@ class DataSourceIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('type', 'status', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
|
@ -10,6 +10,7 @@ class CableIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('type', 'status', 'tenant', 'label', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -21,6 +22,7 @@ class ConsolePortIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('speed', 2000),
|
('speed', 2000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('device', 'label', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -32,6 +34,7 @@ class ConsoleServerPortIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('speed', 2000),
|
('speed', 2000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('device', 'label', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -44,6 +47,9 @@ class DeviceIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = (
|
||||||
|
'site', 'location', 'rack', 'device_type', 'role', 'tenant', 'platform', 'serial', 'asset_tag', 'description',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -54,6 +60,7 @@ class DeviceBayIndex(SearchIndex):
|
|||||||
('label', 200),
|
('label', 200),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('device', 'label', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -64,6 +71,7 @@ class DeviceRoleIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('description',)
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -75,6 +83,7 @@ class DeviceTypeIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('manufacturer', 'part_number', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -85,6 +94,7 @@ class FrontPortIndex(SearchIndex):
|
|||||||
('label', 200),
|
('label', 200),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('device', 'label', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -99,6 +109,7 @@ class InterfaceIndex(SearchIndex):
|
|||||||
('mtu', 2000),
|
('mtu', 2000),
|
||||||
('speed', 2000),
|
('speed', 2000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('device', 'label', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -112,6 +123,7 @@ class InventoryItemIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('part_id', 2000),
|
('part_id', 2000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('device', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -122,6 +134,7 @@ class LocationIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('site', 'status', 'tenant', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -132,6 +145,7 @@ class ManufacturerIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('description',)
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -143,6 +157,7 @@ class ModuleIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('device', 'module_bay', 'module_type', 'status', 'serial', 'asset_tag', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -153,6 +168,7 @@ class ModuleBayIndex(SearchIndex):
|
|||||||
('label', 200),
|
('label', 200),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('device', 'label', 'position', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -164,6 +180,7 @@ class ModuleTypeIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('manufacturer', 'model', 'part_number', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -174,6 +191,7 @@ class PlatformIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('manufacturer', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -184,6 +202,7 @@ class PowerFeedIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('power_panel', 'rack', 'status', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -194,6 +213,7 @@ class PowerOutletIndex(SearchIndex):
|
|||||||
('label', 200),
|
('label', 200),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('device', 'label', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -204,6 +224,7 @@ class PowerPanelIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('site', 'location', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -216,6 +237,7 @@ class PowerPortIndex(SearchIndex):
|
|||||||
('maximum_draw', 2000),
|
('maximum_draw', 2000),
|
||||||
('allocated_draw', 2000),
|
('allocated_draw', 2000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('device', 'label', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -229,6 +251,7 @@ class RackIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('site', 'location', 'facility_id', 'tenant', 'status', 'role', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -238,6 +261,7 @@ class RackReservationIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('rack', 'tenant', 'user', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -248,6 +272,7 @@ class RackRoleIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('device', 'label', 'description',)
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -258,6 +283,7 @@ class RearPortIndex(SearchIndex):
|
|||||||
('label', 200),
|
('label', 200),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('device', 'label', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -268,6 +294,7 @@ class RegionIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('parent', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -282,6 +309,7 @@ class SiteIndex(SearchIndex):
|
|||||||
('shipping_address', 2000),
|
('shipping_address', 2000),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('region', 'group', 'status', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -292,6 +320,7 @@ class SiteGroupIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('parent', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -303,6 +332,7 @@ class VirtualChassisIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('master', 'domain', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -314,3 +344,4 @@ class VirtualDeviceContextIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('device', 'status', 'identifier', 'description')
|
||||||
|
@ -4,7 +4,10 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from netbox.search.utils import get_indexer
|
||||||
|
from netbox.registry import registry
|
||||||
from utilities.fields import RestrictedGenericForeignKey
|
from utilities.fields import RestrictedGenericForeignKey
|
||||||
|
from utilities.utils import content_type_identifier
|
||||||
from ..fields import CachedValueField
|
from ..fields import CachedValueField
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -58,3 +61,19 @@ class CachedValue(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.object_type} {self.object_id}: {self.field}={self.value}'
|
return f'{self.object_type} {self.object_id}: {self.field}={self.value}'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def display_attrs(self):
|
||||||
|
"""
|
||||||
|
Render any display attributes associated with this search result.
|
||||||
|
"""
|
||||||
|
indexer = get_indexer(self.object_type)
|
||||||
|
attrs = {}
|
||||||
|
for attr in indexer.display_attrs:
|
||||||
|
name = self.object._meta.get_field(attr).verbose_name
|
||||||
|
if value := getattr(self.object, attr):
|
||||||
|
if display_func := getattr(self.object, f'get_{attr}_display', None):
|
||||||
|
attrs[name] = display_func()
|
||||||
|
else:
|
||||||
|
attrs[name] = value
|
||||||
|
return attrs
|
||||||
|
@ -11,6 +11,7 @@ class AggregateIndex(SearchIndex):
|
|||||||
('date_added', 2000),
|
('date_added', 2000),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('rir', 'tenant', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -20,6 +21,7 @@ class ASNIndex(SearchIndex):
|
|||||||
('asn', 100),
|
('asn', 100),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('rir', 'tenant', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -28,6 +30,7 @@ class ASNRangeIndex(SearchIndex):
|
|||||||
fields = (
|
fields = (
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('rir', 'tenant', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -39,6 +42,7 @@ class FHRPGroupIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('protocol', 'auth_type', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -50,6 +54,7 @@ class IPAddressIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('vrf', 'tenant', 'status', 'role', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -61,6 +66,7 @@ class IPRangeIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('vrf', 'tenant', 'status', 'role', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -72,6 +78,7 @@ class L2VPNIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('type', 'identifier', 'tenant', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -82,6 +89,7 @@ class PrefixIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -92,6 +100,7 @@ class RIRIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('description',)
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -102,6 +111,7 @@ class RoleIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('description',)
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -112,6 +122,7 @@ class RouteTargetIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('tenant', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -122,6 +133,7 @@ class ServiceIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('device', 'virtual_machine', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -132,6 +144,7 @@ class ServiceTemplateIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('description',)
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -143,6 +156,7 @@ class VLANIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('site', 'group', 'tenant', 'status', 'role', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -154,6 +168,7 @@ class VLANGroupIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('max_vid', 2000),
|
('max_vid', 2000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('scope_type', 'min_vid', 'max_vid', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -165,3 +180,4 @@ class VRFIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('rd', 'tenant', 'description')
|
||||||
|
@ -33,10 +33,12 @@ class SearchIndex:
|
|||||||
category: The label of the group under which this indexer is categorized (for form field display). If none,
|
category: The label of the group under which this indexer is categorized (for form field display). If none,
|
||||||
the name of the model's app will be used.
|
the name of the model's app will be used.
|
||||||
fields: An iterable of two-tuples defining the model fields to be indexed and the weight associated with each.
|
fields: An iterable of two-tuples defining the model fields to be indexed and the weight associated with each.
|
||||||
|
display_attrs: An iterable of additional object attributes to include when displaying search results.
|
||||||
"""
|
"""
|
||||||
model = None
|
model = None
|
||||||
category = None
|
category = None
|
||||||
fields = ()
|
fields = ()
|
||||||
|
display_attrs = ()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_field_type(instance, field_name):
|
def get_field_type(instance, field_name):
|
||||||
|
@ -3,7 +3,8 @@ from collections import defaultdict
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db.models import F, Window, Q
|
from django.db.models import F, Window, Q, prefetch_related_objects
|
||||||
|
from django.db.models.fields.related import ForeignKey
|
||||||
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 django.utils.module_loading import import_string
|
||||||
@ -13,7 +14,7 @@ from netaddr.core import AddrFormatError
|
|||||||
from extras.models import CachedValue, CustomField
|
from extras.models import CachedValue, CustomField
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
from utilities.querysets import RestrictedPrefetch
|
from utilities.querysets import RestrictedPrefetch
|
||||||
from utilities.utils import title
|
from utilities.utils import content_type_identifier, title
|
||||||
from . import FieldTypes, LookupTypes, get_indexer
|
from . import FieldTypes, LookupTypes, get_indexer
|
||||||
|
|
||||||
DEFAULT_LOOKUP_TYPE = LookupTypes.PARTIAL
|
DEFAULT_LOOKUP_TYPE = LookupTypes.PARTIAL
|
||||||
@ -103,17 +104,17 @@ class CachedValueSearchBackend(SearchBackend):
|
|||||||
|
|
||||||
def search(self, value, user=None, object_types=None, lookup=DEFAULT_LOOKUP_TYPE):
|
def search(self, value, user=None, object_types=None, lookup=DEFAULT_LOOKUP_TYPE):
|
||||||
|
|
||||||
|
# Build the filter used to find relevant CachedValue records
|
||||||
query_filter = Q(**{f'value__{lookup}': value})
|
query_filter = Q(**{f'value__{lookup}': value})
|
||||||
|
|
||||||
if object_types:
|
if object_types:
|
||||||
|
# Limit results by object type
|
||||||
query_filter &= Q(object_type__in=object_types)
|
query_filter &= Q(object_type__in=object_types)
|
||||||
|
|
||||||
if lookup in (LookupTypes.STARTSWITH, LookupTypes.ENDSWITH):
|
if lookup in (LookupTypes.STARTSWITH, LookupTypes.ENDSWITH):
|
||||||
# Partial string matches are valid only on string values
|
# "Starts/ends with" matches are valid only on string values
|
||||||
query_filter &= Q(type=FieldTypes.STRING)
|
query_filter &= Q(type=FieldTypes.STRING)
|
||||||
|
elif lookup == LookupTypes.PARTIAL:
|
||||||
if lookup == LookupTypes.PARTIAL:
|
|
||||||
try:
|
try:
|
||||||
|
# If the value looks like an IP address, add an extra match for CIDR values
|
||||||
address = str(netaddr.IPNetwork(value.strip()).cidr)
|
address = str(netaddr.IPNetwork(value.strip()).cidr)
|
||||||
query_filter |= Q(type=FieldTypes.CIDR) & Q(value__net_contains_or_equals=address)
|
query_filter |= Q(type=FieldTypes.CIDR) & Q(value__net_contains_or_equals=address)
|
||||||
except (AddrFormatError, ValueError):
|
except (AddrFormatError, ValueError):
|
||||||
@ -129,6 +130,12 @@ class CachedValueSearchBackend(SearchBackend):
|
|||||||
)
|
)
|
||||||
)[:MAX_RESULTS]
|
)[:MAX_RESULTS]
|
||||||
|
|
||||||
|
# Gather all ContentTypes present in the search results (used for prefetching related
|
||||||
|
# objects). This must be done before generating the final results list, which returns
|
||||||
|
# a RawQuerySet.
|
||||||
|
content_type_ids = set(queryset.values_list('object_type', flat=True))
|
||||||
|
content_types = ContentType.objects.filter(pk__in=content_type_ids)
|
||||||
|
|
||||||
# Construct a Prefetch to pre-fetch only those related objects for which the
|
# Construct a Prefetch to pre-fetch only those related objects for which the
|
||||||
# user has permission to view.
|
# user has permission to view.
|
||||||
if user:
|
if user:
|
||||||
@ -144,12 +151,34 @@ class CachedValueSearchBackend(SearchBackend):
|
|||||||
params
|
params
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Iterate through each ContentType represented in the search results and prefetch any
|
||||||
|
# related objects necessary to render the prescribed display attributes (display_attrs).
|
||||||
|
for ct in content_types:
|
||||||
|
model = ct.model_class()
|
||||||
|
indexer = registry['search'].get(content_type_identifier(ct))
|
||||||
|
if not (display_attrs := getattr(indexer, 'display_attrs', None)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Add ForeignKey fields to prefetch list
|
||||||
|
prefetch_fields = []
|
||||||
|
for attr in display_attrs:
|
||||||
|
field = model._meta.get_field(attr)
|
||||||
|
if type(field) is ForeignKey:
|
||||||
|
prefetch_fields.append(f'object__{attr}')
|
||||||
|
|
||||||
|
# Compile a list of all CachedValues referencing this object type, and prefetch
|
||||||
|
# any related objects
|
||||||
|
if prefetch_fields:
|
||||||
|
objects = [r for r in results if r.object_type == ct]
|
||||||
|
prefetch_related_objects(objects, *prefetch_fields)
|
||||||
|
|
||||||
# Omit any results pertaining to an object the user does not have permission to view
|
# Omit any results pertaining to an object the user does not have permission to view
|
||||||
ret = []
|
ret = []
|
||||||
for r in results:
|
for r in results:
|
||||||
if r.object is not None:
|
if r.object is not None:
|
||||||
r.name = str(r.object)
|
r.name = str(r.object)
|
||||||
ret.append(r)
|
ret.append(r)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def cache(self, instances, indexer=None, remove_existing=True):
|
def cache(self, instances, indexer=None, remove_existing=True):
|
||||||
|
14
netbox/netbox/search/utils.py
Normal file
14
netbox/netbox/search/utils.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from netbox.registry import registry
|
||||||
|
from utilities.utils import content_type_identifier
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'get_indexer',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_indexer(content_type):
|
||||||
|
"""
|
||||||
|
Return the registered search indexer for the given ContentType.
|
||||||
|
"""
|
||||||
|
ct_identifier = content_type_identifier(content_type)
|
||||||
|
return registry['search'].get(ct_identifier)
|
@ -15,6 +15,7 @@ from extras.choices import CustomFieldVisibilityChoices
|
|||||||
from netbox.tables import columns
|
from netbox.tables import columns
|
||||||
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
from utilities.paginator import EnhancedPaginator, get_paginate_count
|
||||||
from utilities.utils import get_viewname, highlight_string, title
|
from utilities.utils import get_viewname, highlight_string, title
|
||||||
|
from .template_code import *
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'BaseTable',
|
'BaseTable',
|
||||||
@ -236,6 +237,10 @@ class SearchTable(tables.Table):
|
|||||||
value = tables.Column(
|
value = tables.Column(
|
||||||
verbose_name=_('Value'),
|
verbose_name=_('Value'),
|
||||||
)
|
)
|
||||||
|
attrs = columns.TemplateColumn(
|
||||||
|
template_code=SEARCH_RESULT_ATTRS,
|
||||||
|
verbose_name=_('Attributes')
|
||||||
|
)
|
||||||
|
|
||||||
trim_length = 30
|
trim_length = 30
|
||||||
|
|
||||||
|
18
netbox/netbox/tables/template_code.py
Normal file
18
netbox/netbox/tables/template_code.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
SEARCH_RESULT_ATTRS = """
|
||||||
|
{% for name, value in record.display_attrs.items %}
|
||||||
|
<span class="badge bg-secondary"
|
||||||
|
{% if value|length > 40 %} data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{ value }}"{% endif %}
|
||||||
|
>
|
||||||
|
{{ name|bettertitle }}:
|
||||||
|
{% with url=value.get_absolute_url %}
|
||||||
|
{% if url %}<a href="url">{% endif %}
|
||||||
|
{% if value|length > 40 %}
|
||||||
|
{{ value|truncatechars:"40" }}
|
||||||
|
{% else %}
|
||||||
|
{{ value }}
|
||||||
|
{% endif %}
|
||||||
|
{% if url %}</a>{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
"""
|
@ -15,6 +15,7 @@ class ContactIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('group', 'title', 'phone', 'email', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -25,6 +26,7 @@ class ContactGroupIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('description',)
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -35,6 +37,7 @@ class ContactRoleIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('description',)
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -46,6 +49,7 @@ class TenantIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('group', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -56,3 +60,4 @@ class TenantGroupIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('description',)
|
||||||
|
@ -10,6 +10,7 @@ class ClusterIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('type', 'group', 'status', 'tenant', 'site', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -20,6 +21,7 @@ class ClusterGroupIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('description',)
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -30,6 +32,7 @@ class ClusterTypeIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('description',)
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -40,6 +43,7 @@ class VirtualMachineIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('site', 'cluster', 'device', 'tenant', 'platform', 'status', 'role', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -51,3 +55,4 @@ class VMInterfaceIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('mtu', 2000),
|
('mtu', 2000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('virtual_machine', 'description')
|
||||||
|
@ -11,6 +11,7 @@ class WirelessLANIndex(SearchIndex):
|
|||||||
('auth_psk', 2000),
|
('auth_psk', 2000),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('group', 'status', 'vlan', 'tenant', 'description')
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -21,6 +22,7 @@ class WirelessLANGroupIndex(SearchIndex):
|
|||||||
('slug', 110),
|
('slug', 110),
|
||||||
('description', 500),
|
('description', 500),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('description',)
|
||||||
|
|
||||||
|
|
||||||
@register_search
|
@register_search
|
||||||
@ -32,3 +34,4 @@ class WirelessLinkIndex(SearchIndex):
|
|||||||
('auth_psk', 2000),
|
('auth_psk', 2000),
|
||||||
('comments', 5000),
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
display_attrs = ('status', 'tenant', 'description')
|
||||||
|
Reference in New Issue
Block a user