Commit 6a8ba2ee authored by Claude Paroz's avatar Claude Paroz
Browse files

[1.9.x] 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.
Partial backport of db196195 from master.
parent a304e7dd
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
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)
+3 −0
Original line number Diff line number Diff line
@@ -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
Original line number Diff line number Diff line
@@ -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)