Highlight matched portion of field value

This commit is contained in:
jeremystretch 2022-10-19 22:20:53 -04:00
parent 64cb2cc085
commit 6eb2983ccd
4 changed files with 49 additions and 1 deletions

View File

@ -269,6 +269,7 @@ class SiteIndex(SearchIndex):
('description', 500),
('physical_address', 2000),
('shipping_address', 2000),
('comments', 5000),
)

View File

@ -4,6 +4,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldDoesNotExist
from django.db.models.fields.related import RelatedField
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _
from django_tables2.data import TableQuerysetData
@ -11,6 +12,8 @@ from extras.models import CustomField, CustomLink
from extras.choices import CustomFieldVisibilityChoices
from netbox.tables import columns
from utilities.paginator import EnhancedPaginator, get_paginate_count
from utilities.templatetags.builtins.filters import bettertitle
from utilities.utils import highlight_string
__all__ = (
'BaseTable',
@ -206,8 +209,25 @@ class SearchTable(tables.Table):
field = tables.Column()
value = tables.Column()
trim_length = 30
class Meta:
attrs = {
'class': 'table table-hover object-list',
}
empty_text = _('No results found')
def __init__(self, data, highlight=None, **kwargs):
self.highlight = highlight
super().__init__(data, **kwargs)
def render_field(self, value, record):
return bettertitle(record.object._meta.get_field(value).verbose_name)
def render_value(self, value):
if not self.highlight:
return value
value = highlight_string(value, self.highlight, trim_pre=self.trim_length, trim_post=self.trim_length)
return mark_safe(value)

View File

@ -23,6 +23,7 @@ from extras.models import ObjectChange
from extras.tables import ObjectChangeTable
from ipam.models import Aggregate, IPAddress, IPRange, Prefix, VLAN, VRF
from netbox.forms import SearchForm
from netbox.search import LookupTypes
from netbox.search.backends import search_backend
from netbox.tables import SearchTable
from tenancy.models import Tenant
@ -153,6 +154,7 @@ class SearchView(View):
def get(self, request):
results = []
highlight = None
# Initialize search form
form = SearchForm(request.GET) if 'q' in request.GET else SearchForm()
@ -172,7 +174,10 @@ class SearchView(View):
lookup=form.cleaned_data['lookup']
)
table = SearchTable(results)
if form.cleaned_data['lookup'] != LookupTypes.EXACT:
highlight = form.cleaned_data['q']
table = SearchTable(results, highlight=highlight)
# Paginate the table results
RequestConfig(request, {

View File

@ -1,6 +1,7 @@
import datetime
import decimal
import json
import re
from decimal import Decimal
from itertools import count, groupby
@ -9,6 +10,7 @@ from django.core.serializers import serialize
from django.db.models import Count, OuterRef, Subquery
from django.db.models.functions import Coalesce
from django.http import QueryDict
from django.utils.html import escape
from jinja2.sandbox import SandboxedEnvironment
from mptt.models import MPTTModel
@ -472,3 +474,23 @@ def clean_html(html, schemes):
attributes=ALLOWED_ATTRIBUTES,
protocols=schemes
)
def highlight_string(value, highlight, trim_pre=None, trim_post=None, trim_placeholder='...'):
"""
Highlight a string within a string and optionally trim the pre/post portions of the original string.
"""
# Split value on highlight string
try:
pre, match, post = re.split(fr'({highlight})', value, maxsplit=1, flags=re.IGNORECASE)
except ValueError:
# Match not found
return escape(value)
# Trim pre/post sections to length
if trim_pre and len(pre) > trim_pre:
pre = trim_placeholder + pre[-trim_pre:]
if trim_post and len(post) > trim_post:
post = post[:trim_post] + trim_placeholder
return f'{escape(pre)}<mark>{escape(match)}</mark>{escape(post)}'