mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	Schema: Mark nullable fields (#9546)
* Add allow_null to nullable fields * Fix serializer for InfoApi, add nullable flags * Bump api version * Fix incorrectly replaced required tag
This commit is contained in:
		| @@ -229,11 +229,11 @@ class InfoApiSerializer(serializers.Serializer): | ||||
|  | ||||
|         logo = serializers.CharField() | ||||
|         splash = serializers.CharField() | ||||
|         login_message = serializers.CharField() | ||||
|         navbar_message = serializers | ||||
|         login_message = serializers.CharField(allow_null=True) | ||||
|         navbar_message = serializers.CharField(allow_null=True) | ||||
|  | ||||
|     server = serializers.CharField(read_only=True) | ||||
|     id = serializers.CharField(read_only=True) | ||||
|     id = serializers.CharField(read_only=True, allow_null=True) | ||||
|     version = serializers.CharField(read_only=True) | ||||
|     instance = serializers.CharField(read_only=True) | ||||
|     apiVersion = serializers.IntegerField(read_only=True)  # noqa: N815 | ||||
| @@ -246,15 +246,13 @@ class InfoApiSerializer(serializers.Serializer): | ||||
|     email_configured = serializers.BooleanField(read_only=True) | ||||
|     debug_mode = serializers.BooleanField(read_only=True) | ||||
|     docker_mode = serializers.BooleanField(read_only=True) | ||||
|     default_locale = serializers.ChoiceField( | ||||
|         choices=settings.LOCALE_CODES, read_only=True | ||||
|     ) | ||||
|     default_locale = serializers.CharField(read_only=True) | ||||
|     customize = CustomizeSerializer(read_only=True) | ||||
|     system_health = serializers.BooleanField(read_only=True) | ||||
|     database = serializers.CharField(read_only=True) | ||||
|     platform = serializers.CharField(read_only=True) | ||||
|     installer = serializers.CharField(read_only=True) | ||||
|     target = serializers.CharField(read_only=True) | ||||
|     target = serializers.CharField(read_only=True, allow_null=True) | ||||
|     django_admin = serializers.CharField(read_only=True) | ||||
|     settings = SettingsSerializer(read_only=True, many=False) | ||||
|  | ||||
|   | ||||
| @@ -1,13 +1,16 @@ | ||||
| """InvenTree API version information.""" | ||||
|  | ||||
| # InvenTree API version | ||||
| INVENTREE_API_VERSION = 339 | ||||
| INVENTREE_API_VERSION = 340 | ||||
|  | ||||
| """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" | ||||
|  | ||||
|  | ||||
| INVENTREE_API_TEXT = """ | ||||
|  | ||||
| v340 -> 2025-04-15 : https://github.com/inventree/InvenTree/pull/9546 | ||||
|     - Add nullable to various fields to make them not required | ||||
|  | ||||
| v339 -> 2025-04-15 : https://github.com/inventree/InvenTree/pull/9283 | ||||
|     - Remove need for source in /plugins/ui/features | ||||
|  | ||||
|   | ||||
| @@ -1431,7 +1431,12 @@ class BuildLineSerializer(DataImportExportSerializerMixin, InvenTreeModelSeriali | ||||
|         pricing=False, | ||||
|     ) | ||||
|     build_detail = BuildSerializer( | ||||
|         label=_('Build'), source='build', part_detail=False, many=False, read_only=True | ||||
|         label=_('Build'), | ||||
|         source='build', | ||||
|         part_detail=False, | ||||
|         many=False, | ||||
|         read_only=True, | ||||
|         allow_null=True, | ||||
|     ) | ||||
|  | ||||
|     # Annotated (calculated) fields | ||||
|   | ||||
| @@ -812,6 +812,4 @@ class DataOutputSerializer(InvenTreeModelSerializer): | ||||
|  | ||||
|     user_detail = UserSerializer(source='user', read_only=True, many=False) | ||||
|  | ||||
|     output = InvenTreeAttachmentSerializerField( | ||||
|         required=False, allow_null=True, read_only=True | ||||
|     ) | ||||
|     output = InvenTreeAttachmentSerializerField(allow_null=True, read_only=True) | ||||
|   | ||||
| @@ -169,7 +169,7 @@ class CompanySerializer( | ||||
|         read_only=True, | ||||
|     ) | ||||
|  | ||||
|     primary_address = AddressSerializer(required=False, allow_null=True, read_only=True) | ||||
|     primary_address = AddressSerializer(allow_null=True, read_only=True) | ||||
|  | ||||
|     image = InvenTreeImageSerializerField(required=False, allow_null=True) | ||||
|  | ||||
| @@ -463,7 +463,7 @@ class SupplierPartSerializer( | ||||
|     ) | ||||
|  | ||||
|     MPN = serializers.CharField( | ||||
|         source='manufacturer_part.MPN', read_only=True, label=_('MPN') | ||||
|         source='manufacturer_part.MPN', read_only=True, allow_null=True, label=_('MPN') | ||||
|     ) | ||||
|  | ||||
|     # Date fields | ||||
|   | ||||
| @@ -65,7 +65,7 @@ class DataImporterModelSerializer(serializers.Serializer): | ||||
|  | ||||
|     serializer = serializers.CharField(read_only=True) | ||||
|     model_type = serializers.CharField(read_only=True) | ||||
|     api_url = serializers.URLField(read_only=True) | ||||
|     api_url = serializers.URLField(read_only=True, allow_null=True) | ||||
|  | ||||
|  | ||||
| class DataImporterModelList(APIView): | ||||
|   | ||||
| @@ -112,11 +112,13 @@ class AbstractOrderSerializer(DataImportExportSerializerMixin, serializers.Seria | ||||
|     import_exclude_fields = ['notes', 'duplicate'] | ||||
|  | ||||
|     # Number of line items in this order | ||||
|     line_items = serializers.IntegerField(read_only=True, label=_('Line Items')) | ||||
|     line_items = serializers.IntegerField( | ||||
|         read_only=True, allow_null=True, label=_('Line Items') | ||||
|     ) | ||||
|  | ||||
|     # Number of completed line items (this is an annotated field) | ||||
|     completed_lines = serializers.IntegerField( | ||||
|         read_only=True, label=_('Completed Lines') | ||||
|         read_only=True, allow_null=True, label=_('Completed Lines') | ||||
|     ) | ||||
|  | ||||
|     # Human-readable status text (read-only) | ||||
| @@ -156,7 +158,7 @@ class AbstractOrderSerializer(DataImportExportSerializerMixin, serializers.Seria | ||||
|     ) | ||||
|  | ||||
|     # Boolean field indicating if this order is overdue (Note: must be annotated) | ||||
|     overdue = serializers.BooleanField(required=False, read_only=True) | ||||
|     overdue = serializers.BooleanField(read_only=True, allow_null=True) | ||||
|  | ||||
|     barcode_hash = serializers.CharField(read_only=True) | ||||
|  | ||||
| @@ -609,7 +611,7 @@ class PurchaseOrderLineItemSerializer( | ||||
|  | ||||
|     received = serializers.FloatField(default=0, read_only=True) | ||||
|  | ||||
|     overdue = serializers.BooleanField(required=False, read_only=True) | ||||
|     overdue = serializers.BooleanField(read_only=True, allow_null=True) | ||||
|  | ||||
|     total_price = serializers.FloatField(read_only=True) | ||||
|  | ||||
| @@ -632,7 +634,7 @@ class PurchaseOrderLineItemSerializer( | ||||
|     ) | ||||
|  | ||||
|     destination_detail = stock.serializers.LocationBriefSerializer( | ||||
|         source='get_destination', read_only=True | ||||
|         source='get_destination', read_only=True, allow_null=True | ||||
|     ) | ||||
|  | ||||
|     purchase_price_currency = InvenTreeCurrencySerializer( | ||||
| @@ -652,14 +654,22 @@ class PurchaseOrderLineItemSerializer( | ||||
|         write_only=True, | ||||
|     ) | ||||
|  | ||||
|     sku = serializers.CharField(source='part.SKU', read_only=True, label=_('SKU')) | ||||
|     sku = serializers.CharField( | ||||
|         source='part.SKU', read_only=True, allow_null=True, label=_('SKU') | ||||
|     ) | ||||
|  | ||||
|     mpn = serializers.CharField( | ||||
|         source='part.manufacturer_part.MPN', read_only=True, label=_('MPN') | ||||
|         source='part.manufacturer_part.MPN', | ||||
|         read_only=True, | ||||
|         allow_null=True, | ||||
|         label=_('MPN'), | ||||
|     ) | ||||
|  | ||||
|     ipn = serializers.CharField( | ||||
|         source='part.part.IPN', read_only=True, label=_('Internal Part Number') | ||||
|         source='part.part.IPN', | ||||
|         read_only=True, | ||||
|         allow_null=True, | ||||
|         label=_('Internal Part Number'), | ||||
|     ) | ||||
|  | ||||
|     internal_part = serializers.PrimaryKeyRelatedField( | ||||
| @@ -1078,10 +1088,12 @@ class SalesOrderSerializer( | ||||
|         source='customer', many=False, read_only=True, allow_null=True | ||||
|     ) | ||||
|  | ||||
|     shipments_count = serializers.IntegerField(read_only=True, label=_('Shipments')) | ||||
|     shipments_count = serializers.IntegerField( | ||||
|         read_only=True, allow_null=True, label=_('Shipments') | ||||
|     ) | ||||
|  | ||||
|     completed_shipments_count = serializers.IntegerField( | ||||
|         read_only=True, label=_('Completed Shipments') | ||||
|         read_only=True, allow_null=True, label=_('Completed Shipments') | ||||
|     ) | ||||
|  | ||||
|  | ||||
| @@ -1253,7 +1265,7 @@ class SalesOrderLineItemSerializer( | ||||
|     ) | ||||
|  | ||||
|     # Annotated fields | ||||
|     overdue = serializers.BooleanField(required=False, read_only=True) | ||||
|     overdue = serializers.BooleanField(read_only=True, allow_null=True) | ||||
|     available_stock = serializers.FloatField(read_only=True) | ||||
|     available_variant_stock = serializers.FloatField(read_only=True) | ||||
|     on_order = serializers.FloatField(label=_('On Order'), read_only=True) | ||||
| @@ -1316,7 +1328,7 @@ class SalesOrderShipmentSerializer(NotesFieldMixin, InvenTreeModelSerializer): | ||||
|         return queryset | ||||
|  | ||||
|     allocated_items = serializers.IntegerField( | ||||
|         read_only=True, label=_('Allocated Items') | ||||
|         read_only=True, allow_null=True, label=_('Allocated Items') | ||||
|     ) | ||||
|  | ||||
|     order_detail = SalesOrderSerializer( | ||||
| @@ -1389,7 +1401,7 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer): | ||||
|     order = serializers.PrimaryKeyRelatedField( | ||||
|         source='line.order', many=False, read_only=True | ||||
|     ) | ||||
|     serial = serializers.CharField(source='get_serial', read_only=True) | ||||
|     serial = serializers.CharField(source='get_serial', read_only=True, allow_null=True) | ||||
|     quantity = serializers.FloatField(read_only=False) | ||||
|     location = serializers.PrimaryKeyRelatedField( | ||||
|         source='item.location', many=False, read_only=True | ||||
|   | ||||
| @@ -126,9 +126,13 @@ class CategorySerializer( | ||||
|         help_text=_('Parent part category'), | ||||
|     ) | ||||
|  | ||||
|     part_count = serializers.IntegerField(read_only=True, label=_('Parts')) | ||||
|     part_count = serializers.IntegerField( | ||||
|         read_only=True, allow_null=True, label=_('Parts') | ||||
|     ) | ||||
|  | ||||
|     subcategories = serializers.IntegerField(read_only=True, label=_('Subcategories')) | ||||
|     subcategories = serializers.IntegerField( | ||||
|         read_only=True, allow_null=True, label=_('Subcategories') | ||||
|     ) | ||||
|  | ||||
|     level = serializers.IntegerField(read_only=True) | ||||
|  | ||||
| @@ -149,7 +153,7 @@ class CategorySerializer( | ||||
|         max_length=100, | ||||
|     ) | ||||
|  | ||||
|     parent_default_location = serializers.IntegerField(read_only=True) | ||||
|     parent_default_location = serializers.IntegerField(read_only=True, allow_null=True) | ||||
|  | ||||
|  | ||||
| class CategoryTree(InvenTree.serializers.InvenTreeModelSerializer): | ||||
| @@ -335,6 +339,7 @@ class PartParameterTemplateSerializer( | ||||
|  | ||||
|     parts = serializers.IntegerField( | ||||
|         read_only=True, | ||||
|         allow_null=True, | ||||
|         label=_('Parts'), | ||||
|         help_text=_('Number of parts using this template'), | ||||
|     ) | ||||
| @@ -396,7 +401,9 @@ class PartBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer): | ||||
|         read_only=True, allow_null=True | ||||
|     ) | ||||
|  | ||||
|     image = InvenTree.serializers.InvenTreeImageSerializerField(read_only=True) | ||||
|     image = InvenTree.serializers.InvenTreeImageSerializerField( | ||||
|         read_only=True, allow_null=True | ||||
|     ) | ||||
|     thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True) | ||||
|  | ||||
|     IPN = serializers.CharField( | ||||
| @@ -924,25 +931,47 @@ class PartSerializer( | ||||
|     ) | ||||
|  | ||||
|     # Annotated fields | ||||
|     allocated_to_build_orders = serializers.FloatField(read_only=True) | ||||
|     allocated_to_sales_orders = serializers.FloatField(read_only=True) | ||||
|     building = serializers.FloatField(read_only=True, label=_('Building')) | ||||
|     in_stock = serializers.FloatField(read_only=True, label=_('In Stock')) | ||||
|     ordering = serializers.FloatField(read_only=True, label=_('On Order')) | ||||
|     required_for_build_orders = serializers.IntegerField(read_only=True) | ||||
|     required_for_sales_orders = serializers.IntegerField(read_only=True) | ||||
|     stock_item_count = serializers.IntegerField(read_only=True, label=_('Stock Items')) | ||||
|     revision_count = serializers.IntegerField(read_only=True, label=_('Revisions')) | ||||
|     suppliers = serializers.IntegerField(read_only=True, label=_('Suppliers')) | ||||
|     total_in_stock = serializers.FloatField(read_only=True, label=_('Total Stock')) | ||||
|     external_stock = serializers.FloatField(read_only=True, label=_('External Stock')) | ||||
|     allocated_to_build_orders = serializers.FloatField(read_only=True, allow_null=True) | ||||
|     allocated_to_sales_orders = serializers.FloatField(read_only=True, allow_null=True) | ||||
|     building = serializers.FloatField( | ||||
|         read_only=True, allow_null=True, label=_('Building') | ||||
|     ) | ||||
|     in_stock = serializers.FloatField( | ||||
|         read_only=True, allow_null=True, label=_('In Stock') | ||||
|     ) | ||||
|     ordering = serializers.FloatField( | ||||
|         read_only=True, allow_null=True, label=_('On Order') | ||||
|     ) | ||||
|     required_for_build_orders = serializers.IntegerField( | ||||
|         read_only=True, allow_null=True | ||||
|     ) | ||||
|     required_for_sales_orders = serializers.IntegerField( | ||||
|         read_only=True, allow_null=True | ||||
|     ) | ||||
|     stock_item_count = serializers.IntegerField( | ||||
|         read_only=True, allow_null=True, label=_('Stock Items') | ||||
|     ) | ||||
|     revision_count = serializers.IntegerField( | ||||
|         read_only=True, allow_null=True, label=_('Revisions') | ||||
|     ) | ||||
|     suppliers = serializers.IntegerField( | ||||
|         read_only=True, allow_null=True, label=_('Suppliers') | ||||
|     ) | ||||
|     total_in_stock = serializers.FloatField( | ||||
|         read_only=True, allow_null=True, label=_('Total Stock') | ||||
|     ) | ||||
|     external_stock = serializers.FloatField( | ||||
|         read_only=True, allow_null=True, label=_('External Stock') | ||||
|     ) | ||||
|     unallocated_stock = serializers.FloatField( | ||||
|         read_only=True, label=_('Unallocated Stock') | ||||
|         read_only=True, allow_null=True, label=_('Unallocated Stock') | ||||
|     ) | ||||
|     category_default_location = serializers.IntegerField( | ||||
|         read_only=True, allow_null=True | ||||
|     ) | ||||
|     variant_stock = serializers.FloatField(read_only=True, label=_('Variant Stock')) | ||||
|     variant_stock = serializers.FloatField( | ||||
|         read_only=True, allow_null=True, label=_('Variant Stock') | ||||
|     ) | ||||
|  | ||||
|     minimum_stock = serializers.FloatField( | ||||
|         required=False, label=_('Minimum Stock'), default=0 | ||||
| @@ -1657,11 +1686,17 @@ class BomItemSerializer( | ||||
|         allow_null=True, | ||||
|     ) | ||||
|  | ||||
|     on_order = serializers.FloatField(label=_('On Order'), read_only=True) | ||||
|     on_order = serializers.FloatField( | ||||
|         label=_('On Order'), read_only=True, allow_null=True | ||||
|     ) | ||||
|  | ||||
|     building = serializers.FloatField(label=_('In Production'), read_only=True) | ||||
|     building = serializers.FloatField( | ||||
|         label=_('In Production'), read_only=True, allow_null=True | ||||
|     ) | ||||
|  | ||||
|     can_build = serializers.FloatField(label=_('Can Build'), read_only=True) | ||||
|     can_build = serializers.FloatField( | ||||
|         label=_('Can Build'), read_only=True, allow_null=True | ||||
|     ) | ||||
|  | ||||
|     # Cached pricing fields | ||||
|     pricing_min = InvenTree.serializers.InvenTreeMoneySerializer( | ||||
| @@ -1684,12 +1719,14 @@ class BomItemSerializer( | ||||
|     ) | ||||
|  | ||||
|     # Annotated fields for available stock | ||||
|     available_stock = serializers.FloatField(label=_('Available Stock'), read_only=True) | ||||
|     available_stock = serializers.FloatField( | ||||
|         label=_('Available Stock'), read_only=True, allow_null=True | ||||
|     ) | ||||
|  | ||||
|     available_substitute_stock = serializers.FloatField(read_only=True) | ||||
|     available_variant_stock = serializers.FloatField(read_only=True) | ||||
|     available_substitute_stock = serializers.FloatField(read_only=True, allow_null=True) | ||||
|     available_variant_stock = serializers.FloatField(read_only=True, allow_null=True) | ||||
|  | ||||
|     external_stock = serializers.FloatField(read_only=True) | ||||
|     external_stock = serializers.FloatField(read_only=True, allow_null=True) | ||||
|  | ||||
|     @staticmethod | ||||
|     def annotate_queryset(queryset): | ||||
|   | ||||
| @@ -655,20 +655,20 @@ class StockItemSerializer( | ||||
|  | ||||
|     # Annotated fields | ||||
|     allocated = serializers.FloatField( | ||||
|         required=False, read_only=True, label=_('Allocated Quantity') | ||||
|         read_only=True, allow_null=True, label=_('Allocated Quantity') | ||||
|     ) | ||||
|     expired = serializers.BooleanField( | ||||
|         required=False, read_only=True, label=_('Expired') | ||||
|         read_only=True, allow_null=True, label=_('Expired') | ||||
|     ) | ||||
|     installed_items = serializers.IntegerField( | ||||
|         read_only=True, required=False, label=_('Installed Items') | ||||
|         read_only=True, allow_null=True, label=_('Installed Items') | ||||
|     ) | ||||
|     child_items = serializers.IntegerField( | ||||
|         read_only=True, required=False, label=_('Child Items') | ||||
|         read_only=True, allow_null=True, label=_('Child Items') | ||||
|     ) | ||||
|     stale = serializers.BooleanField(required=False, read_only=True, label=_('Stale')) | ||||
|     stale = serializers.BooleanField(read_only=True, allow_null=True, label=_('Stale')) | ||||
|     tracking_items = serializers.IntegerField( | ||||
|         read_only=True, required=False, label=_('Tracking Items') | ||||
|         read_only=True, allow_null=True, label=_('Tracking Items') | ||||
|     ) | ||||
|  | ||||
|     purchase_price = InvenTree.serializers.InvenTreeMoneySerializer( | ||||
| @@ -1161,7 +1161,7 @@ class StockLocationTypeSerializer(InvenTree.serializers.InvenTreeModelSerializer | ||||
|  | ||||
|         read_only_fields = ['location_count'] | ||||
|  | ||||
|     location_count = serializers.IntegerField(read_only=True) | ||||
|     location_count = serializers.IntegerField(read_only=True, allow_null=True) | ||||
|  | ||||
|     @staticmethod | ||||
|     def annotate_queryset(queryset): | ||||
| @@ -1273,7 +1273,7 @@ class LocationSerializer( | ||||
|  | ||||
|     # Detail for location type | ||||
|     location_type_detail = StockLocationTypeSerializer( | ||||
|         source='location_type', read_only=True, many=False | ||||
|         source='location_type', read_only=True, allow_null=True, many=False | ||||
|     ) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -283,9 +283,13 @@ class GroupSerializer(InvenTreeModelSerializer): | ||||
|         """Return a list of permissions associated with the group.""" | ||||
|         return generate_permission_dict(group.permissions.all()) | ||||
|  | ||||
|     roles = RuleSetSerializer(source='rule_sets', many=True, read_only=True) | ||||
|     roles = RuleSetSerializer( | ||||
|         source='rule_sets', many=True, read_only=True, allow_null=True | ||||
|     ) | ||||
|  | ||||
|     users = UserSerializer(source='user_set', many=True, read_only=True) | ||||
|     users = UserSerializer( | ||||
|         source='user_set', many=True, read_only=True, allow_null=True | ||||
|     ) | ||||
|  | ||||
|  | ||||
| class ExtendedUserSerializer(UserSerializer): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user