Fixed #25532 -- Properly redisplayed JSONField form input values
Thanks David Szotten for the report and Tommy Beadle for code inspiration. Thanks Tim Graham for the review.
This commit is contained in:
parent
64aba7a8ab
commit
db19619545
|
@ -1,11 +1,16 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.utils import six
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
__all__ = ['JSONField']
|
__all__ = ['JSONField']
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidJSONInput(six.text_type):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class JSONField(forms.CharField):
|
class JSONField(forms.CharField):
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _("'%(value)s' value must be valid JSON."),
|
'invalid': _("'%(value)s' value must be valid JSON."),
|
||||||
|
@ -27,5 +32,15 @@ class JSONField(forms.CharField):
|
||||||
params={'value': value},
|
params={'value': value},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def bound_data(self, data, initial):
|
||||||
|
if self.disabled:
|
||||||
|
return initial
|
||||||
|
try:
|
||||||
|
return json.loads(data)
|
||||||
|
except ValueError:
|
||||||
|
return InvalidJSONInput(data)
|
||||||
|
|
||||||
def prepare_value(self, value):
|
def prepare_value(self, value):
|
||||||
|
if isinstance(value, InvalidJSONInput):
|
||||||
|
return value
|
||||||
return json.dumps(value)
|
return json.dumps(value)
|
||||||
|
|
|
@ -365,13 +365,14 @@ class BaseForm(object):
|
||||||
|
|
||||||
def _clean_fields(self):
|
def _clean_fields(self):
|
||||||
for name, field in self.fields.items():
|
for name, field in self.fields.items():
|
||||||
|
if field.disabled:
|
||||||
|
# Initial values are supposed to be clean
|
||||||
|
self.cleaned_data[name] = self.initial.get(name, field.initial)
|
||||||
|
continue
|
||||||
# value_from_datadict() gets the data from the data dictionaries.
|
# value_from_datadict() gets the data from the data dictionaries.
|
||||||
# Each widget type knows how to retrieve its own data, because some
|
# Each widget type knows how to retrieve its own data, because some
|
||||||
# widgets split data over several HTML fields.
|
# widgets split data over several HTML fields.
|
||||||
if field.disabled:
|
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
|
||||||
value = self.initial.get(name, field.initial)
|
|
||||||
else:
|
|
||||||
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
|
|
||||||
try:
|
try:
|
||||||
if isinstance(field, FileField):
|
if isinstance(field, FileField):
|
||||||
initial = self.initial.get(name, field.initial)
|
initial = self.initial.get(name, field.initial)
|
||||||
|
|
|
@ -46,3 +46,6 @@ Bugfixes
|
||||||
|
|
||||||
* Fixed a migrations crash on SQLite when renaming the primary key of a model
|
* Fixed a migrations crash on SQLite when renaming the primary key of a model
|
||||||
containing a ``ForeignKey`` to ``'self'`` (:ticket:`26384`).
|
containing a ``ForeignKey`` to ``'self'`` (:ticket:`26384`).
|
||||||
|
|
||||||
|
* Fixed ``JSONField`` inadvertently escaping its contents when displaying values
|
||||||
|
after failed form validation (:ticket:`25532`).
|
||||||
|
|
|
@ -3,7 +3,9 @@ import unittest
|
||||||
|
|
||||||
from django.core import exceptions, serializers
|
from django.core import exceptions, serializers
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
|
from django.forms import CharField, Form
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.utils.html import escape
|
||||||
|
|
||||||
from . import PostgreSQLTestCase
|
from . import PostgreSQLTestCase
|
||||||
from .models import JSONModel
|
from .models import JSONModel
|
||||||
|
@ -258,7 +260,34 @@ class TestFormField(PostgreSQLTestCase):
|
||||||
form_field = model_field.formfield()
|
form_field = model_field.formfield()
|
||||||
self.assertIsInstance(form_field, forms.JSONField)
|
self.assertIsInstance(form_field, forms.JSONField)
|
||||||
|
|
||||||
|
def test_formfield_disabled(self):
|
||||||
|
class JsonForm(Form):
|
||||||
|
name = CharField()
|
||||||
|
jfield = forms.JSONField(disabled=True)
|
||||||
|
|
||||||
|
form = JsonForm({'name': 'xyz', 'jfield': '["bar"]'}, initial={'jfield': ['foo']})
|
||||||
|
self.assertIn('["foo"]</textarea>', form.as_p())
|
||||||
|
|
||||||
def test_prepare_value(self):
|
def test_prepare_value(self):
|
||||||
field = forms.JSONField()
|
field = forms.JSONField()
|
||||||
self.assertEqual(field.prepare_value({'a': 'b'}), '{"a": "b"}')
|
self.assertEqual(field.prepare_value({'a': 'b'}), '{"a": "b"}')
|
||||||
self.assertEqual(field.prepare_value(None), 'null')
|
self.assertEqual(field.prepare_value(None), 'null')
|
||||||
|
self.assertEqual(field.prepare_value('foo'), '"foo"')
|
||||||
|
|
||||||
|
def test_redisplay_wrong_input(self):
|
||||||
|
"""
|
||||||
|
When displaying a bound form (typically due to invalid input), the form
|
||||||
|
should not overquote JSONField inputs.
|
||||||
|
"""
|
||||||
|
class JsonForm(Form):
|
||||||
|
name = CharField(max_length=2)
|
||||||
|
jfield = forms.JSONField()
|
||||||
|
|
||||||
|
# JSONField input is fine, name is too long
|
||||||
|
form = JsonForm({'name': 'xyz', 'jfield': '["foo"]'})
|
||||||
|
self.assertIn('["foo"]</textarea>', form.as_p())
|
||||||
|
|
||||||
|
# This time, the JSONField input is wrong
|
||||||
|
form = JsonForm({'name': 'xy', 'jfield': '{"foo"}'})
|
||||||
|
# Appears once in the textarea and once in the error message
|
||||||
|
self.assertEqual(form.as_p().count(escape('{"foo"}')), 2)
|
||||||
|
|
Loading…
Reference in New Issue