From e66902c05cd6cd1eff07c1bb5d24ea0c9a5d950c Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Mon, 17 Oct 2022 15:16:54 -0400 Subject: [PATCH] Extend search backend to filter by object type --- netbox/netbox/search/__init__.py | 3 ++- netbox/netbox/search/backends.py | 21 ++++++++++++++------- netbox/netbox/views/__init__.py | 17 ++++++++++------- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/netbox/netbox/search/__init__.py b/netbox/netbox/search/__init__.py index 8989c0c77..b4169359b 100644 --- a/netbox/netbox/search/__init__.py +++ b/netbox/netbox/search/__init__.py @@ -23,7 +23,8 @@ class LookupTypes: def get_registry(): r = {} for app_label, models in registry['search'].items(): - r.update(**models) + for model_name, idx in models.items(): + r[f'{app_label}.{model_name}'] = idx return r diff --git a/netbox/netbox/search/backends.py b/netbox/netbox/search/backends.py index fbd9a83a7..2d5e5b007 100644 --- a/netbox/netbox/search/backends.py +++ b/netbox/netbox/search/backends.py @@ -51,19 +51,20 @@ class SearchBackend: for app_label, models in registry['search'].items(): for name, cls in models.items(): title = cls.model._meta.verbose_name.title() - categories[cls.get_category()][name] = title + value = f'{app_label}.{name}' + categories[cls.get_category()][value] = title # Compile a nested tuple of choices for form rendering results = ( ('', 'All Objects'), - *[(category, choices.items()) for category, choices in categories.items()] + *[(category, list(choices.items())) for category, choices in categories.items()] ) self._search_choice_options = results return self._search_choice_options - def search(self, request, value, lookup=DEFAULT_LOOKUP_TYPE): + def search(self, request, value, object_types=None, lookup=DEFAULT_LOOKUP_TYPE): """ Search cached object representations for the given value. """ @@ -114,11 +115,16 @@ class FilterSetSearchBackend(SearchBackend): Legacy search backend. Performs a discrete database query for each registered object type, using the FilterSet class specified by the index for each. """ - def search(self, request, value, lookup=DEFAULT_LOOKUP_TYPE): + def search(self, request, value, object_types=None, lookup=DEFAULT_LOOKUP_TYPE): results = [] search_registry = get_registry() - for obj_type in search_registry.keys(): + if object_types is not None: + keys = [f'{ct.app_label}.{ct.name}' for ct in object_types] + else: + keys = search_registry.keys() + + for obj_type in keys: queryset = getattr(search_registry[obj_type], 'queryset', None) if not queryset: @@ -154,16 +160,17 @@ class FilterSetSearchBackend(SearchBackend): class CachedValueSearchBackend(SearchBackend): - def search(self, request, value, lookup=DEFAULT_LOOKUP_TYPE): + def search(self, request, value, object_types=None, lookup=DEFAULT_LOOKUP_TYPE): # Define the search parameters - # TODO: Filter object types to only those which the use has permission to view params = { f'value__{lookup}': value } if lookup != LookupTypes.EXACT: # Partial matches are valid only on string values params['type'] = FieldTypes.STRING + if object_types: + params['object_type__in'] = object_types # Construct the base queryset to retrieve matching results queryset = CachedValue.objects.filter(**params).annotate( diff --git a/netbox/netbox/views/__init__.py b/netbox/netbox/views/__init__.py index e0948c3ce..eb91e0d60 100644 --- a/netbox/netbox/views/__init__.py +++ b/netbox/netbox/views/__init__.py @@ -2,6 +2,7 @@ import platform import sys from django.conf import settings +from django.contrib.contenttypes.models import ContentType from django.core.cache import cache from django.http import HttpResponseServerError from django.shortcuts import redirect, render @@ -150,17 +151,19 @@ class SearchView(View): def get(self, request): form = SearchForm(request.GET) + object_types = None results = [] if form.is_valid(): - # If an object type has been specified, redirect to the dedicated view for it - if form.cleaned_data['obj_type']: - search_registry = get_registry() - object_type = form.cleaned_data['obj_type'] - url = reverse(search_registry[object_type].url) - return redirect(f"{url}?q={form.cleaned_data['q']}") - results = search_backend.search(request, form.cleaned_data['q']) + # Restrict results by object type + if form.cleaned_data['obj_type']: + app_label, model_name = form.cleaned_data['obj_type'].split('.') + object_types = [ + ContentType.objects.get_by_natural_key(app_label, model_name) + ] + + results = search_backend.search(request, form.cleaned_data['q'], object_types=object_types) return render(request, 'search.html', { 'form': form,