feat(backend): use a generic registration approach for search defintion (#13239)

* use a generic registration approach for search defintion

this is in theory open to plugins but not scope for this PR

* reduce unneeded fn wrap

* simplify

* increase max_time by 1 sec
This commit is contained in:
Matthias Mair authored and GitHub committed 2026-11-13 13:14:49 +11:00
1 parent cdfa7f1921
commit 6bf2bbbb63
11 files changed
+70 -34

No files matched your search

+3 -28
View File
@@ -32,6 +32,7 @@ import InvenTree.permissions
import InvenTree.version
from audit.helpers import write_batched
from common.settings import get_global_setting
from generic.search import get_search_filters, get_search_providers
from InvenTree import helpers, ready
from InvenTree.auth_overrides import registration_enabled
from InvenTree.mixins import ListCreateAPI
@@ -797,37 +798,11 @@ class APISearchView(GenericAPIView):
def get_result_types(self):
"""Construct a list of search types we can return."""
import build.api
import company.api
import order.api
import part.api
import stock.api
return {
'build': build.api.BuildList,
'company': company.api.CompanyList,
'supplier': company.api.CompanyList,
'manufacturer': company.api.CompanyList,
'customer': company.api.CompanyList,
'manufacturerpart': company.api.ManufacturerPartList,
'supplierpart': company.api.SupplierPartList,
'part': part.api.PartList,
'partcategory': part.api.CategoryList,
'purchaseorder': order.api.PurchaseOrderViewSet,
'returnorder': order.api.ReturnOrderList,
'salesorder': order.api.SalesOrderList,
'salesordershipment': order.api.SalesOrderShipmentList,
'stockitem': stock.api.StockList,
'stocklocation': stock.api.StockLocationList,
}
return get_search_providers()
def get_result_filters(self):
"""Provide extra filtering options for particular search groups."""
return {
'supplier': {'is_supplier': True},
'manufacturer': {'is_manufacturer': True},
'customer': {'is_customer': True},
}
return get_search_filters()
def post(self, request, *args, **kwargs):
"""Perform search query against available models."""
+2
View File
@@ -34,6 +34,7 @@ import stock.serializers
from build.models import Build, BuildItem, BuildLine
from build.status_codes import BuildStatus, BuildStatusGroups
from data_exporter.mixins import DataExportViewMixin
from generic.search import search_provider
from generic.states.api import StatusView
from InvenTree.api import BulkDeleteMixin, ParameterListMixin, meta_path
from InvenTree.fields import InvenTreeOutputOption, OutputConfiguration
@@ -341,6 +342,7 @@ class BuildListOutputOptions(OutputConfiguration):
OPTIONS = [InvenTreeOutputOption('part_detail', default=True)]
@search_provider(ref_names=['build'])
class BuildList(
DataExportViewMixin,
BuildMixin,
+4 -4
View File
@@ -1883,11 +1883,11 @@ class BuildLineTests(BuildAPITest):
self.assertEqual(len(response.data), BuildLine.objects.count())
# Filter by 'available' status
response = self.get(url, data={'available': True}, max_query_time=10)
response = self.get(url, data={'available': True}, max_query_time=11)
n_t = len(response.data)
self.assertGreater(n_t, 0)
response = self.get(url, data={'available': False}, max_query_time=10)
response = self.get(url, data={'available': False}, max_query_time=11)
n_f = len(response.data)
self.assertGreater(n_f, 0)
@@ -2212,14 +2212,14 @@ class BuildLineTests(BuildAPITest):
StockItem.objects.create(part=line.bom_item.sub_part, quantity=60)
response = self.get(
url, {'build': build.pk, 'available': True}, max_query_time=10
url, {'build': build.pk, 'available': True}, max_query_time=11
)
# We expect 2 lines to have "available" stock
self.assertEqual(len(response.data), 2)
response = self.get(
url, {'build': build.pk, 'available': False}, max_query_time=10
url, {'build': build.pk, 'available': False}, max_query_time=11
)
self.assertEqual(len(response.data), 1)
+11
View File
@@ -10,6 +10,7 @@ from django_filters.rest_framework.filterset import FilterSet
import common.filters
import part.models
from data_exporter.mixins import DataExportViewMixin
from generic.search import search_provider
from InvenTree.api import ListCreateDestroyAPIView, ParameterListMixin, meta_path
from InvenTree.fields import InvenTreeOutputOption, OutputConfiguration
from InvenTree.filters import SEARCH_ORDER_FILTER
@@ -71,6 +72,14 @@ class CompanyMixin(OutputOptionsMixin):
return queryset
@search_provider(
ref_names=['company', 'supplier', 'manufacturer', 'customer'],
default_filters={
'supplier': {'is_supplier': True},
'manufacturer': {'is_manufacturer': True},
'customer': {'is_customer': True},
},
)
class CompanyList(CompanyMixin, ParameterListMixin, DataExportViewMixin, ListCreateAPI):
"""API endpoint for accessing a list of Company objects.
@@ -212,6 +221,7 @@ class ManufacturerPartMixin(SerializerContextMixin):
return queryset
@search_provider(ref_names=['manufacturerpart'])
class ManufacturerPartList(
DataExportViewMixin,
ManufacturerPartMixin,
@@ -387,6 +397,7 @@ class SupplierPartMixin:
return queryset
@search_provider(ref_names=['supplierpart'])
class SupplierPartList(
DataExportViewMixin,
SupplierPartMixin,
@@ -0,0 +1,5 @@
"""Generic search provider registration."""
from .func import get_search_filters, get_search_providers, search_provider
__all__ = ['get_search_filters', 'get_search_providers', 'search_provider']
@@ -0,0 +1,32 @@
"""Support for generic search functionality."""
from collections.abc import Callable
SEARCH_PROVIDERS = {}
SEARCH_FILTERS = {}
def search_provider(
ref_names: list[str] | None = None, default_filters: dict | None = None
):
"""Register a view class as a provider for one or more search result types."""
def decorator(func):
"""Register the view class for each configured search result type."""
filters = default_filters or {}
for name in ref_names or [func.__name__.lower()]:
SEARCH_PROVIDERS[name] = func
SEARCH_FILTERS[name] = filters.get(name, {})
return func
return decorator
def get_search_providers() -> dict[str, Callable]:
"""Return the registered search provider classes."""
return dict(SEARCH_PROVIDERS)
def get_search_filters() -> dict[str, dict]:
"""Return default filters keyed by search result type."""
return dict(SEARCH_FILTERS)
+5
View File
@@ -31,6 +31,7 @@ import company.models
import stock.models as stock_models
import stock.serializers as stock_serializers
from data_exporter.mixins import DataExportViewMixin
from generic.search import search_provider
from generic.states.api import FSMTransitionMixin, StatusView
from InvenTree.api import (
BulkDeleteMixin,
@@ -383,6 +384,7 @@ class PurchaseOrderOutputOptions(OutputConfiguration):
OPTIONS = [InvenTreeOutputOption('supplier_detail')]
@search_provider(ref_names=['purchaseorder'])
class PurchaseOrderViewSet(
SerializerContextMixin,
OrderCreateMixin,
@@ -905,6 +907,7 @@ class SalesOrderOutputOptions(OutputConfiguration):
OPTIONS = [InvenTreeOutputOption('customer_detail')]
@search_provider(ref_names=['salesorder'])
class SalesOrderList(
SalesOrderMixin,
OrderCreateMixin,
@@ -1547,6 +1550,7 @@ class SalesOrderShipmentMixin:
return queryset
@search_provider(ref_names=['salesordershipment'])
class SalesOrderShipmentList(SalesOrderShipmentMixin, ListCreateAPI):
"""API list endpoint for SalesOrderShipment model."""
@@ -1695,6 +1699,7 @@ class ReturnOrderOutputOptions(OutputConfiguration):
OPTIONS = [InvenTreeOutputOption(flag='customer_detail')]
@search_provider(ref_names=['returnorder'])
class ReturnOrderList(
ReturnOrderMixin,
OrderCreateMixin,
@@ -621,7 +621,7 @@ class SalesOrderTest(InvenTreeAPITestCase):
url,
expected_code=200,
benchmark=True,
max_query_time=10.0,
max_query_time=11,
max_query_count=150,
)
+3
View File
@@ -16,6 +16,7 @@ import common.filters
import common.serializers
import part.tasks as part_tasks
from data_exporter.mixins import DataExportViewMixin
from generic.search import search_provider
from InvenTree.api import (
BulkDeleteMixin,
BulkUpdateMixin,
@@ -218,6 +219,7 @@ class CategoryOutputOption(OutputConfiguration):
OPTIONS = [InvenTreeOutputOption(flag='path_detail')]
@search_provider(ref_names=['partcategory'])
class CategoryList(
CategoryMixin,
BulkUpdateMixin,
@@ -1025,6 +1027,7 @@ class PartOutputOptions(OutputConfiguration):
]
@search_provider(ref_names=['part'])
class PartList(
PartMixin,
BulkUpdateMixin,
+1 -1
View File
@@ -149,7 +149,7 @@ class PartCategoryAPITest(InvenTreeAPITestCase):
{'items': [category.pk for category in categories], 'parent': parent_b.pk},
expected_code=200,
max_query_count=60 * len(categories),
max_query_time=10, # Note: in production this is offloaded to the background worker
max_query_time=11, # Note: in production this is offloaded to the background worker
)
for category in categories:
+3
View File
@@ -28,6 +28,7 @@ import stock.serializers as StockSerializers
from build.models import Build
from company.models import Company, ManufacturerPart, SupplierPart
from data_exporter.mixins import DataExportViewMixin
from generic.search import search_provider
from generic.states.api import StatusView
from InvenTree.api import (
BulkCreateMixin,
@@ -442,6 +443,7 @@ class StockLocationOutputOptions(OutputConfiguration):
]
@search_provider(ref_names=['stocklocation'])
class StockLocationList(
DataExportViewMixin,
BulkUpdateMixin,
@@ -1117,6 +1119,7 @@ class StockOutputOptions(OutputConfiguration):
]
@search_provider(ref_names=['stockitem'])
class StockList(
DataExportViewMixin,
BulkUpdateMixin,