mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Fix a few warnings in api generation (#6836)
* remove unuses api method * update API docstrings to be more acurate * updated typing for fields * upate serializer to avoid collision * fix typing for API generation * fix ModelChoiceFilter inference * fix typing for manufacturer * bump API version * fix test * fix type checker warnings * fix api path
This commit is contained in:
parent
c04ab12fc5
commit
9a0c978f2f
@ -91,7 +91,7 @@ class VersionView(APIView):
|
||||
})
|
||||
|
||||
|
||||
class VersionSerializer(serializers.Serializer):
|
||||
class VersionInformationSerializer(serializers.Serializer):
|
||||
"""Serializer for a single version."""
|
||||
|
||||
version = serializers.CharField()
|
||||
@ -101,21 +101,21 @@ class VersionSerializer(serializers.Serializer):
|
||||
latest = serializers.BooleanField()
|
||||
|
||||
class Meta:
|
||||
"""Meta class for VersionSerializer."""
|
||||
"""Meta class for VersionInformationSerializer."""
|
||||
|
||||
fields = ['version', 'date', 'gh', 'text', 'latest']
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class VersionApiSerializer(serializers.Serializer):
|
||||
"""Serializer for the version api endpoint."""
|
||||
|
||||
VersionSerializer(many=True)
|
||||
VersionInformationSerializer(many=True)
|
||||
|
||||
|
||||
class VersionTextView(ListAPI):
|
||||
"""Simple JSON endpoint for InvenTree version text."""
|
||||
|
||||
serializer_class = VersionSerializer
|
||||
serializer_class = VersionInformationSerializer
|
||||
|
||||
permission_classes = [permissions.IsAdminUser]
|
||||
|
||||
|
@ -1,11 +1,15 @@
|
||||
"""InvenTree API version information."""
|
||||
|
||||
# InvenTree API version
|
||||
INVENTREE_API_VERSION = 184
|
||||
INVENTREE_API_VERSION = 185
|
||||
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
|
||||
|
||||
INVENTREE_API_TEXT = """
|
||||
|
||||
v185 - 2024-03-24 : https://github.com/inventree/InvenTree/pull/6836
|
||||
- Remove /plugin/activate endpoint
|
||||
- Update docstrings and typing for various API endpoints (no functional changes)
|
||||
|
||||
v184 - 2024-03-17 : https://github.com/inventree/InvenTree/pull/10464
|
||||
- Add additional fields for tests (start/end datetime, test station)
|
||||
|
||||
|
@ -63,7 +63,7 @@ class SettingsSerializer(InvenTreeModelSerializer):
|
||||
|
||||
typ = serializers.CharField(read_only=True)
|
||||
|
||||
def get_choices(self, obj):
|
||||
def get_choices(self, obj) -> list:
|
||||
"""Returns the choices available for a given item."""
|
||||
results = []
|
||||
|
||||
|
@ -82,7 +82,7 @@ class CompanyDetail(RetrieveUpdateDestroyAPI):
|
||||
|
||||
|
||||
class CompanyAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
|
||||
"""API endpoint for the CompanyAttachment model."""
|
||||
"""API endpoint for listing, creating and bulk deleting a CompanyAttachment."""
|
||||
|
||||
queryset = CompanyAttachment.objects.all()
|
||||
serializer_class = CompanyAttachmentSerializer
|
||||
@ -215,7 +215,7 @@ class ManufacturerPartDetail(RetrieveUpdateDestroyAPI):
|
||||
|
||||
|
||||
class ManufacturerPartAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
|
||||
"""API endpoint for listing (and creating) a ManufacturerPartAttachment (file upload)."""
|
||||
"""API endpoint for listing, creating and bulk deleting a ManufacturerPartAttachment (file upload)."""
|
||||
|
||||
queryset = ManufacturerPartAttachment.objects.all()
|
||||
serializer_class = ManufacturerPartAttachmentSerializer
|
||||
|
@ -629,7 +629,7 @@ class PurchaseOrderExtraLineDetail(RetrieveUpdateDestroyAPI):
|
||||
|
||||
|
||||
class SalesOrderAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
|
||||
"""API endpoint for listing (and creating) a SalesOrderAttachment (file upload)."""
|
||||
"""API endpoint for listing, creating and bulk deleting a SalesOrderAttachment (file upload)."""
|
||||
|
||||
queryset = models.SalesOrderAttachment.objects.all()
|
||||
serializer_class = serializers.SalesOrderAttachmentSerializer
|
||||
@ -1097,7 +1097,7 @@ class SalesOrderShipmentComplete(CreateAPI):
|
||||
|
||||
|
||||
class PurchaseOrderAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
|
||||
"""API endpoint for listing (and creating) a PurchaseOrderAttachment (file upload)."""
|
||||
"""API endpoint for listing, creating and bulk deleting) a PurchaseOrderAttachment (file upload)."""
|
||||
|
||||
queryset = models.PurchaseOrderAttachment.objects.all()
|
||||
serializer_class = serializers.PurchaseOrderAttachmentSerializer
|
||||
@ -1363,7 +1363,7 @@ class ReturnOrderExtraLineDetail(RetrieveUpdateDestroyAPI):
|
||||
|
||||
|
||||
class ReturnOrderAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
|
||||
"""API endpoint for listing (and creating) a ReturnOrderAttachment (file upload)."""
|
||||
"""API endpoint for listing, creating and bulk deleting a ReturnOrderAttachment (file upload)."""
|
||||
|
||||
queryset = models.ReturnOrderAttachment.objects.all()
|
||||
serializer_class = serializers.ReturnOrderAttachmentSerializer
|
||||
|
@ -10,6 +10,8 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from django_filters import rest_framework as rest_filters
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from rest_framework import permissions, serializers, status
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.response import Response
|
||||
@ -214,6 +216,7 @@ class CategoryFilter(rest_filters.FilterSet):
|
||||
help_text=_('Exclude sub-categories under the specified category'),
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def filter_exclude_tree(self, queryset, name, value):
|
||||
"""Exclude all sub-categories under the specified category."""
|
||||
# Exclude the specified category
|
||||
@ -406,7 +409,7 @@ class PartInternalPriceList(ListCreateAPI):
|
||||
|
||||
|
||||
class PartAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
|
||||
"""API endpoint for listing (and creating) a PartAttachment (file upload)."""
|
||||
"""API endpoint for listing, creating and bulk deleting a PartAttachment (file upload)."""
|
||||
|
||||
queryset = PartAttachment.objects.all()
|
||||
serializer_class = part_serializers.PartAttachmentSerializer
|
||||
@ -1003,6 +1006,7 @@ class PartFilter(rest_filters.FilterSet):
|
||||
method='filter_convert_from',
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def filter_convert_from(self, queryset, name, part):
|
||||
"""Limit the queryset to valid conversion options for the specified part."""
|
||||
conversion_options = part.get_conversion_options()
|
||||
@ -1017,6 +1021,7 @@ class PartFilter(rest_filters.FilterSet):
|
||||
method='filter_exclude_tree',
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def filter_exclude_tree(self, queryset, name, part):
|
||||
"""Exclude all parts and variants 'down' from the specified part from the queryset."""
|
||||
children = part.get_descendants(include_self=True)
|
||||
@ -1027,6 +1032,7 @@ class PartFilter(rest_filters.FilterSet):
|
||||
label='Ancestor', queryset=Part.objects.all(), method='filter_ancestor'
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def filter_ancestor(self, queryset, name, part):
|
||||
"""Limit queryset to descendants of the specified ancestor part."""
|
||||
descendants = part.get_descendants(include_self=False)
|
||||
@ -1044,6 +1050,7 @@ class PartFilter(rest_filters.FilterSet):
|
||||
label='In BOM Of', queryset=Part.objects.all(), method='filter_in_bom'
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def filter_in_bom(self, queryset, name, part):
|
||||
"""Limit queryset to parts in the BOM for the specified part."""
|
||||
bom_parts = part.get_parts_in_bom()
|
||||
@ -1528,6 +1535,7 @@ class PartParameterTemplateFilter(rest_filters.FilterSet):
|
||||
queryset=Part.objects.all(), method='filter_part', label=_('Part')
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def filter_part(self, queryset, name, part):
|
||||
"""Filter queryset to include only PartParameterTemplates which are referenced by a part."""
|
||||
parameters = PartParameter.objects.filter(part=part)
|
||||
@ -1541,6 +1549,7 @@ class PartParameterTemplateFilter(rest_filters.FilterSet):
|
||||
label=_('Category'),
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def filter_category(self, queryset, name, category):
|
||||
"""Filter queryset to include only PartParameterTemplates which are referenced by parts in this category."""
|
||||
cats = category.get_descendants(include_self=True)
|
||||
@ -1828,6 +1837,7 @@ class BomFilter(rest_filters.FilterSet):
|
||||
queryset=Part.objects.all(), method='filter_uses', label=_('Uses')
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def filter_uses(self, queryset, name, part):
|
||||
"""Filter the queryset based on the specified part."""
|
||||
return queryset.filter(part.get_used_in_bom_item_filter())
|
||||
|
@ -466,7 +466,6 @@ plugin_api_urls = [
|
||||
# Plugin management
|
||||
path('reload/', PluginReload.as_view(), name='api-plugin-reload'),
|
||||
path('install/', PluginInstall.as_view(), name='api-plugin-install'),
|
||||
path('activate/', PluginActivate.as_view(), name='api-plugin-activate'),
|
||||
# Registry status
|
||||
path(
|
||||
'status/',
|
||||
|
@ -90,12 +90,19 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
|
||||
def test_plugin_activate(self):
|
||||
"""Test the plugin activate."""
|
||||
test_plg = self.plugin_confs.first()
|
||||
assert test_plg is not None
|
||||
|
||||
def assert_plugin_active(self, active):
|
||||
self.assertEqual(PluginConfig.objects.all().first().active, active)
|
||||
plgs = PluginConfig.objects.all().first()
|
||||
assert plgs is not None
|
||||
self.assertEqual(plgs.active, active)
|
||||
|
||||
# Should not work - not a superuser
|
||||
response = self.client.post(reverse('api-plugin-activate'), {}, follow=True)
|
||||
response = self.client.post(
|
||||
reverse('api-plugin-detail-activate', kwargs={'pk': test_plg.pk}),
|
||||
{},
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Make user superuser
|
||||
@ -109,7 +116,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
|
||||
# Activate plugin with detail url
|
||||
assert_plugin_active(self, False)
|
||||
response = self.client.patch(
|
||||
reverse('api-plugin-detail-activate', kwargs={'pk': test_plg.id}),
|
||||
reverse('api-plugin-detail-activate', kwargs={'pk': test_plg.pk}),
|
||||
{},
|
||||
follow=True,
|
||||
)
|
||||
@ -123,7 +130,9 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
|
||||
# Activate plugin
|
||||
assert_plugin_active(self, False)
|
||||
response = self.client.patch(
|
||||
reverse('api-plugin-activate'), {'pk': test_plg.pk}, follow=True
|
||||
reverse('api-plugin-detail-activate', kwargs={'pk': test_plg.pk}),
|
||||
{},
|
||||
follow=True,
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
assert_plugin_active(self, True)
|
||||
@ -133,6 +142,8 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
|
||||
url = reverse('admin:plugin_pluginconfig_changelist')
|
||||
|
||||
test_plg = self.plugin_confs.first()
|
||||
assert test_plg is not None
|
||||
|
||||
# deactivate plugin
|
||||
response = self.client.post(
|
||||
url,
|
||||
@ -181,6 +192,8 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
|
||||
"""Test the PluginConfig model."""
|
||||
# check mixin registry
|
||||
plg = self.plugin_confs.first()
|
||||
assert plg is not None
|
||||
|
||||
mixin_dict = plg.mixins()
|
||||
self.assertIn('base', mixin_dict)
|
||||
self.assertDictContainsSubset(
|
||||
@ -190,6 +203,8 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
|
||||
# check reload on save
|
||||
with self.assertWarns(Warning) as cm:
|
||||
plg_inactive = self.plugin_confs.filter(active=False).first()
|
||||
assert plg_inactive is not None
|
||||
|
||||
plg_inactive.active = True
|
||||
plg_inactive.save()
|
||||
self.assertEqual(cm.warning.args[0], 'A reload was triggered')
|
||||
@ -208,7 +223,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
|
||||
|
||||
# Wrong with pk
|
||||
with self.assertRaises(NotFound) as exc:
|
||||
check_plugin(plugin_slug=None, plugin_pk='123')
|
||||
check_plugin(plugin_slug=None, plugin_pk=123)
|
||||
self.assertEqual(str(exc.exception.detail), "Plugin '123' not installed")
|
||||
|
||||
def test_plugin_settings(self):
|
||||
@ -219,6 +234,8 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
|
||||
|
||||
# Activate the 'sample' plugin via the API
|
||||
cfg = PluginConfig.objects.filter(key='sample').first()
|
||||
assert cfg is not None
|
||||
|
||||
url = reverse('api-plugin-detail-activate', kwargs={'pk': cfg.pk})
|
||||
self.client.patch(url, {}, expected_code=200)
|
||||
|
||||
|
@ -11,6 +11,8 @@ from django.urls import include, path
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from django_filters import rest_framework as rest_filters
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ValidationError
|
||||
@ -480,9 +482,17 @@ class StockFilter(rest_filters.FilterSet):
|
||||
# Relationship filters
|
||||
manufacturer = rest_filters.ModelChoiceFilter(
|
||||
label='Manufacturer',
|
||||
queryset=Company.objects.filter(is_manufacturer=True),
|
||||
field_name='manufacturer_part__manufacturer',
|
||||
queryset=Company.objects.all(),
|
||||
method='filter_manufacturer',
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def filter_manufacturer(self, queryset, name, company):
|
||||
"""Filter by manufacturer."""
|
||||
return queryset.filter(
|
||||
Q(is_manufacturer=True) & Q(manufacturer_part__manufacturer=company)
|
||||
)
|
||||
|
||||
supplier = rest_filters.ModelChoiceFilter(
|
||||
label='Supplier',
|
||||
queryset=Company.objects.filter(is_supplier=True),
|
||||
@ -725,6 +735,7 @@ class StockFilter(rest_filters.FilterSet):
|
||||
label='Ancestor', queryset=StockItem.objects.all(), method='filter_ancestor'
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def filter_ancestor(self, queryset, name, ancestor):
|
||||
"""Filter based on ancestor stock item."""
|
||||
return queryset.filter(parent__in=ancestor.get_descendants(include_self=True))
|
||||
@ -735,6 +746,7 @@ class StockFilter(rest_filters.FilterSet):
|
||||
method='filter_category',
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def filter_category(self, queryset, name, category):
|
||||
"""Filter based on part category."""
|
||||
child_categories = category.get_descendants(include_self=True)
|
||||
@ -745,6 +757,7 @@ class StockFilter(rest_filters.FilterSet):
|
||||
label=_('BOM Item'), queryset=BomItem.objects.all(), method='filter_bom_item'
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def filter_bom_item(self, queryset, name, bom_item):
|
||||
"""Filter based on BOM item."""
|
||||
return queryset.filter(bom_item.get_stock_filter())
|
||||
@ -753,6 +766,7 @@ class StockFilter(rest_filters.FilterSet):
|
||||
label=_('Part Tree'), queryset=Part.objects.all(), method='filter_part_tree'
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def filter_part_tree(self, queryset, name, part_tree):
|
||||
"""Filter based on part tree."""
|
||||
return queryset.filter(part__tree_id=part_tree.tree_id)
|
||||
@ -761,6 +775,7 @@ class StockFilter(rest_filters.FilterSet):
|
||||
label=_('Company'), queryset=Company.objects.all(), method='filter_company'
|
||||
)
|
||||
|
||||
@extend_schema_field(OpenApiTypes.INT)
|
||||
def filter_company(self, queryset, name, company):
|
||||
"""Filter by company (either manufacturer or supplier)."""
|
||||
return queryset.filter(
|
||||
@ -813,6 +828,7 @@ class StockList(APIDownloadMixin, ListCreateDestroyAPIView):
|
||||
|
||||
- GET: Return a list of all StockItem objects (with optional query filters)
|
||||
- POST: Create a new StockItem
|
||||
- DELETE: Delete multiple StockItem objects
|
||||
"""
|
||||
|
||||
serializer_class = StockSerializers.StockItemSerializer
|
||||
@ -1199,7 +1215,7 @@ class StockList(APIDownloadMixin, ListCreateDestroyAPIView):
|
||||
|
||||
|
||||
class StockAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
|
||||
"""API endpoint for listing (and creating) a StockItemAttachment (file upload)."""
|
||||
"""API endpoint for listing, creating and bulk deleting a StockItemAttachment (file upload)."""
|
||||
|
||||
queryset = StockItemAttachment.objects.all()
|
||||
serializer_class = StockSerializers.StockItemAttachmentSerializer
|
||||
|
@ -29,11 +29,11 @@ class PreferredSerializer(serializers.Serializer):
|
||||
pui = serializers.SerializerMethodField(read_only=True)
|
||||
cui = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
def get_pui(self, obj):
|
||||
def get_pui(self, obj) -> bool:
|
||||
"""Return true if preferred method is PUI."""
|
||||
return obj['preferred_method'] == 'pui'
|
||||
|
||||
def get_cui(self, obj):
|
||||
def get_cui(self, obj) -> bool:
|
||||
"""Return true if preferred method is CUI."""
|
||||
return obj['preferred_method'] == 'cui'
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user