From 30f334cc58e939c7d9bd8455c80bd066fbde9f2b Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Wed, 12 Jul 2017 15:18:49 +0500 Subject: [PATCH] [1.11.x] Fixed #28389 -- Fixed pickling of LazyObject on Python 2 when wrapped object doesn't have __reduce__(). Partial revert of 35355a4ffedb2aeed52d5fe3034380ffc6a438db. --- django/utils/functional.py | 13 +++++++------ docs/releases/1.11.4.txt | 3 +++ tests/utils_tests/test_lazyobject.py | 2 ++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/django/utils/functional.py b/django/utils/functional.py index 7d5b7feea5..4ea5fe5745 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -300,13 +300,14 @@ class LazyObject(object): self._setup() return (unpickle_lazyobject, (self._wrapped,)) + # Overriding __class__ stops __reduce__ from being called on Python 2. + # So, define __getstate__ in a way that cooperates with the way that + # pickle interprets this class. This fails when the wrapped class is a + # builtin, but it's better than nothing. def __getstate__(self): - """ - Prevent older versions of pickle from trying to pickle the __dict__ - (which in the case of a SimpleLazyObject may contain a lambda). The - value will be ignored by __reduce__() and the custom unpickler. - """ - return {} + if self._wrapped is empty: + self._setup() + return self._wrapped.__dict__ def __copy__(self): if self._wrapped is empty: diff --git a/docs/releases/1.11.4.txt b/docs/releases/1.11.4.txt index 2383ad6a17..2cd093335e 100644 --- a/docs/releases/1.11.4.txt +++ b/docs/releases/1.11.4.txt @@ -15,3 +15,6 @@ Bugfixes * Fixed ``QuerySet.union()`` and ``difference()`` when combining with a queryset raising ``EmptyResultSet`` (:ticket:`28378`). + +* Fixed a regression in pickling of ``LazyObject`` on Python 2 when the wrapped + object doesn't have ``__reduce__()`` (:ticket:`28389`). diff --git a/tests/utils_tests/test_lazyobject.py b/tests/utils_tests/test_lazyobject.py index 9dc225b55f..c2a9855abf 100644 --- a/tests/utils_tests/test_lazyobject.py +++ b/tests/utils_tests/test_lazyobject.py @@ -187,11 +187,13 @@ class LazyObjectTestCase(TestCase): def test_pickle(self): # See ticket #16563 obj = self.lazy_wrap(Foo()) + obj.bar = 'baz' pickled = pickle.dumps(obj) unpickled = pickle.loads(pickled) self.assertIsInstance(unpickled, Foo) self.assertEqual(unpickled, obj) self.assertEqual(unpickled.foo, obj.foo) + self.assertEqual(unpickled.bar, obj.bar) # Test copying lazy objects wrapping both builtin types and user-defined # classes since a lot of the relevant code does __dict__ manipulation and