2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-15 05:32:21 +00:00

Generic status endpoint fixes (#10530)

* Allow querying for generic status by class name, add schema return type for AllStatusViews

* Bump version api

* Fix tests
This commit is contained in:
Joe Rogers
2025-10-08 03:39:30 +02:00
committed by GitHub
parent 0c54671abe
commit 7ca72ff262
3 changed files with 61 additions and 34 deletions

View File

@@ -1,12 +1,16 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 404 INVENTREE_API_VERSION = 405
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ INVENTREE_API_TEXT = """
v405 -> 2025-10-07: https://github.com/inventree/InvenTree/pull/10530
- Add response to generic/status endpoint
- Fix logic for generic status model lookup to allow searching by class name string
v404 -> 2025-10-06: https://github.com/inventree/InvenTree/pull/10497 v404 -> 2025-10-06: https://github.com/inventree/InvenTree/pull/10497
- Add minimum_stock to PartBrief api response - Add minimum_stock to PartBrief api response

View File

@@ -4,6 +4,7 @@ import inspect
from django.urls import include, path from django.urls import include, path
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework import serializers from rest_framework import serializers
from rest_framework.generics import GenericAPIView from rest_framework.generics import GenericAPIView
@@ -14,6 +15,7 @@ import common.serializers
import InvenTree.permissions import InvenTree.permissions
from data_exporter.mixins import DataExportViewMixin from data_exporter.mixins import DataExportViewMixin
from InvenTree.filters import SEARCH_ORDER_FILTER from InvenTree.filters import SEARCH_ORDER_FILTER
from InvenTree.helpers import inheritors
from InvenTree.mixins import ListCreateAPI, RetrieveUpdateDestroyAPI from InvenTree.mixins import ListCreateAPI, RetrieveUpdateDestroyAPI
from InvenTree.serializers import EmptySerializer from InvenTree.serializers import EmptySerializer
@@ -64,11 +66,20 @@ class StatusView(GenericAPIView):
"""Perform a GET request to learn information about status codes.""" """Perform a GET request to learn information about status codes."""
status_class = self.get_status_model() status_class = self.get_status_model()
if isinstance(status_class, str):
# Attempt to convert string to class
status_classes = inheritors(StatusCode)
for cls in status_classes:
if cls.__name__ == status_class:
status_class = cls
break
if not inspect.isclass(status_class): if not inspect.isclass(status_class):
raise NotImplementedError('`status_class` not a class') raise NotImplementedError(f'`{status_class}` not a class')
if not issubclass(status_class, StatusCode): if not issubclass(status_class, StatusCode):
raise NotImplementedError('`status_class` not a valid StatusCode class') raise NotImplementedError(f'`{status_class}` not a valid StatusCode class')
data = {'status_class': status_class.__name__, 'values': status_class.dict()} data = {'status_class': status_class.__name__, 'values': status_class.dict()}
@@ -99,11 +110,20 @@ class AllStatusViews(StatusView):
permission_classes = [InvenTree.permissions.IsAuthenticatedOrReadScope] permission_classes = [InvenTree.permissions.IsAuthenticatedOrReadScope]
serializer_class = EmptySerializer serializer_class = EmptySerializer
@extend_schema(operation_id='generic_status_retrieve_all') # Specifically disable pagination for this view
pagination_class = None
@extend_schema(
operation_id='generic_status_retrieve_all',
responses={
200: OpenApiResponse(
description='Mapping from class name to GenericStateClass data',
response=OpenApiTypes.OBJECT,
)
},
)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Perform a GET request to learn information about status codes.""" """Perform a GET request to learn information about status codes."""
from InvenTree.helpers import inheritors
data = {} data = {}
# Find all inherited status classes # Find all inherited status classes

View File

@@ -172,34 +172,37 @@ class GeneralStateTest(InvenTreeTestCase):
rqst = RequestFactory().get('status/') rqst = RequestFactory().get('status/')
force_authenticate(rqst, user=self.user) force_authenticate(rqst, user=self.user)
# Correct call expected = {
resp = view(rqst, **{StatusView.MODEL_REF: GeneralStatus}) 'status_class': 'GeneralStatus',
self.assertDictEqual( 'values': {
resp.data, 'COMPLETE': {
{ 'key': 30,
'status_class': 'GeneralStatus', 'name': 'COMPLETE',
'values': { 'label': 'Complete',
'COMPLETE': { 'color': 'success',
'key': 30, },
'name': 'COMPLETE', 'PENDING': {
'label': 'Complete', 'key': 10,
'color': 'success', 'name': 'PENDING',
}, 'label': 'Pending',
'PENDING': { 'color': 'secondary',
'key': 10, },
'name': 'PENDING', 'PLACED': {
'label': 'Pending', 'key': 20,
'color': 'secondary', 'name': 'PLACED',
}, 'label': 'Placed',
'PLACED': { 'color': 'primary',
'key': 20,
'name': 'PLACED',
'label': 'Placed',
'color': 'primary',
},
}, },
}, },
) }
# Correct call (class)
resp = view(rqst, **{StatusView.MODEL_REF: GeneralStatus})
self.assertDictEqual(resp.data, expected)
# Correct call (name)
resp = view(rqst, **{StatusView.MODEL_REF: 'GeneralStatus'})
self.assertDictEqual(resp.data, expected)
# No status defined # No status defined
resp = view(rqst, **{StatusView.MODEL_REF: None}) resp = view(rqst, **{StatusView.MODEL_REF: None})
@@ -212,13 +215,13 @@ class GeneralStateTest(InvenTreeTestCase):
# Invalid call - not a class # Invalid call - not a class
with self.assertRaises(NotImplementedError) as e: with self.assertRaises(NotImplementedError) as e:
resp = view(rqst, **{StatusView.MODEL_REF: 'invalid'}) resp = view(rqst, **{StatusView.MODEL_REF: 'invalid'})
self.assertEqual(str(e.exception), '`status_class` not a class') self.assertEqual(str(e.exception), '`invalid` not a class')
# Invalid call - not the right class # Invalid call - not the right class
with self.assertRaises(NotImplementedError) as e: with self.assertRaises(NotImplementedError) as e:
resp = view(rqst, **{StatusView.MODEL_REF: object}) resp = view(rqst, **{StatusView.MODEL_REF: object})
self.assertEqual( self.assertEqual(
str(e.exception), '`status_class` not a valid StatusCode class' str(e.exception), "`<class 'object'>` not a valid StatusCode class"
) )