From 611ffd25ec91161a5df2ea457f2b6c580c1ad36c Mon Sep 17 00:00:00 2001 From: Jason Novinger Date: Tue, 15 Sep 2026 09:13:27 -0500 Subject: [PATCH] Omit the length bounds for a UUID-format property UUIDField subclasses CharField, so it passed the guard and received min_length and max_length. Those install length validators which call len() on the cleaned value, and UUIDField cleans to a uuid.UUID, so saving a module type against a profile declaring {"type": "string", "format": "uuid", "maxLength": 36} raised an unhandled TypeError rather than a form error. The test covering the CharField-derived formats asserted the bound attributes without cleaning a value, which is why this stayed hidden. It now cleans one, and UUID gets its own test. --- netbox/utilities/jsonschema.py | 7 +++--- netbox/utilities/tests/test_jsonschema.py | 28 +++++++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/netbox/utilities/jsonschema.py b/netbox/utilities/jsonschema.py index b6ede7632..3eb751925 100644 --- a/netbox/utilities/jsonschema.py +++ b/netbox/utilities/jsonschema.py @@ -112,9 +112,10 @@ class JSONSchemaProperty: # String validation if self.type == PropertyTypeEnum.STRING.value: - # it's safe to check against CharField here because the other - # CharField-derived fields are ruled out by the "is a string check" above - if issubclass(field_class, forms.CharField): + # Checking against CharField is safe because the other CharField-derived fields are + # ruled out by the "is a string" check above. UUIDField is the exception: it cleans to + # a uuid.UUID, which the length validators can't call len() on. + if issubclass(field_class, forms.CharField) and not issubclass(field_class, forms.UUIDField): if self.minLength is not None: field_kwargs['min_length'] = self.minLength if self.maxLength is not None: diff --git a/netbox/utilities/tests/test_jsonschema.py b/netbox/utilities/tests/test_jsonschema.py index e866df9f9..642838690 100644 --- a/netbox/utilities/tests/test_jsonschema.py +++ b/netbox/utilities/tests/test_jsonschema.py @@ -1,3 +1,5 @@ +from uuid import UUID + from django import forms from django.contrib.postgres.forms import SimpleArrayField from django.core.exceptions import ValidationError @@ -217,11 +219,10 @@ class JSONSchemaPropertyTestCase(TestCase): self.assertIsInstance(field.validators[0], RegexValidator) def test_charfield_derived_format_retains_length_bounds(self): - """EmailField, URLField and UUIDField subclass CharField, so they keep their bounds.""" - for string_format, expected_class in ( - ('email', forms.EmailField), - ('uri', forms.URLField), - ('uuid', forms.UUIDField), + """EmailField and URLField clean to a string, so the length bounds apply to them.""" + for string_format, expected_class, value in ( + ('email', forms.EmailField, 'user@example.com'), + ('uri', forms.URLField, 'https://example.com/x'), ): with self.subTest(format=string_format): prop = JSONSchemaProperty( @@ -233,6 +234,23 @@ class JSONSchemaPropertyTestCase(TestCase): self.assertIsInstance(field, expected_class) self.assertEqual(field.min_length, 5) self.assertEqual(field.max_length, 40) + self.assertEqual(field.clean(value), value) + + def test_uuid_format_omits_length_bounds(self): + """UUIDField subclasses CharField but cleans to a uuid.UUID, which has no length. + + CharField.__init__() installs a MinLengthValidator and MaxLengthValidator for the bounds, + and those call len() on the cleaned value, so a UUID raises TypeError at clean time. + """ + value = '12345678-1234-5678-1234-567812345678' + prop = JSONSchemaProperty(type='string', title='Serial', format='uuid', minLength=5, maxLength=40) + + field = prop.to_form_field('serial') + + self.assertIsInstance(field, forms.UUIDField) + self.assertIsNone(field.min_length) + self.assertIsNone(field.max_length) + self.assertEqual(field.clean(value), UUID(value)) class JSONSchemaPropertyDescriptionSanitizationTestCase(TestCase):