Repository: encode/django-rest-framework

Issue:
HStore: This field may not be null.
## Steps to reproduce
```python
class Product(models.Model):
    title = CharField(max_length=512)
    attributes = HStoreField(null=True, blank=True)

class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ('title', 'attributes')
```

Some try:
```python
p = Product(title="foobar", attributes={'first': 'value', 'second': None})
p.save()     # Save success. No error here!


# Now, Let's try with Serializer
data = {"title": "foobar", "attributes": {"first": "value", "second": None}}
q = ProductSerializer(data=data)
q.is_valid()
#> False
q.errors
#> {'attributes': ['This field may not be null.']}
```

## Expected behavior
`q` is valid

## Actual behavior
As you can see, `q` is invalid with an error `{'attributes': ['This field may not be null.']}`.  
When I remove `"second": None` from `attributes`. `q` is valid


Discussion:
This issue seems valid. Django 1.11 added the ability to store nulls as values in addition to strings.

https://docs.djangoproject.com/en/2.0/ref/contrib/postgres/fields/#hstorefield
Ideally the error message would be more specific too. (It would be nice if `second` were mentioned, so I knew where the problem lay, assuming the `null=False` case.) 
@rpkilby I see. But It would be nice if Rest framework also support this too.

Resolving patch:
diff --git a/rest_framework/fields.py b/rest_framework/fields.py
--- a/rest_framework/fields.py
+++ b/rest_framework/fields.py
@@ -1684,6 +1684,17 @@ def to_representation(self, value):
         }
 
 
+class HStoreField(DictField):
+    child = CharField(allow_blank=True, allow_null=True)
+
+    def __init__(self, *args, **kwargs):
+        super(HStoreField, self).__init__(*args, **kwargs)
+        assert isinstance(self.child, CharField), (
+            "The `child` argument must be an instance of `CharField`, "
+            "as the hstore extension stores values as strings."
+        )
+
+
 class JSONField(Field):
     default_error_messages = {
         'invalid': _('Value must be valid JSON.')
diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
--- a/rest_framework/serializers.py
+++ b/rest_framework/serializers.py
@@ -54,9 +54,9 @@
 from rest_framework.fields import (  # NOQA # isort:skip
     BooleanField, CharField, ChoiceField, DateField, DateTimeField, DecimalField,
     DictField, DurationField, EmailField, Field, FileField, FilePathField, FloatField,
-    HiddenField, IPAddressField, ImageField, IntegerField, JSONField, ListField,
-    ModelField, MultipleChoiceField, NullBooleanField, ReadOnlyField, RegexField,
-    SerializerMethodField, SlugField, TimeField, URLField, UUIDField,
+    HiddenField, HStoreField, IPAddressField, ImageField, IntegerField, JSONField,
+    ListField, ModelField, MultipleChoiceField, NullBooleanField, ReadOnlyField,
+    RegexField, SerializerMethodField, SlugField, TimeField, URLField, UUIDField,
 )
 from rest_framework.relations import (  # NOQA # isort:skip
     HyperlinkedIdentityField, HyperlinkedRelatedField, ManyRelatedField,
@@ -1541,10 +1541,7 @@ def get_unique_for_date_validators(self):
     ModelSerializer.serializer_field_mapping[models.IPAddressField] = IPAddressField
 
 if postgres_fields:
-    class CharMappingField(DictField):
-        child = CharField(allow_blank=True)
-
-    ModelSerializer.serializer_field_mapping[postgres_fields.HStoreField] = CharMappingField
+    ModelSerializer.serializer_field_mapping[postgres_fields.HStoreField] = HStoreField
     ModelSerializer.serializer_field_mapping[postgres_fields.ArrayField] = ListField
     ModelSerializer.serializer_field_mapping[postgres_fields.JSONField] = JSONField
 


Test patch:
diff --git a/tests/test_fields.py b/tests/test_fields.py
--- a/tests/test_fields.py
+++ b/tests/test_fields.py
@@ -1897,6 +1897,49 @@ class TestUnvalidatedDictField(FieldValues):
     field = serializers.DictField()
 
 
+class TestHStoreField(FieldValues):
+    """
+    Values for `ListField` with CharField as child.
+    """
+    valid_inputs = [
+        ({'a': 1, 'b': '2', 3: 3}, {'a': '1', 'b': '2', '3': '3'}),
+        ({'a': 1, 'b': None}, {'a': '1', 'b': None}),
+    ]
+    invalid_inputs = [
+        ('not a dict', ['Expected a dictionary of items but got type "str".']),
+    ]
+    outputs = [
+        ({'a': 1, 'b': '2', 3: 3}, {'a': '1', 'b': '2', '3': '3'}),
+    ]
+    field = serializers.HStoreField()
+
+    def test_child_is_charfield(self):
+        with pytest.raises(AssertionError) as exc_info:
+            serializers.HStoreField(child=serializers.IntegerField())
+
+        assert str(exc_info.value) == (
+            "The `child` argument must be an instance of `CharField`, "
+            "as the hstore extension stores values as strings."
+        )
+
+    def test_no_source_on_child(self):
+        with pytest.raises(AssertionError) as exc_info:
+            serializers.HStoreField(child=serializers.CharField(source='other'))
+
+        assert str(exc_info.value) == (
+            "The `source` argument is not meaningful when applied to a `child=` field. "
+            "Remove `source=` from the field declaration."
+        )
+
+    def test_allow_null(self):
+        """
+        If `allow_null=True` then `None` is a valid input.
+        """
+        field = serializers.HStoreField(allow_null=True)
+        output = field.run_validation(None)
+        assert output is None
+
+
 class TestJSONField(FieldValues):
     """
     Values for `JSONField`.
diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py
--- a/tests/test_model_serializer.py
+++ b/tests/test_model_serializer.py
@@ -21,7 +21,7 @@
 from django.utils import six
 
 from rest_framework import serializers
-from rest_framework.compat import unicode_repr
+from rest_framework.compat import postgres_fields, unicode_repr
 
 
 def dedent(blocktext):
@@ -379,6 +379,54 @@ class Meta:
                          '{0}'.format(s.errors))
 
 
+@pytest.mark.skipUnless(postgres_fields, 'postgres is required')
+class TestPosgresFieldsMapping(TestCase):
+    def test_hstore_field(self):
+        class HStoreFieldModel(models.Model):
+            hstore_field = postgres_fields.HStoreField()
+
+        class TestSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = HStoreFieldModel
+                fields = ['hstore_field']
+
+        expected = dedent("""
+            TestSerializer():
+                hstore_field = HStoreField()
+        """)
+        self.assertEqual(unicode_repr(TestSerializer()), expected)
+
+    def test_array_field(self):
+        class ArrayFieldModel(models.Model):
+            array_field = postgres_fields.ArrayField(base_field=models.CharField())
+
+        class TestSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = ArrayFieldModel
+                fields = ['array_field']
+
+        expected = dedent("""
+            TestSerializer():
+                array_field = ListField(child=CharField(label='Array field', validators=[<django.core.validators.MaxLengthValidator object>]))
+        """)
+        self.assertEqual(unicode_repr(TestSerializer()), expected)
+
+    def test_json_field(self):
+        class JSONFieldModel(models.Model):
+            json_field = postgres_fields.JSONField()
+
+        class TestSerializer(serializers.ModelSerializer):
+            class Meta:
+                model = JSONFieldModel
+                fields = ['json_field']
+
+        expected = dedent("""
+            TestSerializer():
+                json_field = JSONField(style={'base_template': 'textarea.html'})
+        """)
+        self.assertEqual(unicode_repr(TestSerializer()), expected)
+
+
 # Tests for relational field mappings.
 # ------------------------------------