From 2c96b3da6f9be3795bd2ded59175332aa986bf2d Mon Sep 17 00:00:00 2001
From: Matthew Somerville <matthew-github@dracos.co.uk>
Date: Fri, 5 Jun 2015 21:56:00 +0100
Subject: [PATCH] [1.8.x] Refs #24937 -- Backported more commits to fix for
 serialization of Date(Time)RangeField.

Instead of using DjangoJSONEncoder, use base_field's value_to_string().

Note this means the serialization of e.g. IntegerRangeField now has
strings for lower and upper, so use to_python when they came back in
(same behaviour as ArrayField, hopefully, from where I also got the
set_attributes_from_name function).

Backport of 86d9b10dc33cc115fee2ecab40a569354ac55d15 and
8a842148b6deaab021526e2689279cf5e232945f from master
---
 django/contrib/postgres/fields/array.py  |  7 ++-----
 django/contrib/postgres/fields/ranges.py | 25 +++++++++++++++++-------
 django/contrib/postgres/fields/utils.py  |  3 +++
 tests/postgres_tests/test_ranges.py      | 10 +++++-----
 4 files changed, 28 insertions(+), 17 deletions(-)
 create mode 100644 django/contrib/postgres/fields/utils.py

diff --git a/django/contrib/postgres/fields/array.py b/django/contrib/postgres/fields/array.py
index 970355fd62..5d49d49e22 100644
--- a/django/contrib/postgres/fields/array.py
+++ b/django/contrib/postgres/fields/array.py
@@ -8,14 +8,11 @@ from django.db.models import Field, IntegerField, Transform
 from django.utils import six
 from django.utils.translation import string_concat, ugettext_lazy as _
 
+from .utils import AttributeSetter
+
 __all__ = ['ArrayField']
 
 
-class AttributeSetter(object):
-    def __init__(self, name, value):
-        setattr(self, name, value)
-
-
 class ArrayField(Field):
     empty_strings_allowed = False
     default_error_messages = {
diff --git a/django/contrib/postgres/fields/ranges.py b/django/contrib/postgres/fields/ranges.py
index 5681c373c0..fe69b08f9a 100644
--- a/django/contrib/postgres/fields/ranges.py
+++ b/django/contrib/postgres/fields/ranges.py
@@ -3,10 +3,11 @@ import json
 from psycopg2.extras import DateRange, DateTimeTZRange, NumericRange, Range
 
 from django.contrib.postgres import forms, lookups
-from django.core.serializers.json import DjangoJSONEncoder
 from django.db import models
 from django.utils import six
 
+from .utils import AttributeSetter
+
 __all__ = [
     'RangeField', 'IntegerRangeField', 'BigIntegerRangeField',
     'FloatRangeField', 'DateTimeRangeField', 'DateRangeField',
@@ -27,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,
-        }, cls=DjangoJSONEncoder)
+        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)
diff --git a/django/contrib/postgres/fields/utils.py b/django/contrib/postgres/fields/utils.py
new file mode 100644
index 0000000000..424a78f521
--- /dev/null
+++ b/django/contrib/postgres/fields/utils.py
@@ -0,0 +1,3 @@
+class AttributeSetter(object):
+    def __init__(self, name, value):
+        setattr(self, name, value)
diff --git a/tests/postgres_tests/test_ranges.py b/tests/postgres_tests/test_ranges.py
index e90c2f8709..feab733161 100644
--- a/tests/postgres_tests/test_ranges.py
+++ b/tests/postgres_tests/test_ranges.py
@@ -193,18 +193,18 @@ class TestQuerying(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": "{\\"upper\\": \\"2014-02-02T12:12:12\\", '
-        '\\"lower\\": \\"2014-01-01T00:00:00\\", \\"bounds\\": \\"[)\\"}", '
+        '"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)
-    upper_dt = datetime.datetime(2014, 2, 2, 12, 12, 12)
+    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),