diff --git a/src/backend/InvenTree/InvenTree/api.py b/src/backend/InvenTree/InvenTree/api.py index 4610157e56..f76b3bea3f 100644 --- a/src/backend/InvenTree/InvenTree/api.py +++ b/src/backend/InvenTree/InvenTree/api.py @@ -18,10 +18,11 @@ from rest_framework.response import Response from rest_framework.serializers import ValidationError from rest_framework.views import APIView +import common.models import InvenTree.version import users.models from common.settings import get_global_setting -from InvenTree import helpers +from InvenTree import helpers, ready from InvenTree.auth_overrides import registration_enabled from InvenTree.mixins import ListCreateAPI from InvenTree.sso import sso_registration_enabled @@ -710,6 +711,9 @@ class MetadataView(RetrieveUpdateAPI): """Return the model type associated with this API instance.""" model = self.kwargs.get(self.MODEL_REF, None) + if ready.isGeneratingSchema(): + model = common.models.ProjectCode + if 'lookup_field' in self.kwargs: # Set custom lookup field (instead of default 'pk' value) if supplied self.lookup_field = self.kwargs.pop('lookup_field') diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 7dfc9bffb2..0c6f105590 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,12 +1,15 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 330 +INVENTREE_API_VERSION = 331 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v331 - 2025-04-01 : https://github.com/inventree/InvenTree/pull/9437 + - Set correct types on various formerly-string PK fields as well permissions + - Include metadata request and response types v330 - 2025-03-31 : https://github.com/inventree/InvenTree/pull/9420 - Deconflict operation id between single and bulk destroy operations diff --git a/src/backend/InvenTree/build/api.py b/src/backend/InvenTree/build/api.py index 9fb43d1c87..0720407fd0 100644 --- a/src/backend/InvenTree/build/api.py +++ b/src/backend/InvenTree/build/api.py @@ -8,6 +8,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.utils import extend_schema_field +from rest_framework import serializers from rest_framework.exceptions import ValidationError import build.admin @@ -103,6 +105,7 @@ class BuildFilter(rest_filters.FilterSet): label=_('Category'), ) + @extend_schema_field(serializers.IntegerField(help_text=_('Category'))) def filter_category(self, queryset, name, category): """Filter by part category (including sub-categories).""" categories = category.get_descendants(include_self=True) @@ -114,6 +117,7 @@ class BuildFilter(rest_filters.FilterSet): method='filter_ancestor', ) + @extend_schema_field(serializers.IntegerField(help_text=_('Ancestor Build'))) def filter_ancestor(self, queryset, name, parent): """Filter by 'parent' build order.""" builds = parent.get_descendants(include_self=False) @@ -293,6 +297,7 @@ class BuildFilter(rest_filters.FilterSet): label=_('Exclude Tree'), ) + @extend_schema_field(serializers.IntegerField(help_text=_('Exclude Tree'))) def filter_exclude_tree(self, queryset, name, value): """Filter by excluding a tree of Build objects.""" queryset = queryset.exclude( diff --git a/src/backend/InvenTree/common/serializers.py b/src/backend/InvenTree/common/serializers.py index 896c75d9e3..2c4e5403ef 100644 --- a/src/backend/InvenTree/common/serializers.py +++ b/src/backend/InvenTree/common/serializers.py @@ -6,6 +6,8 @@ from django.urls import reverse from django.utils.translation import gettext_lazy as _ import django_q.models +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import extend_schema_field from error_report.models import Error from flags.state import flag_state from rest_framework import serializers @@ -28,6 +30,7 @@ from plugin import registry as plugin_registry from users.serializers import OwnerSerializer, UserSerializer +@extend_schema_field(OpenApiTypes.STR) class SettingsValueField(serializers.Field): """Custom serializer field for a settings value.""" @@ -35,7 +38,7 @@ class SettingsValueField(serializers.Field): """Return the object instance, not the attribute value.""" return instance - def to_representation(self, instance): + def to_representation(self, instance) -> str: """Return the value of the setting. Protected settings are returned as '***' @@ -47,7 +50,7 @@ class SettingsValueField(serializers.Field): else: return str(instance.value) - def to_internal_value(self, data): + def to_internal_value(self, data) -> str: """Return the internal value of the setting.""" if data is None: return '' diff --git a/src/backend/InvenTree/company/api.py b/src/backend/InvenTree/company/api.py index b9cdd44835..97c04117dd 100644 --- a/src/backend/InvenTree/company/api.py +++ b/src/backend/InvenTree/company/api.py @@ -306,7 +306,7 @@ class SupplierPartFilter(rest_filters.FilterSet): label=_('Company'), queryset=Company.objects.all(), method='filter_company' ) - def filter_company(self, queryset, name, value): + def filter_company(self, queryset, name, value: int): """Filter the queryset by either manufacturer or supplier.""" return queryset.filter( Q(manufacturer_part__manufacturer=value) | Q(supplier=value) diff --git a/src/backend/InvenTree/order/api.py b/src/backend/InvenTree/order/api.py index 11e31cca0d..f09a04e0e2 100644 --- a/src/backend/InvenTree/order/api.py +++ b/src/backend/InvenTree/order/api.py @@ -11,8 +11,11 @@ from django.http.response import JsonResponse from django.urls import include, path, re_path from django.utils.translation import gettext_lazy as _ +import rest_framework.serializers from django_filters import rest_framework as rest_filters from django_ical.views import ICalFeed +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 @@ -289,6 +292,7 @@ class PurchaseOrderFilter(OrderFilter): method='filter_part', ) + @extend_schema_field(rest_framework.serializers.IntegerField(help_text=_('Part'))) def filter_part(self, queryset, name, part: Part): """Filter by provided Part instance.""" orders = part.purchase_orders() @@ -301,6 +305,9 @@ class PurchaseOrderFilter(OrderFilter): method='filter_supplier_part', ) + @extend_schema_field( + rest_framework.serializers.IntegerField(help_text=_('Supplier Part')) + ) def filter_supplier_part( self, queryset, name, supplier_part: company.models.SupplierPart ): @@ -520,6 +527,9 @@ class PurchaseOrderLineItemFilter(LineItemFilter): label=_('Internal Part'), ) + @extend_schema_field( + rest_framework.serializers.IntegerField(help_text=_('Internal Part')) + ) def filter_base_part(self, queryset, name, base_part): """Filter by the 'base_part' attribute. @@ -735,6 +745,7 @@ class SalesOrderFilter(OrderFilter): queryset=Part.objects.all(), field_name='part', method='filter_part' ) + @extend_schema_field(OpenApiTypes.INT) def filter_part(self, queryset, name, part): """Filter SalesOrder by selected 'part'. @@ -1108,6 +1119,7 @@ class SalesOrderAllocationFilter(rest_filters.FilterSet): queryset=Part.objects.all(), method='filter_part', label=_('Part') ) + @extend_schema_field(rest_framework.serializers.IntegerField(help_text=_('Part'))) def filter_part(self, queryset, name, part): """Filter by the 'part' attribute. @@ -1335,6 +1347,7 @@ class ReturnOrderFilter(OrderFilter): queryset=Part.objects.all(), field_name='part', method='filter_part' ) + @extend_schema_field(OpenApiTypes.INT) def filter_part(self, queryset, name, part): """Filter by selected 'part'. diff --git a/src/backend/InvenTree/part/api.py b/src/backend/InvenTree/part/api.py index 73f0c2f559..6fad282fe9 100644 --- a/src/backend/InvenTree/part/api.py +++ b/src/backend/InvenTree/part/api.py @@ -1441,6 +1441,7 @@ class PartRelatedFilter(rest_filters.FilterSet): queryset=Part.objects.all(), method='filter_part', label=_('Part') ) + @extend_schema_field(serializers.IntegerField(help_text=_('Part'))) def filter_part(self, queryset, name, part): """Filter queryset to include only PartRelated objects which reference the specified part.""" return queryset.filter(Q(part_1=part) | Q(part_2=part)).distinct() diff --git a/src/backend/InvenTree/users/serializers.py b/src/backend/InvenTree/users/serializers.py index 15cb5fc796..a6ddea331d 100644 --- a/src/backend/InvenTree/users/serializers.py +++ b/src/backend/InvenTree/users/serializers.py @@ -52,7 +52,7 @@ class GroupSerializer(InvenTreeModelSerializer): permissions = serializers.SerializerMethodField(allow_null=True) - def get_permissions(self, group: Group): + def get_permissions(self, group: Group) -> dict: """Return a list of permissions associated with the group.""" return generate_permission_dict(group.permissions.all()) @@ -109,7 +109,7 @@ class RoleSerializer(InvenTreeModelSerializer): return generate_permission_dict(permissions) -def generate_permission_dict(permissions): +def generate_permission_dict(permissions) -> dict: """Generate a dictionary of permissions for a given set of permissions.""" perms = {}