Repository: encode/django-rest-framework

Issue:
JSONField not rendered properly in DRF browsable API HTML form
## Checklist

- [x] I have verified that that issue exists against the `master` branch of Django REST framework.
    -   73ad88eaae2f49bfd09508f2dcd6446677800a26
- [x] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
    -   #4135: webform-input parsed as string (PR: #4565)
- [x] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
- [x] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](http://www.django-rest-framework.org/topics/third-party-resources/#about-third-party-packages) where possible.)
- [x] I have reduced the issue to the simplest possible case.
- [ ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

## Expected behavior

Assuming:
- a valid JSON object is stored in postgreSQL as a django `models.JSONField` and 
- that data is serialized using DRF's serializers.JSONField, 

then that data should render as a JSON object in DRF browsable API (particularly because the serializer requires input to be valid JSON for POST/PUT/PATCH methods)

## Actual behavior

Data renders as native python in DRF browsable API form field (e.g. single quotes, uppercase booleans)

![instance_view_html](https://cloud.githubusercontent.com/assets/1246469/24063790/5ac9358a-0b1e-11e7-80bd-2021bbdc8723.png)

Note that the "Raw data" view of the input section renders the data correctly as JSON.

![instance_view_raw](https://cloud.githubusercontent.com/assets/1246469/24063901/dea9db3e-0b1e-11e7-9d51-0d1aa1f7d9e2.png)


## Steps to reproduce

```bash
(venv)vagrant@vm:/vagrant/src/app$ python -V
Python 3.4.3
(venv)vagrant@vm:/vagrant/src/app$ psql -V
psql (PostgreSQL) 9.6.2
```

```
# requirements.txt
Django==1.10.6
psycopg2==2.7.1
-e git+git@github.com:tomchristie/django-rest-framework.git@73ad88eaae2f49bfd09508f2dcd6446677800a26#egg=djangorestframework-origin/HEAD
```

```python
# models.py

from django.db import models
from django.contrib.postgres.fields import JSONField


class Foo(models.Model):

    name = models.CharField(max_length=24)
    data = JSONField()
```

```python
# serializers.py

from rest_framework import serializers

from jsontest import models


class FooSerializer(serializers.HyperlinkedModelSerializer):

    name = serializers.CharField(max_length=24)
    data = serializers.JSONField()

    class Meta:
        model = models.Foo
        fields = ('url', 'name', 'data')
```

```
>>> from jsontest.models import *
>>> f = Foo()
>>> f.name = 'created via shell'
>>> f.data = {'input': 'native dict', 'bar': True}
>>> f.save()
>>> f.id
1
```


Discussion:
Thanks for reporting.
I gave it a try and yeah, this is reproduced.

Resolving patch:
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -38,7 +38,8 @@
     get_relation_kwargs, get_url_kwargs
 )
 from rest_framework.utils.serializer_helpers import (
-    BindingDict, BoundField, NestedBoundField, ReturnDict, ReturnList
+    BindingDict, BoundField, JSONBoundField, NestedBoundField, ReturnDict,
+    ReturnList
 )
 from rest_framework.validators import (
     UniqueForDateValidator, UniqueForMonthValidator, UniqueForYearValidator,
@@ -521,6 +522,8 @@ def __getitem__(self, key):
         error = self.errors.get(key) if hasattr(self, '_errors') else None
         if isinstance(field, Serializer):
             return NestedBoundField(field, value, error)
+        if isinstance(field, JSONField):
+            return JSONBoundField(field, value, error)
         return BoundField(field, value, error)
 
     # Include a backlink to the serializer class on return objects.
diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py
--- a/rest_framework/utils/field_mapping.py
+++ b/rest_framework/utils/field_mapping.py
@@ -8,7 +8,7 @@
 from django.db import models
 from django.utils.text import capfirst
 
-from rest_framework.compat import DecimalValidator
+from rest_framework.compat import DecimalValidator, JSONField
 from rest_framework.validators import UniqueValidator
 
 NUMERIC_FIELD_TYPES = (
@@ -88,7 +88,7 @@ def get_field_kwargs(field_name, model_field):
     if decimal_places is not None:
         kwargs['decimal_places'] = decimal_places
 
-    if isinstance(model_field, models.TextField):
+    if isinstance(model_field, models.TextField) or (JSONField and isinstance(model_field, JSONField)):
         kwargs['style'] = {'base_template': 'textarea.html'}
 
     if isinstance(model_field, models.AutoField) or not model_field.editable:
diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py
--- a/rest_framework/utils/serializer_helpers.py
+++ b/rest_framework/utils/serializer_helpers.py
@@ -1,6 +1,7 @@
 from __future__ import unicode_literals
 
 import collections
+import json
 from collections import OrderedDict
 
 from django.utils.encoding import force_text
@@ -82,6 +83,16 @@ def as_form_field(self):
         return self.__class__(self._field, value, self.errors, self._prefix)
 
 
+class JSONBoundField(BoundField):
+    def as_form_field(self):
+        value = self.value
+        try:
+            value = json.dumps(self.value, sort_keys=True, indent=4)
+        except TypeError:
+            pass
+        return self.__class__(self._field, value, self.errors, self._prefix)
+
+
 class NestedBoundField(BoundField):
     """
     This `BoundField` additionally implements __iter__ and __getitem__


Test patch: