mirror of
https://github.com/inventree/InvenTree.git
synced 2026-11-14 12:32:44 +00:00
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:
1 parent
cdfa7f1921
commit
6bf2bbbb63
11 files changed
+70
-34
No files matched your search
@@ -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."""
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user