Skip to content

Commit 2177ce6

Browse files
authored
Merge pull request #154 from michaelhays/master
Allow passing child_attrs to RangeField
2 parents c010ea0 + d47ea98 commit 2177ce6

File tree

3 files changed

+88
-5
lines changed

3 files changed

+88
-5
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,28 @@ point = {
127127
serializer = PointFieldSerializer(data={'created': now, 'point': point})
128128
```
129129

130+
131+
# RangeField
132+
133+
The Range Fields map to Django's PostgreSQL specific [Range Fields](https://docs.djangoproject.com/en/stable/ref/contrib/postgres/fields/#range-fields).
134+
135+
Each accepts an optional parameter `child_attrs`, which allows passing parameters to the child field.
136+
137+
For example, calling `IntegerRangeField(child_attrs={"allow_null": True})` allows deserializing data with a null value for `lower` and/or `upper`:
138+
139+
```python
140+
from rest_framework import serializers
141+
from drf_extra_fields.fields import IntegerRangeField
142+
143+
144+
class RangeSerializer(serializers.Serializer):
145+
ranges = IntegerRangeField(child_attrs={"allow_null": True})
146+
147+
148+
serializer = RangeSerializer(data={'ranges': {'lower': 0, 'upper': None}})
149+
150+
```
151+
130152
## IntegerRangeField
131153

132154
```python

drf_extra_fields/fields.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,11 @@ class RangeField(DictField):
190190
'bound_ordering': _('The start of the range must not exceed the end of the range.'),
191191
})
192192

193+
def __init__(self, **kwargs):
194+
self.child_attrs = kwargs.pop("child_attrs", {})
195+
self.child = self.child_class(**self.default_child_attrs, **self.child_attrs)
196+
super(RangeField, self).__init__(**kwargs)
197+
193198
def to_internal_value(self, data):
194199
"""
195200
Range instances <- Dicts of primitive datatypes.
@@ -258,27 +263,32 @@ def get_initial(self):
258263

259264

260265
class IntegerRangeField(RangeField):
261-
child = IntegerField()
266+
child_class = IntegerField
267+
default_child_attrs = {}
262268
range_type = NumericRange
263269

264270

265271
class FloatRangeField(RangeField):
266-
child = FloatField()
272+
child_class = FloatField
273+
default_child_attrs = {}
267274
range_type = NumericRange
268275

269276

270277
class DecimalRangeField(RangeField):
271-
child = DecimalField(max_digits=None, decimal_places=None)
278+
child_class = DecimalField
279+
default_child_attrs = {"max_digits": None, "decimal_places": None}
272280
range_type = NumericRange
273281

274282

275283
class DateTimeRangeField(RangeField):
276-
child = DateTimeField()
284+
child_class = DateTimeField
285+
default_child_attrs = {}
277286
range_type = DateTimeTZRange
278287

279288

280289
class DateRangeField(RangeField):
281-
child = DateField()
290+
child_class = DateField
291+
default_child_attrs = {}
282292
range_type = DateRange
283293

284294

tests/test_fields.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,11 @@ class IntegerRangeSerializer(serializers.Serializer):
407407
range = IntegerRangeField()
408408

409409

410+
class IntegerRangeChildAllowNullSerializer(serializers.Serializer):
411+
412+
range = IntegerRangeField(child_attrs={"allow_null": True})
413+
414+
410415
class FloatRangeSerializer(serializers.Serializer):
411416

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

529536

537+
class TestIntegerRangeChildAllowNullField(FieldValues):
538+
serializer_class = IntegerRangeChildAllowNullSerializer
539+
540+
valid_inputs = [
541+
({'lower': '1', 'upper': 2, 'bounds': '[)'},
542+
NumericRange(**{'lower': 1, 'upper': 2, 'bounds': '[)'})),
543+
({'lower': 1, 'upper': 2},
544+
NumericRange(**{'lower': 1, 'upper': 2})),
545+
({'lower': 1},
546+
NumericRange(**{'lower': 1})),
547+
({'upper': 1},
548+
NumericRange(**{'upper': 1})),
549+
({'empty': True},
550+
NumericRange(**{'empty': True})),
551+
({}, NumericRange()),
552+
({'lower': 1, 'upper': None, 'bounds': '[)'},
553+
NumericRange(**{'lower': 1, 'upper': None, 'bounds': '[)'})),
554+
({'lower': None, 'upper': 1, 'bounds': '[)'},
555+
NumericRange(**{'lower': None, 'upper': 1, 'bounds': '[)'})),
556+
]
557+
invalid_inputs = [
558+
({'lower': 'a'}, ['A valid integer is required.']),
559+
('not a dict', ['Expected a dictionary of items but got type "str".']),
560+
({'foo': 'bar'}, ['Extra content not allowed "foo".']),
561+
({'lower': 2, 'upper': 1}, ['The start of the range must not exceed the end of the range.']),
562+
]
563+
outputs = [
564+
(NumericRange(**{'lower': '1', 'upper': '2'}),
565+
{'lower': 1, 'upper': 2, 'bounds': '[)'}),
566+
(NumericRange(**{'empty': True}), {'empty': True}),
567+
(NumericRange(), {'bounds': '[)', 'lower': None, 'upper': None}),
568+
({'lower': '1', 'upper': 2, 'bounds': '[)'},
569+
{'lower': 1, 'upper': 2, 'bounds': '[)'}),
570+
({'lower': 1, 'upper': 2},
571+
{'lower': 1, 'upper': 2, 'bounds': None}),
572+
({'lower': 1},
573+
{'lower': 1, 'upper': None, 'bounds': None}),
574+
({'upper': 1},
575+
{'lower': None, 'upper': 1, 'bounds': None}),
576+
({}, {}),
577+
]
578+
field = IntegerRangeField(child_attrs={"allow_null": True})
579+
580+
530581
class TestDecimalRangeField(FieldValues):
531582
serializer_class = DecimalRangeSerializer
532583

0 commit comments

Comments
 (0)