Skip to content

Commit

Permalink
Merge pull request #154 from michaelhays/master
Browse files Browse the repository at this point in the history
Allow passing child_attrs to RangeField
  • Loading branch information
alicertel committed Feb 18, 2021
2 parents c010ea0 + d47ea98 commit 2177ce6
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 5 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,28 @@ point = {
serializer = PointFieldSerializer(data={'created': now, 'point': point})
```


# RangeField

The Range Fields map to Django's PostgreSQL specific [Range Fields](https://docs.djangoproject.com/en/stable/ref/contrib/postgres/fields/#range-fields).

Each accepts an optional parameter `child_attrs`, which allows passing parameters to the child field.

For example, calling `IntegerRangeField(child_attrs={"allow_null": True})` allows deserializing data with a null value for `lower` and/or `upper`:

```python
from rest_framework import serializers
from drf_extra_fields.fields import IntegerRangeField


class RangeSerializer(serializers.Serializer):
ranges = IntegerRangeField(child_attrs={"allow_null": True})


serializer = RangeSerializer(data={'ranges': {'lower': 0, 'upper': None}})

```

## IntegerRangeField

```python
Expand Down
20 changes: 15 additions & 5 deletions drf_extra_fields/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,11 @@ class RangeField(DictField):
'bound_ordering': _('The start of the range must not exceed the end of the range.'),
})

def __init__(self, **kwargs):
self.child_attrs = kwargs.pop("child_attrs", {})
self.child = self.child_class(**self.default_child_attrs, **self.child_attrs)
super(RangeField, self).__init__(**kwargs)

def to_internal_value(self, data):
"""
Range instances <- Dicts of primitive datatypes.
Expand Down Expand Up @@ -258,27 +263,32 @@ def get_initial(self):


class IntegerRangeField(RangeField):
child = IntegerField()
child_class = IntegerField
default_child_attrs = {}
range_type = NumericRange


class FloatRangeField(RangeField):
child = FloatField()
child_class = FloatField
default_child_attrs = {}
range_type = NumericRange


class DecimalRangeField(RangeField):
child = DecimalField(max_digits=None, decimal_places=None)
child_class = DecimalField
default_child_attrs = {"max_digits": None, "decimal_places": None}
range_type = NumericRange


class DateTimeRangeField(RangeField):
child = DateTimeField()
child_class = DateTimeField
default_child_attrs = {}
range_type = DateTimeTZRange


class DateRangeField(RangeField):
child = DateField()
child_class = DateField
default_child_attrs = {}
range_type = DateRange


Expand Down
51 changes: 51 additions & 0 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,11 @@ class IntegerRangeSerializer(serializers.Serializer):
range = IntegerRangeField()


class IntegerRangeChildAllowNullSerializer(serializers.Serializer):

range = IntegerRangeField(child_attrs={"allow_null": True})


class FloatRangeSerializer(serializers.Serializer):

range = FloatRangeField()
Expand Down Expand Up @@ -499,6 +504,8 @@ class TestIntegerRangeField(FieldValues):
('not a dict', ['Expected a dictionary of items but got type "str".']),
({'foo': 'bar'}, ['Extra content not allowed "foo".']),
({'lower': 2, 'upper': 1}, ['The start of the range must not exceed the end of the range.']),
({'lower': 1, 'upper': None, 'bounds': '[)'}, ['This field may not be null.']),
({'lower': None, 'upper': 1, 'bounds': '[)'}, ['This field may not be null.']),
]
outputs = [
(NumericRange(**{'lower': '1', 'upper': '2'}),
Expand Down Expand Up @@ -527,6 +534,50 @@ def test_no_source_on_child(self):
)


class TestIntegerRangeChildAllowNullField(FieldValues):
serializer_class = IntegerRangeChildAllowNullSerializer

valid_inputs = [
({'lower': '1', 'upper': 2, 'bounds': '[)'},
NumericRange(**{'lower': 1, 'upper': 2, 'bounds': '[)'})),
({'lower': 1, 'upper': 2},
NumericRange(**{'lower': 1, 'upper': 2})),
({'lower': 1},
NumericRange(**{'lower': 1})),
({'upper': 1},
NumericRange(**{'upper': 1})),
({'empty': True},
NumericRange(**{'empty': True})),
({}, NumericRange()),
({'lower': 1, 'upper': None, 'bounds': '[)'},
NumericRange(**{'lower': 1, 'upper': None, 'bounds': '[)'})),
({'lower': None, 'upper': 1, 'bounds': '[)'},
NumericRange(**{'lower': None, 'upper': 1, 'bounds': '[)'})),
]
invalid_inputs = [
({'lower': 'a'}, ['A valid integer is required.']),
('not a dict', ['Expected a dictionary of items but got type "str".']),
({'foo': 'bar'}, ['Extra content not allowed "foo".']),
({'lower': 2, 'upper': 1}, ['The start of the range must not exceed the end of the range.']),
]
outputs = [
(NumericRange(**{'lower': '1', 'upper': '2'}),
{'lower': 1, 'upper': 2, 'bounds': '[)'}),
(NumericRange(**{'empty': True}), {'empty': True}),
(NumericRange(), {'bounds': '[)', 'lower': None, 'upper': None}),
({'lower': '1', 'upper': 2, 'bounds': '[)'},
{'lower': 1, 'upper': 2, 'bounds': '[)'}),
({'lower': 1, 'upper': 2},
{'lower': 1, 'upper': 2, 'bounds': None}),
({'lower': 1},
{'lower': 1, 'upper': None, 'bounds': None}),
({'upper': 1},
{'lower': None, 'upper': 1, 'bounds': None}),
({}, {}),
]
field = IntegerRangeField(child_attrs={"allow_null": True})


class TestDecimalRangeField(FieldValues):
serializer_class = DecimalRangeSerializer

Expand Down

0 comments on commit 2177ce6

Please sign in to comment.