From e09564d902cbd0ec9bdfe4585bcc65981f80cd79 Mon Sep 17 00:00:00 2001 From: "utkarsh.arya@zomato.com" Date: Sat, 15 Nov 2025 23:01:27 +0000 Subject: [PATCH] Fix JSONField readonly admin display to use valid JSON Previously, readonly JSONField values in the admin were shown as Python dicts, not valid JSON. Now, display_for_field uses json.dumps with the field's encoder to ensure correct JSON output. Fixes # (if applicable) --- django/contrib/admin/utils.py | 3 ++ tests/admin_utils/tests.py | 53 +++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py index e4ddc8f017..3e29474b68 100644 --- a/django/contrib/admin/utils.py +++ b/django/contrib/admin/utils.py @@ -1,5 +1,6 @@ import datetime import decimal +import json from collections import defaultdict from django.core.exceptions import FieldDoesNotExist @@ -398,6 +399,8 @@ def display_for_field(value, field, empty_value_display): return formats.number_format(value) elif isinstance(field, models.FileField) and value: return format_html('{}', value.url, value) + elif isinstance(field, models.JSONField): + return json.dumps(value, ensure_ascii=False, cls=field.encoder) else: return display_for_value(value, empty_value_display) diff --git a/tests/admin_utils/tests.py b/tests/admin_utils/tests.py index 463ba9556d..0b43b45bce 100644 --- a/tests/admin_utils/tests.py +++ b/tests/admin_utils/tests.py @@ -197,6 +197,59 @@ class UtilsTests(SimpleTestCase): display_value = display_for_field(12345, models.IntegerField(), self.empty_value) self.assertEqual(display_value, '12,345') + def test_json_display_for_field(self): + """ + JSONField values are displayed as valid JSON. + """ + tests = [ + ({'foo': 'bar'}, '{"foo": "bar"}'), + (['foo', 'bar'], '["foo", "bar"]'), + ('foo', '"foo"'), + ({'a': [1, 2]}, '{"a": [1, 2]}'), + (None, self.empty_value), + (True, 'true'), + (False, 'false'), + (123, '123'), + (45.67, '45.67'), + ] + for value, expected_display in tests: + with self.subTest(value=value): + display_value = display_for_field(value, models.JSONField(), self.empty_value) + self.assertEqual(display_value, expected_display) + + def test_json_display_for_field_custom_encoder(self): + """ + JSONField values are displayed as valid JSON with custom encoder. + """ + import json + + class CustomEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, Decimal): + return str(obj) + return super().default(obj) + + field = models.JSONField(encoder=CustomEncoder) + value = {'price': Decimal('10.50')} + display_value = display_for_field(value, field, self.empty_value) + # The custom encoder should convert Decimal to string + self.assertEqual(display_value, '{"price": "10.50"}') + + def test_json_display_for_field_unicode(self): + """ + JSONField values with Unicode characters are properly displayed. + """ + tests = [ + ({'name': 'José'}, '{"name": "José"}'), + ({'emoji': '😀'}, '{"emoji": "😀"}'), + ({'chinese': '你好'}, '{"chinese": "你好"}'), + ({'mixed': 'Hello 世界 🌍'}, '{"mixed": "Hello 世界 🌍"}'), + ] + for value, expected_display in tests: + with self.subTest(value=value): + display_value = display_for_field(value, models.JSONField(), self.empty_value) + self.assertEqual(display_value, expected_display) + def test_list_display_for_value(self): display_value = display_for_value([1, 2, 3], self.empty_value) self.assertEqual(display_value, '1, 2, 3')