Merge branch 'master' into preview-panel
This commit is contained in:
commit
cf88f84247
|
|
@ -1,11 +1,14 @@
|
|||
"""InvenTree API version information."""
|
||||
|
||||
# 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."""
|
||||
|
||||
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
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""Custom fields used in InvenTree."""
|
||||
|
||||
import sys
|
||||
import uuid
|
||||
from decimal import Decimal
|
||||
|
||||
from django import forms
|
||||
|
|
@ -59,6 +60,34 @@ class InvenTreeURLField(models.URLField):
|
|||
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):
|
||||
"""Returns the database settings for MoneyFields."""
|
||||
from common.currency import currency_code_mappings
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from drf_spectacular.utils import (
|
|||
extend_schema,
|
||||
extend_schema_view,
|
||||
)
|
||||
from rest_framework import serializers
|
||||
from rest_framework.pagination import LimitOffsetPagination
|
||||
|
||||
from InvenTree.permissions import OASTokenMixin
|
||||
|
|
@ -46,6 +47,20 @@ class ExtendedOAuth2Scheme(DjangoOAuthToolkitScheme):
|
|||
class ExtendedAutoSchema(AutoSchema):
|
||||
"""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:
|
||||
"""Check the class of the current view for the bulk mixins."""
|
||||
return ref in [c.__name__ for c in type(self.view).__mro__]
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from djmoney.contrib.exchange.exceptions import MissingRate
|
|||
from djmoney.contrib.exchange.models import Rate, convert_money
|
||||
from djmoney.money import Money
|
||||
from maintenance_mode.core import get_maintenance_mode, set_maintenance_mode
|
||||
from rest_framework import serializers
|
||||
from sesame.utils import get_user
|
||||
from stdimage.models import StdImageFieldFile
|
||||
|
||||
|
|
@ -1817,6 +1818,35 @@ class SchemaPostprocessingTest(TestCase):
|
|||
# required key removed when empty
|
||||
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):
|
||||
"""Unit test for legacy URL compatibility."""
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
@ -1584,7 +1584,7 @@ class WebhookMessage(models.Model):
|
|||
worked_on: Was the work on this message finished?
|
||||
"""
|
||||
|
||||
message_id = models.UUIDField(
|
||||
message_id = InvenTree.fields.InvenTreeUUIDField(
|
||||
verbose_name=_('Message ID'),
|
||||
help_text=_('Unique identifier for this message'),
|
||||
primary_key=True,
|
||||
|
|
@ -3266,7 +3266,7 @@ class EmailMessage(models.Model):
|
|||
TRACK_READ = 'track_read', _('Track Read')
|
||||
TRACK_CLICK = 'track_click', _('Track Click')
|
||||
|
||||
global_id = models.UUIDField(
|
||||
global_id = InvenTree.fields.InvenTreeUUIDField(
|
||||
verbose_name=_('Global ID'),
|
||||
help_text=_('Unique identifier for this message'),
|
||||
primary_key=True,
|
||||
|
|
@ -3374,7 +3374,7 @@ class EmailThread(InvenTree.models.InvenTreeMetadataModel):
|
|||
blank=True,
|
||||
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'),
|
||||
help_text=_('Unique identifier for this thread'),
|
||||
primary_key=True,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
@ -10,6 +10,7 @@ from django.utils.safestring import mark_safe
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import common.models
|
||||
import InvenTree.fields
|
||||
from machine import registry
|
||||
from machine.machine_type import BaseMachineType
|
||||
|
||||
|
|
@ -17,7 +18,9 @@ from machine.machine_type import BaseMachineType
|
|||
class MachineConfig(models.Model):
|
||||
"""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(
|
||||
unique=True,
|
||||
|
|
|
|||
Loading…
Reference in New Issue