Render JSON fields with proper indentation in browsable API forms. (#6243)

* Fix JSONBoundField usage on nested serializers (#6211)

* Unify JSONBoundField as_form_field output between py2 and py3

When using json.dumps with indenting, in python2 the default formatting
prints whitespace after commas (,) and python3 does not. This can be
unified with the separators keyword argument.
This commit is contained in:
Alex Hedlund 2021-03-15 12:44:03 +02:00 committed by GitHub
parent ff625ecff5
commit b256c46cb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 33 additions and 2 deletions

View File

@ -1764,6 +1764,9 @@ class JSONField(Field):
'invalid': _('Value must be valid JSON.') 'invalid': _('Value must be valid JSON.')
} }
# Workaround for isinstance calls when importing the field isn't possible
_is_jsonfield = True
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.binary = kwargs.pop('binary', False) self.binary = kwargs.pop('binary', False)
self.encoder = kwargs.pop('encoder', None) self.encoder = kwargs.pop('encoder', None)

View File

@ -87,7 +87,12 @@ class JSONBoundField(BoundField):
# value will be a JSONString, rather than a JSON primitive. # value will be a JSONString, rather than a JSON primitive.
if not getattr(value, 'is_json_string', False): if not getattr(value, 'is_json_string', False):
try: try:
value = json.dumps(self.value, sort_keys=True, indent=4) value = json.dumps(
self.value,
sort_keys=True,
indent=4,
separators=(',', ': '),
)
except (TypeError, ValueError): except (TypeError, ValueError):
pass pass
return self.__class__(self._field, value, self.errors, self._prefix) return self.__class__(self._field, value, self.errors, self._prefix)
@ -115,6 +120,8 @@ class NestedBoundField(BoundField):
error = self.errors.get(key) if isinstance(self.errors, dict) else None error = self.errors.get(key) if isinstance(self.errors, dict) else None
if hasattr(field, 'fields'): if hasattr(field, 'fields'):
return NestedBoundField(field, value, error, prefix=self.name + '.') return NestedBoundField(field, value, error, prefix=self.name + '.')
elif getattr(field, '_is_jsonfield', False):
return JSONBoundField(field, value, error, prefix=self.name + '.')
return BoundField(field, value, error, prefix=self.name + '.') return BoundField(field, value, error, prefix=self.name + '.')
def as_form_field(self): def as_form_field(self):

View File

@ -91,6 +91,10 @@ class TestSimpleBoundField:
assert rendered_packed == expected_packed assert rendered_packed == expected_packed
class CustomJSONField(serializers.JSONField):
pass
class TestNestedBoundField: class TestNestedBoundField:
def test_nested_empty_bound_field(self): def test_nested_empty_bound_field(self):
class Nested(serializers.Serializer): class Nested(serializers.Serializer):
@ -117,14 +121,31 @@ class TestNestedBoundField:
class Nested(serializers.Serializer): class Nested(serializers.Serializer):
bool_field = serializers.BooleanField() bool_field = serializers.BooleanField()
null_field = serializers.IntegerField(allow_null=True) null_field = serializers.IntegerField(allow_null=True)
json_field = serializers.JSONField()
custom_json_field = CustomJSONField()
class ExampleSerializer(serializers.Serializer): class ExampleSerializer(serializers.Serializer):
nested = Nested() nested = Nested()
serializer = ExampleSerializer(data={'nested': {'bool_field': False, 'null_field': None}}) serializer = ExampleSerializer(
data={'nested': {
'bool_field': False, 'null_field': None,
'json_field': {'bool_item': True, 'number': 1, 'text_item': 'text'},
'custom_json_field': {'bool_item': True, 'number': 1, 'text_item': 'text'},
}})
assert serializer.is_valid() assert serializer.is_valid()
assert serializer['nested']['bool_field'].as_form_field().value == '' assert serializer['nested']['bool_field'].as_form_field().value == ''
assert serializer['nested']['null_field'].as_form_field().value == '' assert serializer['nested']['null_field'].as_form_field().value == ''
assert serializer['nested']['json_field'].as_form_field().value == '''{
"bool_item": true,
"number": 1,
"text_item": "text"
}'''
assert serializer['nested']['custom_json_field'].as_form_field().value == '''{
"bool_item": true,
"number": 1,
"text_item": "text"
}'''
def test_rendering_nested_fields_with_none_value(self): def test_rendering_nested_fields_with_none_value(self):
from rest_framework.renderers import HTMLFormRenderer from rest_framework.renderers import HTMLFormRenderer