Skip to content

Commit 89eb66f

Browse files
Ryan P KilbyPierre Chiquet
authored andcommitted
Add HStoreField, postgres fields tests (encode#5654)
* Test postgres field mapping * Add HStoreField * Ensure 'HStoreField' child is a 'CharField' * Add HStoreField docs
1 parent bc78654 commit 89eb66f

File tree

6 files changed

+118
-8
lines changed

6 files changed

+118
-8
lines changed

docs/api-guide/fields.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,16 @@ You can also use the declarative style, as with `ListField`. For example:
473473
class DocumentField(DictField):
474474
child = CharField()
475475

476+
## HStoreField
477+
478+
A preconfigured `DictField` that is compatible with Django's postgres `HStoreField`.
479+
480+
**Signature**: `HStoreField(child=<A_FIELD_INSTANCE>)`
481+
482+
- `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.
483+
484+
Note that the child field **must** be an instance of `CharField`, as the hstore extension stores values as strings.
485+
476486
## JSONField
477487

478488
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.

requirements/requirements-optionals.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Optional packages which may be used with REST framework.
22
pytz==2017.2
3+
psycopg2==2.7.3
34
markdown==2.6.4
45
django-guardian==1.4.9
56
django-filter==1.1.0

rest_framework/fields.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1711,6 +1711,17 @@ def run_child_validation(self, data):
17111711
raise ValidationError(errors)
17121712

17131713

1714+
class HStoreField(DictField):
1715+
child = CharField(allow_blank=True, allow_null=True)
1716+
1717+
def __init__(self, *args, **kwargs):
1718+
super(HStoreField, self).__init__(*args, **kwargs)
1719+
assert isinstance(self.child, CharField), (
1720+
"The `child` argument must be an instance of `CharField`, "
1721+
"as the hstore extension stores values as strings."
1722+
)
1723+
1724+
17141725
class JSONField(Field):
17151726
default_error_messages = {
17161727
'invalid': _('Value must be valid JSON.')

rest_framework/serializers.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@
5454
from rest_framework.fields import ( # NOQA # isort:skip
5555
BooleanField, CharField, ChoiceField, DateField, DateTimeField, DecimalField,
5656
DictField, DurationField, EmailField, Field, FileField, FilePathField, FloatField,
57-
HiddenField, IPAddressField, ImageField, IntegerField, JSONField, ListField,
58-
ModelField, MultipleChoiceField, NullBooleanField, ReadOnlyField, RegexField,
59-
SerializerMethodField, SlugField, TimeField, URLField, UUIDField,
57+
HiddenField, HStoreField, IPAddressField, ImageField, IntegerField, JSONField,
58+
ListField, ModelField, MultipleChoiceField, NullBooleanField, ReadOnlyField,
59+
RegexField, SerializerMethodField, SlugField, TimeField, URLField, UUIDField,
6060
)
6161
from rest_framework.relations import ( # NOQA # isort:skip
6262
HyperlinkedIdentityField, HyperlinkedRelatedField, ManyRelatedField,
@@ -1541,10 +1541,7 @@ def get_unique_for_date_validators(self):
15411541
ModelSerializer.serializer_field_mapping[models.IPAddressField] = IPAddressField
15421542

15431543
if postgres_fields:
1544-
class CharMappingField(DictField):
1545-
child = CharField(allow_blank=True)
1546-
1547-
ModelSerializer.serializer_field_mapping[postgres_fields.HStoreField] = CharMappingField
1544+
ModelSerializer.serializer_field_mapping[postgres_fields.HStoreField] = HStoreField
15481545
ModelSerializer.serializer_field_mapping[postgres_fields.ArrayField] = ListField
15491546
ModelSerializer.serializer_field_mapping[postgres_fields.JSONField] = JSONField
15501547

tests/test_fields.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,6 +1933,49 @@ class TestUnvalidatedDictField(FieldValues):
19331933
field = serializers.DictField()
19341934

19351935

1936+
class TestHStoreField(FieldValues):
1937+
"""
1938+
Values for `ListField` with CharField as child.
1939+
"""
1940+
valid_inputs = [
1941+
({'a': 1, 'b': '2', 3: 3}, {'a': '1', 'b': '2', '3': '3'}),
1942+
({'a': 1, 'b': None}, {'a': '1', 'b': None}),
1943+
]
1944+
invalid_inputs = [
1945+
('not a dict', ['Expected a dictionary of items but got type "str".']),
1946+
]
1947+
outputs = [
1948+
({'a': 1, 'b': '2', 3: 3}, {'a': '1', 'b': '2', '3': '3'}),
1949+
]
1950+
field = serializers.HStoreField()
1951+
1952+
def test_child_is_charfield(self):
1953+
with pytest.raises(AssertionError) as exc_info:
1954+
serializers.HStoreField(child=serializers.IntegerField())
1955+
1956+
assert str(exc_info.value) == (
1957+
"The `child` argument must be an instance of `CharField`, "
1958+
"as the hstore extension stores values as strings."
1959+
)
1960+
1961+
def test_no_source_on_child(self):
1962+
with pytest.raises(AssertionError) as exc_info:
1963+
serializers.HStoreField(child=serializers.CharField(source='other'))
1964+
1965+
assert str(exc_info.value) == (
1966+
"The `source` argument is not meaningful when applied to a `child=` field. "
1967+
"Remove `source=` from the field declaration."
1968+
)
1969+
1970+
def test_allow_null(self):
1971+
"""
1972+
If `allow_null=True` then `None` is a valid input.
1973+
"""
1974+
field = serializers.HStoreField(allow_null=True)
1975+
output = field.run_validation(None)
1976+
assert output is None
1977+
1978+
19361979
class TestJSONField(FieldValues):
19371980
"""
19381981
Values for `JSONField`.

tests/test_model_serializer.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from django.utils import six
2222

2323
from rest_framework import serializers
24-
from rest_framework.compat import unicode_repr
24+
from rest_framework.compat import postgres_fields, unicode_repr
2525

2626

2727
def dedent(blocktext):
@@ -379,6 +379,54 @@ class Meta:
379379
'{0}'.format(s.errors))
380380

381381

382+
@pytest.mark.skipUnless(postgres_fields, 'postgres is required')
383+
class TestPosgresFieldsMapping(TestCase):
384+
def test_hstore_field(self):
385+
class HStoreFieldModel(models.Model):
386+
hstore_field = postgres_fields.HStoreField()
387+
388+
class TestSerializer(serializers.ModelSerializer):
389+
class Meta:
390+
model = HStoreFieldModel
391+
fields = ['hstore_field']
392+
393+
expected = dedent("""
394+
TestSerializer():
395+
hstore_field = HStoreField()
396+
""")
397+
self.assertEqual(unicode_repr(TestSerializer()), expected)
398+
399+
def test_array_field(self):
400+
class ArrayFieldModel(models.Model):
401+
array_field = postgres_fields.ArrayField(base_field=models.CharField())
402+
403+
class TestSerializer(serializers.ModelSerializer):
404+
class Meta:
405+
model = ArrayFieldModel
406+
fields = ['array_field']
407+
408+
expected = dedent("""
409+
TestSerializer():
410+
array_field = ListField(child=CharField(label='Array field', validators=[<django.core.validators.MaxLengthValidator object>]))
411+
""")
412+
self.assertEqual(unicode_repr(TestSerializer()), expected)
413+
414+
def test_json_field(self):
415+
class JSONFieldModel(models.Model):
416+
json_field = postgres_fields.JSONField()
417+
418+
class TestSerializer(serializers.ModelSerializer):
419+
class Meta:
420+
model = JSONFieldModel
421+
fields = ['json_field']
422+
423+
expected = dedent("""
424+
TestSerializer():
425+
json_field = JSONField(style={'base_template': 'textarea.html'})
426+
""")
427+
self.assertEqual(unicode_repr(TestSerializer()), expected)
428+
429+
382430
# Tests for relational field mappings.
383431
# ------------------------------------
384432

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy