Commit d58816bd authored by Marc Tamlyn's avatar Marc Tamlyn
Browse files

Merge pull request #4818 from dracos/24937-ranging-to-victory

Fixed #24937 -- fix serialization of Date(Time)RangeField.
parents 3fd754f1 8a842148
Loading
Loading
Loading
Loading
+2 −5
Original line number Diff line number Diff line
@@ -8,12 +8,9 @@ from django.db.models import Field, IntegerField, Transform
from django.utils import six
from django.utils.translation import string_concat, ugettext_lazy as _

__all__ = ['ArrayField']

from .utils import AttributeSetter

class AttributeSetter(object):
    def __init__(self, name, value):
        setattr(self, name, value)
__all__ = ['ArrayField']


class ArrayField(Field):
+18 −6
Original line number Diff line number Diff line
@@ -6,6 +6,8 @@ from django.contrib.postgres import forms, lookups
from django.db import models
from django.utils import six

from .utils import AttributeSetter

__all__ = [
    'RangeField', 'IntegerRangeField', 'BigIntegerRangeField',
    'FloatRangeField', 'DateTimeRangeField', 'DateRangeField',
@@ -26,22 +28,32 @@ class RangeField(models.Field):

    def to_python(self, value):
        if isinstance(value, six.string_types):
            value = self.range_type(**json.loads(value))
            # Assume we're deserializing
            vals = json.loads(value)
            for end in ('lower', 'upper'):
                if end in vals:
                    vals[end] = self.base_field.to_python(vals[end])
            value = self.range_type(**vals)
        elif isinstance(value, (list, tuple)):
            value = self.range_type(value[0], value[1])
        return value

    def set_attributes_from_name(self, name):
        super(RangeField, self).set_attributes_from_name(name)
        self.base_field.set_attributes_from_name(name)

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        if value is None:
            return None
        if value.isempty:
            return json.dumps({"empty": True})
        return json.dumps({
            "lower": value.lower,
            "upper": value.upper,
            "bounds": value._bounds,
        })
        base_field = self.base_field
        result = {"bounds": value._bounds}
        for end in ('lower', 'upper'):
            obj = AttributeSetter(base_field.attname, getattr(value, end))
            result[end] = base_field.value_to_string(obj)
        return json.dumps(result)

    def formfield(self, **kwargs):
        kwargs.setdefault('form_class', self.form_field)
+3 −0
Original line number Diff line number Diff line
class AttributeSetter(object):
    def __init__(self, name, value):
        setattr(self, name, value)
+4 −0
Original line number Diff line number Diff line
@@ -102,6 +102,10 @@ Minor features
* Added :class:`~django.contrib.postgres.fields.JSONField`.
* Added :doc:`/ref/contrib/postgres/aggregates`.

* Fixed serialization of
  :class:`~django.contrib.postgres.fields.DateRangeField` and
  :class:`~django.contrib.postgres.fields.DateTimeRangeField`.

:mod:`django.contrib.redirects`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

+17 −6
Original line number Diff line number Diff line
@@ -291,26 +291,37 @@ class TestQueringWithRanges(TestCase):
@skipUnlessPG92
class TestSerialization(TestCase):
    test_data = (
        '[{"fields": {"ints": "{\\"upper\\": 10, \\"lower\\": 0, '
        '[{"fields": {"ints": "{\\"upper\\": \\"10\\", \\"lower\\": \\"0\\", '
        '\\"bounds\\": \\"[)\\"}", "floats": "{\\"empty\\": true}", '
        '"bigints": null, "timestamps": null, "dates": null}, '
        '"bigints": null, "timestamps": "{\\"upper\\": \\"2014-02-02T12:12:12+00:00\\", '
        '\\"lower\\": \\"2014-01-01T00:00:00+00:00\\", \\"bounds\\": \\"[)\\"}", '
        '"dates": "{\\"upper\\": \\"2014-02-02\\", \\"lower\\": \\"2014-01-01\\", \\"bounds\\": \\"[)\\"}" }, '
        '"model": "postgres_tests.rangesmodel", "pk": null}]'
    )

    lower_date = datetime.date(2014, 1, 1)
    upper_date = datetime.date(2014, 2, 2)
    lower_dt = datetime.datetime(2014, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
    upper_dt = datetime.datetime(2014, 2, 2, 12, 12, 12, tzinfo=timezone.utc)

    def test_dumping(self):
        instance = RangesModel(ints=NumericRange(0, 10), floats=NumericRange(empty=True))
        instance = RangesModel(ints=NumericRange(0, 10), floats=NumericRange(empty=True),
            timestamps=DateTimeTZRange(self.lower_dt, self.upper_dt),
            dates=DateRange(self.lower_date, self.upper_date))
        data = serializers.serialize('json', [instance])
        dumped = json.loads(data)
        dumped[0]['fields']['ints'] = json.loads(dumped[0]['fields']['ints'])
        for field in ('ints', 'dates', 'timestamps'):
            dumped[0]['fields'][field] = json.loads(dumped[0]['fields'][field])
        check = json.loads(self.test_data)
        check[0]['fields']['ints'] = json.loads(check[0]['fields']['ints'])
        for field in ('ints', 'dates', 'timestamps'):
            check[0]['fields'][field] = json.loads(check[0]['fields'][field])
        self.assertEqual(dumped, check)

    def test_loading(self):
        instance = list(serializers.deserialize('json', self.test_data))[0].object
        self.assertEqual(instance.ints, NumericRange(0, 10))
        self.assertEqual(instance.floats, NumericRange(empty=True))
        self.assertEqual(instance.dates, None)
        self.assertEqual(instance.bigints, None)


class TestValidators(PostgreSQLTestCase):