mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-25 08:46:10 -06:00
Highlight matched portion of field value
This commit is contained in:
parent
64cb2cc085
commit
6eb2983ccd
@ -269,6 +269,7 @@ class SiteIndex(SearchIndex):
|
|||||||
('description', 500),
|
('description', 500),
|
||||||
('physical_address', 2000),
|
('physical_address', 2000),
|
||||||
('shipping_address', 2000),
|
('shipping_address', 2000),
|
||||||
|
('comments', 5000),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import FieldDoesNotExist
|
from django.core.exceptions import FieldDoesNotExist
|
||||||
from django.db.models.fields.related import RelatedField
|
from django.db.models.fields.related import RelatedField
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django_tables2.data import TableQuerysetData
|
from django_tables2.data import TableQuerysetData
|
||||||
|
|
||||||
@ -11,6 +12,8 @@ from extras.models import CustomField, CustomLink
|
|||||||
from extras.choices import CustomFieldVisibilityChoices
|
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.templatetags.builtins.filters import bettertitle
|
||||||
|
from utilities.utils import highlight_string
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'BaseTable',
|
'BaseTable',
|
||||||
@ -206,8 +209,25 @@ class SearchTable(tables.Table):
|
|||||||
field = tables.Column()
|
field = tables.Column()
|
||||||
value = tables.Column()
|
value = tables.Column()
|
||||||
|
|
||||||
|
trim_length = 30
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
attrs = {
|
attrs = {
|
||||||
'class': 'table table-hover object-list',
|
'class': 'table table-hover object-list',
|
||||||
}
|
}
|
||||||
empty_text = _('No results found')
|
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)
|
||||||
|
@ -23,6 +23,7 @@ from extras.models import ObjectChange
|
|||||||
from extras.tables import ObjectChangeTable
|
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.forms import SearchForm
|
from netbox.forms import SearchForm
|
||||||
|
from netbox.search import LookupTypes
|
||||||
from netbox.search.backends import search_backend
|
from netbox.search.backends import search_backend
|
||||||
from netbox.tables import SearchTable
|
from netbox.tables import SearchTable
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
@ -153,6 +154,7 @@ class SearchView(View):
|
|||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
results = []
|
results = []
|
||||||
|
highlight = None
|
||||||
|
|
||||||
# Initialize search form
|
# Initialize search form
|
||||||
form = SearchForm(request.GET) if 'q' in request.GET else SearchForm()
|
form = SearchForm(request.GET) if 'q' in request.GET else SearchForm()
|
||||||
@ -172,7 +174,10 @@ class SearchView(View):
|
|||||||
lookup=form.cleaned_data['lookup']
|
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
|
# Paginate the table results
|
||||||
RequestConfig(request, {
|
RequestConfig(request, {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from itertools import count, groupby
|
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 import Count, OuterRef, Subquery
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.http import QueryDict
|
from django.http import QueryDict
|
||||||
|
from django.utils.html import escape
|
||||||
from jinja2.sandbox import SandboxedEnvironment
|
from jinja2.sandbox import SandboxedEnvironment
|
||||||
from mptt.models import MPTTModel
|
from mptt.models import MPTTModel
|
||||||
|
|
||||||
@ -472,3 +474,23 @@ def clean_html(html, schemes):
|
|||||||
attributes=ALLOWED_ATTRIBUTES,
|
attributes=ALLOWED_ATTRIBUTES,
|
||||||
protocols=schemes
|
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)}'
|
||||||
|
Loading…
Reference in New Issue
Block a user