diff --git a/django/contrib/postgres/fields/array.py b/django/contrib/postgres/fields/array.py index 736849eb6a..f8009bfb8a 100644 --- a/django/contrib/postgres/fields/array.py +++ b/django/contrib/postgres/fields/array.py @@ -106,8 +106,11 @@ class ArrayField(Field): base_field = self.base_field for val in vals: - obj = AttributeSetter(base_field.attname, val) - values.append(base_field.value_to_string(obj)) + if val is None: + values.append(None) + else: + obj = AttributeSetter(base_field.attname, val) + values.append(base_field.value_to_string(obj)) return json.dumps(values) def get_transform(self, name): diff --git a/django/contrib/postgres/fields/ranges.py b/django/contrib/postgres/fields/ranges.py index bf3f8cb9a4..5bce0743f0 100644 --- a/django/contrib/postgres/fields/ranges.py +++ b/django/contrib/postgres/fields/ranges.py @@ -51,8 +51,12 @@ class RangeField(models.Field): 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) + val = getattr(value, end) + if val is None: + result[end] = None + else: + obj = AttributeSetter(base_field.attname, val) + result[end] = base_field.value_to_string(obj) return json.dumps(result) def formfield(self, **kwargs): diff --git a/docs/releases/1.8.10.txt b/docs/releases/1.8.10.txt index 4eea5a3b7b..b29000eb20 100644 --- a/docs/releases/1.8.10.txt +++ b/docs/releases/1.8.10.txt @@ -17,3 +17,7 @@ Bugfixes * Made ``forms.FileField`` and ``utils.translation.lazy_number()`` picklable (:ticket:`26212`). + +* Fixed :class:`~django.contrib.postgres.fields.RangeField` and + :class:`~django.contrib.postgres.fields.ArrayField` serialization with + ``None`` values (:ticket:`26215`). diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt index 05c6542329..af7bcf21bb 100644 --- a/docs/releases/1.9.3.txt +++ b/docs/releases/1.9.3.txt @@ -27,3 +27,7 @@ Bugfixes * Made ``forms.FileField`` and ``utils.translation.lazy_number()`` picklable (:ticket:`26212`). + +* Fixed :class:`~django.contrib.postgres.fields.RangeField` and + :class:`~django.contrib.postgres.fields.ArrayField` serialization with + ``None`` values (:ticket:`26215`). diff --git a/tests/postgres_tests/test_array.py b/tests/postgres_tests/test_array.py index 24a47db1d7..5df25c1c0f 100644 --- a/tests/postgres_tests/test_array.py +++ b/tests/postgres_tests/test_array.py @@ -448,17 +448,17 @@ class TestMigrations(TransactionTestCase): class TestSerialization(PostgreSQLTestCase): test_data = ( - '[{"fields": {"field": "[\\"1\\", \\"2\\"]"}, "model": "postgres_tests.integerarraymodel", "pk": null}]' + '[{"fields": {"field": "[\\"1\\", \\"2\\", null]"}, "model": "postgres_tests.integerarraymodel", "pk": null}]' ) def test_dumping(self): - instance = IntegerArrayModel(field=[1, 2]) + instance = IntegerArrayModel(field=[1, 2, None]) data = serializers.serialize('json', [instance]) self.assertEqual(json.loads(data), json.loads(self.test_data)) def test_loading(self): instance = list(serializers.deserialize('json', self.test_data))[0].object - self.assertEqual(instance.field, [1, 2]) + self.assertEqual(instance.field, [1, 2, None]) class TestValidation(PostgreSQLTestCase): diff --git a/tests/postgres_tests/test_hstore.py b/tests/postgres_tests/test_hstore.py index 13c08386e2..8ebd757098 100644 --- a/tests/postgres_tests/test_hstore.py +++ b/tests/postgres_tests/test_hstore.py @@ -165,7 +165,8 @@ class TestQuerying(PostgreSQLTestCase): class TestSerialization(PostgreSQLTestCase): - test_data = '[{"fields": {"field": "{\\"a\\": \\"b\\"}"}, "model": "postgres_tests.hstoremodel", "pk": null}]' + test_data = ('[{"fields": {"field": "{\\"a\\": \\"b\\"}"}, ' + '"model": "postgres_tests.hstoremodel", "pk": null}]') def test_dumping(self): instance = HStoreModel(field={'a': 'b'}) @@ -176,6 +177,12 @@ class TestSerialization(PostgreSQLTestCase): instance = list(serializers.deserialize('json', self.test_data))[0].object self.assertEqual(instance.field, {'a': 'b'}) + def test_roundtrip_with_null(self): + instance = HStoreModel(field={'a': 'b', 'c': None}) + data = serializers.serialize('json', [instance]) + new_instance = list(serializers.deserialize('json', data))[0].object + self.assertEqual(instance.field, new_instance.field) + class TestValidation(PostgreSQLTestCase): diff --git a/tests/postgres_tests/test_json.py b/tests/postgres_tests/test_json.py index b4f0dd4ecf..59e5fe3fe7 100644 --- a/tests/postgres_tests/test_json.py +++ b/tests/postgres_tests/test_json.py @@ -213,16 +213,16 @@ class TestQuerying(TestCase): @skipUnlessPG94 class TestSerialization(TestCase): - test_data = '[{"fields": {"field": {"a": "b"}}, "model": "postgres_tests.jsonmodel", "pk": null}]' + test_data = '[{"fields": {"field": {"a": "b", "c": null}}, "model": "postgres_tests.jsonmodel", "pk": null}]' def test_dumping(self): - instance = JSONModel(field={'a': 'b'}) + instance = JSONModel(field={'a': 'b', 'c': None}) data = serializers.serialize('json', [instance]) self.assertJSONEqual(data, self.test_data) def test_loading(self): instance = list(serializers.deserialize('json', self.test_data))[0].object - self.assertEqual(instance.field, {'a': 'b'}) + self.assertEqual(instance.field, {'a': 'b', 'c': None}) class TestValidation(PostgreSQLTestCase): diff --git a/tests/postgres_tests/test_ranges.py b/tests/postgres_tests/test_ranges.py index dd676f96be..692fb54a89 100644 --- a/tests/postgres_tests/test_ranges.py +++ b/tests/postgres_tests/test_ranges.py @@ -309,6 +309,17 @@ class TestSerialization(PostgreSQLTestCase): self.assertEqual(instance.dates, DateRange(self.lower_date, self.upper_date)) self.assertEqual(instance.timestamps, DateTimeTZRange(self.lower_dt, self.upper_dt)) + def test_serialize_range_with_null(self): + instance = RangesModel(ints=NumericRange(None, 10)) + data = serializers.serialize('json', [instance]) + new_instance = list(serializers.deserialize('json', data))[0].object + self.assertEqual(new_instance.ints, NumericRange(None, 10)) + + instance = RangesModel(ints=NumericRange(10, None)) + data = serializers.serialize('json', [instance]) + new_instance = list(serializers.deserialize('json', data))[0].object + self.assertEqual(new_instance.ints, NumericRange(10, None)) + class TestValidators(PostgreSQLTestCase):