Commit db196195 authored by Claude Paroz's avatar Claude Paroz
Browse files

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.
parent 64aba7a8
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)
+5 −4
Original line number Diff line number Diff line
@@ -365,12 +365,13 @@ 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))
            try:
                if isinstance(field, FileField):
+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)