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:
Matthias Mair 2024-03-24 23:10:38 +01:00 committed by GitHub
parent c04ab12fc5
commit 9a0c978f2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 70 additions and 24 deletions

View File

@ -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]

View File

@ -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)

View File

@ -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 = []

View File

@ -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

View File

@ -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

View File

@ -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())

View File

@ -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/',

View File

@ -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)

View File

@ -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

View File

@ -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'