Extend search backend to filter by object type

This commit is contained in:
jeremystretch 2022-10-17 15:16:54 -04:00
parent 79c6bec195
commit e66902c05c
3 changed files with 26 additions and 15 deletions

View File

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

View File

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

View File

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