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(): def get_registry():
r = {} r = {}
for app_label, models in registry['search'].items(): 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 return r

View File

@ -51,19 +51,20 @@ class SearchBackend:
for app_label, models in registry['search'].items(): for app_label, models in registry['search'].items():
for name, cls in models.items(): for name, cls in models.items():
title = cls.model._meta.verbose_name.title() 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 # Compile a nested tuple of choices for form rendering
results = ( results = (
('', 'All Objects'), ('', '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 self._search_choice_options = results
return self._search_choice_options 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. 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 Legacy search backend. Performs a discrete database query for each registered object type, using the FilterSet
class specified by the index for each. 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 = [] results = []
search_registry = get_registry() 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) queryset = getattr(search_registry[obj_type], 'queryset', None)
if not queryset: if not queryset:
@ -154,16 +160,17 @@ class FilterSetSearchBackend(SearchBackend):
class CachedValueSearchBackend(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 # Define the search parameters
# TODO: Filter object types to only those which the use has permission to view
params = { params = {
f'value__{lookup}': value f'value__{lookup}': value
} }
if lookup != LookupTypes.EXACT: if lookup != LookupTypes.EXACT:
# Partial matches are valid only on string values # Partial matches are valid only on string values
params['type'] = FieldTypes.STRING params['type'] = FieldTypes.STRING
if object_types:
params['object_type__in'] = object_types
# Construct the base queryset to retrieve matching results # Construct the base queryset to retrieve matching results
queryset = CachedValue.objects.filter(**params).annotate( queryset = CachedValue.objects.filter(**params).annotate(

View File

@ -2,6 +2,7 @@ import platform
import sys import sys
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.cache import cache from django.core.cache import cache
from django.http import HttpResponseServerError from django.http import HttpResponseServerError
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
@ -150,17 +151,19 @@ class SearchView(View):
def get(self, request): def get(self, request):
form = SearchForm(request.GET) form = SearchForm(request.GET)
object_types = None
results = [] results = []
if form.is_valid(): 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', { return render(request, 'search.html', {
'form': form, 'form': form,