Skip to content

Commit b256c46

Browse files
authored
Render JSON fields with proper indentation in browsable API forms. (encode#6243)
* Fix JSONBoundField usage on nested serializers (encode#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.
1 parent ff625ec commit b256c46

File tree

3 files changed

+33
-2
lines changed

3 files changed

+33
-2
lines changed

rest_framework/fields.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1764,6 +1764,9 @@ class JSONField(Field):
17641764
'invalid': _('Value must be valid JSON.')
17651765
}
17661766

1767+
# Workaround for isinstance calls when importing the field isn't possible
1768+
_is_jsonfield = True
1769+
17671770
def __init__(self, *args, **kwargs):
17681771
self.binary = kwargs.pop('binary', False)
17691772
self.encoder = kwargs.pop('encoder', None)

rest_framework/utils/serializer_helpers.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,12 @@ def as_form_field(self):
8787
# value will be a JSONString, rather than a JSON primitive.
8888
if not getattr(value, 'is_json_string', False):
8989
try:
90-
value = json.dumps(self.value, sort_keys=True, indent=4)
90+
value = json.dumps(
91+
self.value,
92+
sort_keys=True,
93+
indent=4,
94+
separators=(',', ': '),
95+
)
9196
except (TypeError, ValueError):
9297
pass
9398
return self.__class__(self._field, value, self.errors, self._prefix)
@@ -115,6 +120,8 @@ def __getitem__(self, key):
115120
error = self.errors.get(key) if isinstance(self.errors, dict) else None
116121
if hasattr(field, 'fields'):
117122
return NestedBoundField(field, value, error, prefix=self.name + '.')
123+
elif getattr(field, '_is_jsonfield', False):
124+
return JSONBoundField(field, value, error, prefix=self.name + '.')
118125
return BoundField(field, value, error, prefix=self.name + '.')
119126

120127
def as_form_field(self):

tests/test_bound_fields.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ class ExampleSerializer(serializers.Serializer):
9191
assert rendered_packed == expected_packed
9292

9393

94+
class CustomJSONField(serializers.JSONField):
95+
pass
96+
97+
9498
class TestNestedBoundField:
9599
def test_nested_empty_bound_field(self):
96100
class Nested(serializers.Serializer):
@@ -117,14 +121,31 @@ def test_as_form_fields(self):
117121
class Nested(serializers.Serializer):
118122
bool_field = serializers.BooleanField()
119123
null_field = serializers.IntegerField(allow_null=True)
124+
json_field = serializers.JSONField()
125+
custom_json_field = CustomJSONField()
120126

121127
class ExampleSerializer(serializers.Serializer):
122128
nested = Nested()
123129

124-
serializer = ExampleSerializer(data={'nested': {'bool_field': False, 'null_field': None}})
130+
serializer = ExampleSerializer(
131+
data={'nested': {
132+
'bool_field': False, 'null_field': None,
133+
'json_field': {'bool_item': True, 'number': 1, 'text_item': 'text'},
134+
'custom_json_field': {'bool_item': True, 'number': 1, 'text_item': 'text'},
135+
}})
125136
assert serializer.is_valid()
126137
assert serializer['nested']['bool_field'].as_form_field().value == ''
127138
assert serializer['nested']['null_field'].as_form_field().value == ''
139+
assert serializer['nested']['json_field'].as_form_field().value == '''{
140+
"bool_item": true,
141+
"number": 1,
142+
"text_item": "text"
143+
}'''
144+
assert serializer['nested']['custom_json_field'].as_form_field().value == '''{
145+
"bool_item": true,
146+
"number": 1,
147+
"text_item": "text"
148+
}'''
128149

129150
def test_rendering_nested_fields_with_none_value(self):
130151
from rest_framework.renderers import HTMLFormRenderer

0 commit comments

Comments
 (0)