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:
@@ -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
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -172,11 +172,7 @@ 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})
|
|
||||||
self.assertDictEqual(
|
|
||||||
resp.data,
|
|
||||||
{
|
|
||||||
'status_class': 'GeneralStatus',
|
'status_class': 'GeneralStatus',
|
||||||
'values': {
|
'values': {
|
||||||
'COMPLETE': {
|
'COMPLETE': {
|
||||||
@@ -198,8 +194,15 @@ class GeneralStateTest(InvenTreeTestCase):
|
|||||||
'color': 'primary',
|
'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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user