Merge branch 'master' into preview-panel

This commit is contained in:
Oliver 2026-07-05 00:32:17 +10:00 committed by GitHub
commit cf88f84247
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 172 additions and 5 deletions

View File

@ -1,11 +1,14 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 514 INVENTREE_API_VERSION = 515
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ INVENTREE_API_TEXT = """
v515 -> 2026-07-03 : https://github.com/inventree/InvenTree/pull/12298
- Change the file fields definition to binary (from uri) in the upload requests
v514 -> 2026-07-02 : https://github.com/inventree/InvenTree/pull/12294 v514 -> 2026-07-02 : https://github.com/inventree/InvenTree/pull/12294
- Adds "duplicate" field to the BuildOrder, Company, ManufacturerPart, SupplierPart and SalesOrderShipment API endpoints - Adds "duplicate" field to the BuildOrder, Company, ManufacturerPart, SupplierPart and SalesOrderShipment API endpoints
- Order duplication options: renames "order_id" field to "original", which now performs primary-key validation - Order duplication options: renames "order_id" field to "original", which now performs primary-key validation

View File

@ -1,6 +1,7 @@
"""Custom fields used in InvenTree.""" """Custom fields used in InvenTree."""
import sys import sys
import uuid
from decimal import Decimal from decimal import Decimal
from django import forms from django import forms
@ -59,6 +60,34 @@ class InvenTreeURLField(models.URLField):
super().__init__(**kwargs) super().__init__(**kwargs)
class InvenTreeUUIDField(models.UUIDField):
"""UUIDField which is always stored as a char(32) column on MySQL / MariaDB.
On MariaDB 10.7+, Django maps UUIDField to the native 'uuid' column type,
and writes 36-character (hyphenated) values to match.
However, databases migrated under older Django / MariaDB versions retain
their original char(32) columns, into which a 36-character value does not fit.
To ensure consistent behavior across all supported database backends,
we force the legacy char(32) storage format on MySQL / MariaDB.
Ref: https://docs.djangoproject.com/en/5.2/releases/5.0/#migrating-existing-uuidfield-on-mariadb-10-7
"""
def db_type(self, connection):
"""Force a char(32) column type on MySQL / MariaDB backends."""
if connection.vendor == 'mysql':
return 'char(32)'
return super().db_type(connection)
def get_db_prep_value(self, value, connection, prepared=False):
"""Store values in 32-character hex format on MySQL / MariaDB backends."""
value = super().get_db_prep_value(value, connection, prepared)
if connection.vendor == 'mysql' and isinstance(value, uuid.UUID):
value = value.hex
return value
def money_kwargs(**kwargs): def money_kwargs(**kwargs):
"""Returns the database settings for MoneyFields.""" """Returns the database settings for MoneyFields."""
from common.currency import currency_code_mappings from common.currency import currency_code_mappings

View File

@ -16,6 +16,7 @@ from drf_spectacular.utils import (
extend_schema, extend_schema,
extend_schema_view, extend_schema_view,
) )
from rest_framework import serializers
from rest_framework.pagination import LimitOffsetPagination from rest_framework.pagination import LimitOffsetPagination
from InvenTree.permissions import OASTokenMixin from InvenTree.permissions import OASTokenMixin
@ -46,6 +47,20 @@ class ExtendedOAuth2Scheme(DjangoOAuthToolkitScheme):
class ExtendedAutoSchema(AutoSchema): class ExtendedAutoSchema(AutoSchema):
"""Extend drf-spectacular to allow customizing the schema to match the actual API behavior.""" """Extend drf-spectacular to allow customizing the schema to match the actual API behavior."""
def _map_serializer_field(self, field, direction, *args, **kwargs):
"""Custom field mapping overrides, falling back to default behavior."""
schema = super()._map_serializer_field(field, direction, *args, **kwargs)
direction_value = getattr(direction, 'value', direction)
# File and image fields in request schemas must be represented as binary
# payloads. In response schemas they are still rendered as URLs.
if direction_value == 'request' and isinstance(field, serializers.FileField):
schema['type'] = 'string'
schema['format'] = 'binary'
return schema
def is_bulk_action(self, ref: str) -> bool: def is_bulk_action(self, ref: str) -> bool:
"""Check the class of the current view for the bulk mixins.""" """Check the class of the current view for the bulk mixins."""
return ref in [c.__name__ for c in type(self.view).__mro__] return ref in [c.__name__ for c in type(self.view).__mro__]

View File

@ -22,6 +22,7 @@ from djmoney.contrib.exchange.exceptions import MissingRate
from djmoney.contrib.exchange.models import Rate, convert_money from djmoney.contrib.exchange.models import Rate, convert_money
from djmoney.money import Money from djmoney.money import Money
from maintenance_mode.core import get_maintenance_mode, set_maintenance_mode from maintenance_mode.core import get_maintenance_mode, set_maintenance_mode
from rest_framework import serializers
from sesame.utils import get_user from sesame.utils import get_user
from stdimage.models import StdImageFieldFile from stdimage.models import StdImageFieldFile
@ -1817,6 +1818,35 @@ class SchemaPostprocessingTest(TestCase):
# required key removed when empty # required key removed when empty
self.assertNotIn('required', schemas_out.get('SalesOrderShipment')) self.assertNotIn('required', schemas_out.get('SalesOrderShipment'))
def test_file_field_request_schema_binary(self):
"""Verify only request file fields are exposed as binary."""
auto_schema = object.__new__(schema.ExtendedAutoSchema)
mapped_schemas = [
{'type': 'string', 'format': 'uri', 'nullable': True},
{'type': 'string', 'format': 'uri'},
{'type': 'string', 'format': 'uri', 'nullable': True},
]
with mock.patch(
'drf_spectacular.openapi.AutoSchema._map_serializer_field',
side_effect=mapped_schemas,
):
file_request = auto_schema._map_serializer_field(
serializers.FileField(allow_null=True), 'request'
)
url_request = auto_schema._map_serializer_field(
serializers.URLField(), 'request'
)
file_response = auto_schema._map_serializer_field(
serializers.FileField(allow_null=True), 'response'
)
self.assertEqual(file_request['format'], 'binary')
self.assertTrue(file_request['nullable'])
self.assertEqual(url_request['format'], 'uri')
self.assertEqual(file_response['format'], 'uri')
class URLCompatibilityTest(InvenTreeTestCase): class URLCompatibilityTest(InvenTreeTestCase):
"""Unit test for legacy URL compatibility.""" """Unit test for legacy URL compatibility."""

View File

@ -0,0 +1,59 @@
"""Migration to store UUID primary keys as char(32) on MySQL / MariaDB.
On MariaDB 10.7+, Django 5.x creates UUIDField columns with the native 'uuid'
type and writes 36-character (hyphenated) values. Databases migrated under older
Django / MariaDB versions retain char(32) columns, into which the new values
do not fit.
See: https://github.com/inventree/InvenTree/issues/12270
"""
import uuid
from django.db import migrations
import InvenTree.fields
class Migration(migrations.Migration):
dependencies = [('common', '0045_projectcode_active')]
operations = [
migrations.AlterField(
model_name='emailmessage',
name='global_id',
field=InvenTree.fields.InvenTreeUUIDField(
default=uuid.uuid4,
editable=False,
help_text='Unique identifier for this message',
primary_key=True,
serialize=False,
unique=True,
verbose_name='Global ID',
),
),
migrations.AlterField(
model_name='emailthread',
name='global_id',
field=InvenTree.fields.InvenTreeUUIDField(
default=uuid.uuid4,
editable=False,
help_text='Unique identifier for this thread',
primary_key=True,
serialize=False,
verbose_name='Global ID',
),
),
migrations.AlterField(
model_name='webhookmessage',
name='message_id',
field=InvenTree.fields.InvenTreeUUIDField(
default=uuid.uuid4,
editable=False,
help_text='Unique identifier for this message',
primary_key=True,
serialize=False,
verbose_name='Message ID',
),
),
]

View File

@ -1584,7 +1584,7 @@ class WebhookMessage(models.Model):
worked_on: Was the work on this message finished? worked_on: Was the work on this message finished?
""" """
message_id = models.UUIDField( message_id = InvenTree.fields.InvenTreeUUIDField(
verbose_name=_('Message ID'), verbose_name=_('Message ID'),
help_text=_('Unique identifier for this message'), help_text=_('Unique identifier for this message'),
primary_key=True, primary_key=True,
@ -3266,7 +3266,7 @@ class EmailMessage(models.Model):
TRACK_READ = 'track_read', _('Track Read') TRACK_READ = 'track_read', _('Track Read')
TRACK_CLICK = 'track_click', _('Track Click') TRACK_CLICK = 'track_click', _('Track Click')
global_id = models.UUIDField( global_id = InvenTree.fields.InvenTreeUUIDField(
verbose_name=_('Global ID'), verbose_name=_('Global ID'),
help_text=_('Unique identifier for this message'), help_text=_('Unique identifier for this message'),
primary_key=True, primary_key=True,
@ -3374,7 +3374,7 @@ class EmailThread(InvenTree.models.InvenTreeMetadataModel):
blank=True, blank=True,
help_text=_('Unique key for this thread (used to identify the thread)'), help_text=_('Unique key for this thread (used to identify the thread)'),
) )
global_id = models.UUIDField( global_id = InvenTree.fields.InvenTreeUUIDField(
verbose_name=_('Global ID'), verbose_name=_('Global ID'),
help_text=_('Unique identifier for this thread'), help_text=_('Unique identifier for this thread'),
primary_key=True, primary_key=True,

View File

@ -0,0 +1,28 @@
"""Migration to store MachineConfig UUID primary keys as char(32) on MySQL / MariaDB.
On MariaDB 10.7+, Django 5.x writes 36-character (hyphenated) UUID values,
but databases migrated under older Django / MariaDB versions retain char(32)
columns, causing machine creation to fail with "Data too long for column 'id'".
See: https://github.com/inventree/InvenTree/issues/12270
"""
import uuid
from django.db import migrations
import InvenTree.fields
class Migration(migrations.Migration):
dependencies = [('machine', '0001_initial')]
operations = [
migrations.AlterField(
model_name='machineconfig',
name='id',
field=InvenTree.fields.InvenTreeUUIDField(
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
),
),
]

View File

@ -10,6 +10,7 @@ from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import common.models import common.models
import InvenTree.fields
from machine import registry from machine import registry
from machine.machine_type import BaseMachineType from machine.machine_type import BaseMachineType
@ -17,7 +18,9 @@ from machine.machine_type import BaseMachineType
class MachineConfig(models.Model): class MachineConfig(models.Model):
"""A Machine objects represents a physical machine.""" """A Machine objects represents a physical machine."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) id = InvenTree.fields.InvenTreeUUIDField(
primary_key=True, default=uuid.uuid4, editable=False
)
name = models.CharField( name = models.CharField(
unique=True, unique=True,