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:
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user