Browse Source

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.
Claude Paroz 9 years ago
parent
commit
db19619545

+ 15 - 0
django/contrib/postgres/forms/jsonb.py

@@ -1,11 +1,16 @@
 import json
 
 from django import forms
+from django.utils import six
 from django.utils.translation import ugettext_lazy as _
 
 __all__ = ['JSONField']
 
 
+class InvalidJSONInput(six.text_type):
+    pass
+
+
 class JSONField(forms.CharField):
     default_error_messages = {
         'invalid': _("'%(value)s' value must be valid JSON."),
@@ -27,5 +32,15 @@ class JSONField(forms.CharField):
                 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):
+        if isinstance(value, InvalidJSONInput):
+            return value
         return json.dumps(value)

+ 5 - 4
django/forms/forms.py

@@ -365,13 +365,14 @@ class BaseForm(object):
 
     def _clean_fields(self):
         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.
             # Each widget type knows how to retrieve its own data, because some
             # widgets split data over several HTML fields.
-            if field.disabled:
-                value = self.initial.get(name, field.initial)
-            else:
-                value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
+            value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
             try:
                 if isinstance(field, FileField):
                     initial = self.initial.get(name, field.initial)

+ 3 - 0
docs/releases/1.9.5.txt

@@ -46,3 +46,6 @@ Bugfixes
 
 * Fixed a migrations crash on SQLite when renaming the primary key of a model
   containing a ``ForeignKey`` to ``'self'`` (:ticket:`26384`).
+
+* Fixed ``JSONField`` inadvertently escaping its contents when displaying values
+  after failed form validation (:ticket:`25532`).

+ 29 - 0
tests/postgres_tests/test_json.py

@@ -3,7 +3,9 @@ import unittest
 
 from django.core import exceptions, serializers
 from django.db import connection
+from django.forms import CharField, Form
 from django.test import TestCase
+from django.utils.html import escape
 
 from . import PostgreSQLTestCase
 from .models import JSONModel
@@ -258,7 +260,34 @@ class TestFormField(PostgreSQLTestCase):
         form_field = model_field.formfield()
         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('[&quot;foo&quot;]</textarea>', form.as_p())
+
     def test_prepare_value(self):
         field = forms.JSONField()
         self.assertEqual(field.prepare_value({'a': 'b'}), '{"a": "b"}')
         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('[&quot;foo&quot;]</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)