2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-14 21:22:20 +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
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."""
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
- Add minimum_stock to PartBrief api response

View File

@@ -4,6 +4,7 @@ import inspect
from django.urls import include, path
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework import serializers
from rest_framework.generics import GenericAPIView
@@ -14,6 +15,7 @@ import common.serializers
import InvenTree.permissions
from data_exporter.mixins import DataExportViewMixin
from InvenTree.filters import SEARCH_ORDER_FILTER
from InvenTree.helpers import inheritors
from InvenTree.mixins import ListCreateAPI, RetrieveUpdateDestroyAPI
from InvenTree.serializers import EmptySerializer
@@ -64,11 +66,20 @@ class StatusView(GenericAPIView):
"""Perform a GET request to learn information about status codes."""
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):
raise NotImplementedError('`status_class` not a class')
raise NotImplementedError(f'`{status_class}` not a class')
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()}
@@ -99,11 +110,20 @@ class AllStatusViews(StatusView):
permission_classes = [InvenTree.permissions.IsAuthenticatedOrReadScope]
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):
"""Perform a GET request to learn information about status codes."""
from InvenTree.helpers import inheritors
data = {}
# Find all inherited status classes

View File

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