mirror of
https://github.com/netbox-community/netbox.git
synced 2025-09-06 14:23:36 -06:00
Fixes #20221: JSON CustomField does not coerce {}
to null
This fix actually fixes this for all valid JSON values that evaluate to `False` in Python when loaded and cast to bool: `bool(json.loads(<val>))`. - `{}` - `[]` - `0` - `False` This does not change the behavior of `()` or `""` which are both explicitly cited as "empty" values on `JSONField`.
This commit is contained in:
parent
47e4947ca0
commit
e864520821
@ -538,7 +538,7 @@ class CustomField(CloningMixin, ExportTemplatesMixin, ChangeLoggedModel):
|
|||||||
|
|
||||||
# JSON
|
# JSON
|
||||||
elif self.type == CustomFieldTypeChoices.TYPE_JSON:
|
elif self.type == CustomFieldTypeChoices.TYPE_JSON:
|
||||||
field = JSONField(required=required, initial=json.dumps(initial) if initial else None)
|
field = JSONField(required=required, initial=json.dumps(initial) if initial is not None else None)
|
||||||
|
|
||||||
# Object
|
# Object
|
||||||
elif self.type == CustomFieldTypeChoices.TYPE_OBJECT:
|
elif self.type == CustomFieldTypeChoices.TYPE_OBJECT:
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.test import tag
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
@ -269,6 +271,60 @@ class CustomFieldTest(TestCase):
|
|||||||
instance.refresh_from_db()
|
instance.refresh_from_db()
|
||||||
self.assertIsNone(instance.custom_field_data.get(cf.name))
|
self.assertIsNone(instance.custom_field_data.get(cf.name))
|
||||||
|
|
||||||
|
@tag('regression')
|
||||||
|
def test_json_field_falsy_defaults(self):
|
||||||
|
"""Test that falsy JSON default values are properly handled"""
|
||||||
|
falsy_test_cases = [
|
||||||
|
({}, 'empty_dict'),
|
||||||
|
([], 'empty_array'),
|
||||||
|
(0, 'zero'),
|
||||||
|
(False, 'false_bool'),
|
||||||
|
("", 'empty_string'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for default, suffix in falsy_test_cases:
|
||||||
|
with self.subTest(default=default, suffix=suffix):
|
||||||
|
cf = CustomField.objects.create(
|
||||||
|
name=f'json_falsy_{suffix}',
|
||||||
|
type=CustomFieldTypeChoices.TYPE_JSON,
|
||||||
|
default=default,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
cf.object_types.set([self.object_type])
|
||||||
|
|
||||||
|
instance = Site.objects.create(name=f'Test Site {suffix}', slug=f'test-site-{suffix}')
|
||||||
|
|
||||||
|
self.assertIsNotNone(instance.custom_field_data)
|
||||||
|
self.assertIn(cf.name, instance.custom_field_data)
|
||||||
|
|
||||||
|
instance.refresh_from_db()
|
||||||
|
stored = instance.custom_field_data[cf.name]
|
||||||
|
self.assertEqual(stored, default)
|
||||||
|
|
||||||
|
@tag('regression')
|
||||||
|
def test_json_field_falsy_to_form_field(self):
|
||||||
|
"""Test form field generation preserves falsy defaults"""
|
||||||
|
falsy_test_cases = (
|
||||||
|
({}, json.dumps({}), 'empty_dict'),
|
||||||
|
([], json.dumps([]), 'empty_array'),
|
||||||
|
(0, json.dumps(0), 'zero'),
|
||||||
|
(False, json.dumps(False), 'false_bool'),
|
||||||
|
("", '""', 'empty_string'),
|
||||||
|
)
|
||||||
|
|
||||||
|
for default, expected, suffix in falsy_test_cases:
|
||||||
|
with self.subTest(default=default, expected=expected, suffix=suffix):
|
||||||
|
cf = CustomField.objects.create(
|
||||||
|
name=f'json_falsy_{suffix}',
|
||||||
|
type=CustomFieldTypeChoices.TYPE_JSON,
|
||||||
|
default=default,
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
cf.object_types.set([self.object_type])
|
||||||
|
|
||||||
|
form_field = cf.to_form_field(set_initial=True)
|
||||||
|
self.assertEqual(form_field.initial, expected)
|
||||||
|
|
||||||
def test_select_field(self):
|
def test_select_field(self):
|
||||||
CHOICES = (
|
CHOICES = (
|
||||||
('a', 'Option A'),
|
('a', 'Option A'),
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
{{ value|isodatetime }}
|
{{ value|isodatetime }}
|
||||||
{% elif customfield.type == 'url' and value %}
|
{% elif customfield.type == 'url' and value %}
|
||||||
<a href="{{ value }}">{{ value|truncatechars:70 }}</a>
|
<a href="{{ value }}">{{ value|truncatechars:70 }}</a>
|
||||||
{% elif customfield.type == 'json' and value %}
|
{% elif customfield.type == 'json' and value is not None %}
|
||||||
<pre>{{ value|json }}</pre>
|
<pre>{{ value|json }}</pre>
|
||||||
{% elif customfield.type == 'multiselect' and value %}
|
{% elif customfield.type == 'multiselect' and value %}
|
||||||
{{ value|join:", " }}
|
{{ value|join:", " }}
|
||||||
|
Loading…
Reference in New Issue
Block a user