Add HStoreField, postgres fields tests (#5654)

* Test postgres field mapping

* Add HStoreField

* Ensure 'HStoreField' child is a 'CharField'

* Add HStoreField docs
This commit is contained in:
Ryan P Kilby 2018-01-15 09:52:30 -05:00 committed by Carlton Gibson
parent d3f3c3d9c1
commit 2709de1310
6 changed files with 118 additions and 8 deletions

View File

@ -473,6 +473,16 @@ You can also use the declarative style, as with `ListField`. For example:
class DocumentField(DictField):
child = CharField()
## HStoreField
A preconfigured `DictField` that is compatible with Django's postgres `HStoreField`.
**Signature**: `HStoreField(child=<A_FIELD_INSTANCE>)`
- `child` - A field instance that is used for validating the values in the dictionary. The default child field accepts both empty strings and null values.
Note that the child field **must** be an instance of `CharField`, as the hstore extension stores values as strings.
## JSONField
A field class that validates that the incoming data structure consists of valid JSON primitives. In its alternate binary mode, it will represent and validate JSON-encoded binary strings.

View File

@ -1,5 +1,6 @@
# Optional packages which may be used with REST framework.
pytz==2017.2
psycopg2==2.7.3
markdown==2.6.4
django-guardian==1.4.9
django-filter==1.1.0

View File

@ -1711,6 +1711,17 @@ class DictField(Field):
raise ValidationError(errors)
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.')

View File

@ -54,9 +54,9 @@ from rest_framework.validators import (
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 @@ if hasattr(models, 'IPAddressField'):
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

View File

@ -1933,6 +1933,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`.

View File

@ -21,7 +21,7 @@ from django.test import TestCase
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 TestGenericIPAddressFieldValidation(TestCase):
'{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.
# ------------------------------------