This commit is contained in:
Jeremy Stretch 2023-10-28 13:34:41 -04:00
parent 77208bf5f3
commit fa25c2c983
6 changed files with 55 additions and 2 deletions

View File

@ -44,6 +44,7 @@ class DeviceIndex(SearchIndex):
('description', 500), ('description', 500),
('comments', 5000), ('comments', 5000),
) )
display_attrs = ('site', 'location', 'rack', 'device_type', 'description')
@register_search @register_search
@ -282,6 +283,7 @@ class SiteIndex(SearchIndex):
('shipping_address', 2000), ('shipping_address', 2000),
('comments', 5000), ('comments', 5000),
) )
display_attrs = ('region', 'group', 'status', 'description')
@register_search @register_search

View File

@ -4,7 +4,9 @@ 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.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__ = (
@ -56,3 +58,16 @@ 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):
indexer = registry['search'].get(content_type_identifier(self.object_type))
attrs = {}
for attr in getattr(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

View File

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

View File

@ -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
@ -129,6 +130,10 @@ class CachedValueSearchBackend(SearchBackend):
) )
)[:MAX_RESULTS] )[:MAX_RESULTS]
# Find the ContentTypes for all objects present in the search results
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 +149,31 @@ class CachedValueSearchBackend(SearchBackend):
params params
) )
# Prefetch display attributes
for ct in content_types:
model = ct.model_class()
indexer = registry['search'].get(content_type_identifier(ct))
display_attrs = getattr(indexer, 'display_attrs', None)
if not display_attrs:
continue
prefetch_fields = []
for attr in display_attrs:
field = model._meta.get_field(attr)
if type(field) is ForeignKey:
prefetch_fields.append(f'object__{attr}')
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):

View File

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

View File

@ -0,0 +1,5 @@
SEARCH_RESULT_ATTRS = """
{% for name, value in record.display_attrs.items %}
<span class="badge bg-secondary">{{ name|bettertitle }}: {{ value }}</span>
{% endfor %}
"""