summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Charette <charettes@users.noreply.github.com>2017-10-18 21:43:53 -0400
committerTim Graham <timograham@gmail.com>2017-10-18 21:43:53 -0400
commitd4fb742094dba99cb0db17f3aa9d9f5159af676f (patch)
tree2335588c666880ad56415e4ea7c0edb6b66cf98e
parent4a861e88508eacdb8d0d5bc43f9a0576cedb9f22 (diff)
Refs #28575 -- Made RelatedObjectDoesNotExist classes pickable.
Thanks to Rachel Tobin for the initial __qualname__ work and tests.
-rw-r--r--django/db/models/fields/related_descriptors.py30
-rw-r--r--tests/queryset_pickle/models.py1
-rw-r--r--tests/queryset_pickle/tests.py12
3 files changed, 39 insertions, 4 deletions
diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py
index 39d7223d8a..fb3548f22e 100644
--- a/django/db/models/fields/related_descriptors.py
+++ b/django/db/models/fields/related_descriptors.py
@@ -92,8 +92,13 @@ class ForwardManyToOneDescriptor:
# still be a string model reference.
return type(
'RelatedObjectDoesNotExist',
- (self.field.remote_field.model.DoesNotExist, AttributeError),
- {}
+ (self.field.remote_field.model.DoesNotExist, AttributeError), {
+ '__module__': self.field.model.__module__,
+ '__qualname__': '%s.%s.RelatedObjectDoesNotExist' % (
+ self.field.model.__qualname__,
+ self.field.name,
+ ),
+ }
)
def is_cached(self, instance):
@@ -244,6 +249,14 @@ class ForwardManyToOneDescriptor:
if value is not None and not remote_field.multiple:
remote_field.set_cached_value(value, instance)
+ def __reduce__(self):
+ """
+ Pickling should return the instance attached by self.field on the
+ model, not a new copy of that descriptor. Use getattr() to retrieve
+ the instance directly from the model.
+ """
+ return getattr, (self.field.model, self.field.name)
+
class ForwardOneToOneDescriptor(ForwardManyToOneDescriptor):
"""
@@ -317,8 +330,13 @@ class ReverseOneToOneDescriptor:
# consistency with `ForwardManyToOneDescriptor`.
return type(
'RelatedObjectDoesNotExist',
- (self.related.related_model.DoesNotExist, AttributeError),
- {}
+ (self.related.related_model.DoesNotExist, AttributeError), {
+ '__module__': self.related.model.__module__,
+ '__qualname__': '%s.%s.RelatedObjectDoesNotExist' % (
+ self.related.model.__qualname__,
+ self.related.name,
+ )
+ },
)
def is_cached(self, instance):
@@ -455,6 +473,10 @@ class ReverseOneToOneDescriptor:
# instance to avoid an extra SQL query if it's accessed later on.
self.related.field.set_cached_value(value, instance)
+ def __reduce__(self):
+ # Same purpose as ForwardManyToOneDescriptor.__reduce__().
+ return getattr, (self.related.model, self.related.name)
+
class ReverseManyToOneDescriptor:
"""
diff --git a/tests/queryset_pickle/models.py b/tests/queryset_pickle/models.py
index fba65d7a9e..1275ed6f20 100644
--- a/tests/queryset_pickle/models.py
+++ b/tests/queryset_pickle/models.py
@@ -45,6 +45,7 @@ class Happening(models.Model):
name = models.CharField(blank=True, max_length=100, default="test")
number1 = models.IntegerField(blank=True, default=standalone_number)
number2 = models.IntegerField(blank=True, default=Numbers.get_static_number)
+ event = models.OneToOneField(Event, models.CASCADE, null=True)
class Container:
diff --git a/tests/queryset_pickle/tests.py b/tests/queryset_pickle/tests.py
index ebefb690df..27f509a9c1 100644
--- a/tests/queryset_pickle/tests.py
+++ b/tests/queryset_pickle/tests.py
@@ -55,6 +55,18 @@ class PickleabilityTestCase(TestCase):
klass = Event.MultipleObjectsReturned
self.assertIs(pickle.loads(pickle.dumps(klass)), klass)
+ def test_forward_relatedobjectdoesnotexist_class(self):
+ # ForwardManyToOneDescriptor
+ klass = Event.group.RelatedObjectDoesNotExist
+ self.assertIs(pickle.loads(pickle.dumps(klass)), klass)
+ # ForwardOneToOneDescriptor
+ klass = Happening.event.RelatedObjectDoesNotExist
+ self.assertIs(pickle.loads(pickle.dumps(klass)), klass)
+
+ def test_reverse_one_to_one_relatedobjectdoesnotexist_class(self):
+ klass = Event.happening.RelatedObjectDoesNotExist
+ self.assertIs(pickle.loads(pickle.dumps(klass)), klass)
+
def test_manager_pickle(self):
pickle.loads(pickle.dumps(Happening.objects))