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.
This commit is contained in:
Jason Novinger 2026-09-15 09:13:27 -05:00
parent 779ce96dc7
commit 611ffd25ec
2 changed files with 27 additions and 8 deletions

View File

@ -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:

View File

@ -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):