mirror of
https://github.com/django/django.git
synced 2025-06-05 03:29:12 +00:00
[1.5.x] Fixed prefetch_related + pickle regressions
There were a couple of regressions related to field pickling. The regressions were introduced by QuerySet._known_related_objects caching. The regressions aren't present in master, the fix was likely in f403653cf146384946e5c879ad2a351768ebc226. Fixed #20157, fixed #20257. Also made QuerySets with model=None picklable.
This commit is contained in:
parent
63cab03f6d
commit
bac187c0d8
@ -68,11 +68,27 @@ class QuerySet(object):
|
|||||||
"""
|
"""
|
||||||
# Force the cache to be fully populated.
|
# Force the cache to be fully populated.
|
||||||
len(self)
|
len(self)
|
||||||
|
|
||||||
obj_dict = self.__dict__.copy()
|
obj_dict = self.__dict__.copy()
|
||||||
obj_dict['_iter'] = None
|
obj_dict['_iter'] = None
|
||||||
|
obj_dict['_known_related_objects'] = dict(
|
||||||
|
(field.name, val) for field, val in self._known_related_objects.items()
|
||||||
|
)
|
||||||
return obj_dict
|
return obj_dict
|
||||||
|
|
||||||
|
def __setstate__(self, obj_dict):
|
||||||
|
model = obj_dict['model']
|
||||||
|
if model is None:
|
||||||
|
# if model is None, then self should be emptyqs and the related
|
||||||
|
# objects do not matter.
|
||||||
|
self._known_related_objects = {}
|
||||||
|
else:
|
||||||
|
opts = model._meta
|
||||||
|
self._known_related_objects = dict(
|
||||||
|
(opts.get_field(field.name if hasattr(field, 'name') else field), val)
|
||||||
|
for field, val in obj_dict['_known_related_objects'].items()
|
||||||
|
)
|
||||||
|
self.__dict__.update(obj_dict)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
data = list(self[:REPR_OUTPUT_SIZE + 1])
|
data = list(self[:REPR_OUTPUT_SIZE + 1])
|
||||||
if len(data) > REPR_OUTPUT_SIZE:
|
if len(data) > REPR_OUTPUT_SIZE:
|
||||||
|
@ -208,12 +208,17 @@ class Query(object):
|
|||||||
Unpickling support.
|
Unpickling support.
|
||||||
"""
|
"""
|
||||||
# Rebuild list of field instances
|
# Rebuild list of field instances
|
||||||
opts = obj_dict['model']._meta
|
model = obj_dict['model']
|
||||||
obj_dict['select_fields'] = [
|
if model is None:
|
||||||
name is not None and opts.get_field(name) or None
|
# if model is None the queryset should be emptyqs. So the
|
||||||
for name in obj_dict['select_fields']
|
# select_fields do not matter.
|
||||||
]
|
obj_dict['select_fields'] = []
|
||||||
|
else:
|
||||||
|
opts = model._meta
|
||||||
|
obj_dict['select_fields'] = [
|
||||||
|
name is not None and opts.get_field(name) or None
|
||||||
|
for name in obj_dict['select_fields']
|
||||||
|
]
|
||||||
self.__dict__.update(obj_dict)
|
self.__dict__.update(obj_dict)
|
||||||
|
|
||||||
def prepare(self):
|
def prepare(self):
|
||||||
|
@ -36,3 +36,16 @@ class Happening(models.Model):
|
|||||||
number2 = models.IntegerField(blank=True, default=Numbers.get_static_number)
|
number2 = models.IntegerField(blank=True, default=Numbers.get_static_number)
|
||||||
number3 = models.IntegerField(blank=True, default=Numbers.get_class_number)
|
number3 = models.IntegerField(blank=True, default=Numbers.get_class_number)
|
||||||
number4 = models.IntegerField(blank=True, default=nn.get_member_number)
|
number4 = models.IntegerField(blank=True, default=nn.get_member_number)
|
||||||
|
|
||||||
|
class Person(models.Model):
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
|
||||||
|
class SocialProfile(models.Model):
|
||||||
|
person = models.ForeignKey(Person)
|
||||||
|
friends = models.ManyToManyField('self')
|
||||||
|
|
||||||
|
class Post(models.Model):
|
||||||
|
post_date = models.DateTimeField(default=datetime.datetime.now)
|
||||||
|
|
||||||
|
class Material(models.Model):
|
||||||
|
post = models.ForeignKey(Post, related_name='materials')
|
||||||
|
@ -5,7 +5,8 @@ import datetime
|
|||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from .models import Group, Event, Happening
|
from .models import Group, Event, Happening, Person, Post
|
||||||
|
from django.contrib.auth.models import AnonymousUser
|
||||||
|
|
||||||
|
|
||||||
class PickleabilityTestCase(TestCase):
|
class PickleabilityTestCase(TestCase):
|
||||||
@ -46,3 +47,29 @@ class PickleabilityTestCase(TestCase):
|
|||||||
# can't just use assertEqual(original, unpickled)
|
# can't just use assertEqual(original, unpickled)
|
||||||
self.assertEqual(original.__class__, unpickled.__class__)
|
self.assertEqual(original.__class__, unpickled.__class__)
|
||||||
self.assertEqual(original.args, unpickled.args)
|
self.assertEqual(original.args, unpickled.args)
|
||||||
|
|
||||||
|
def test_pickle_m2m_prefetch_related(self):
|
||||||
|
bob = Person(name="Bob")
|
||||||
|
bob.save()
|
||||||
|
people = Person.objects.prefetch_related('socialprofile_set')
|
||||||
|
dumped = pickle.dumps(people)
|
||||||
|
people = pickle.loads(dumped)
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
people, [bob], lambda x: x)
|
||||||
|
|
||||||
|
def test_pickle_field_default_prefetch_related(self):
|
||||||
|
p1 = Post.objects.create()
|
||||||
|
posts = Post.objects.prefetch_related('materials')
|
||||||
|
dumped = pickle.dumps(posts)
|
||||||
|
posts = pickle.loads(dumped)
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
posts, [p1], lambda x: x)
|
||||||
|
|
||||||
|
def test_pickle_emptyqs(self):
|
||||||
|
u = AnonymousUser()
|
||||||
|
# Use AnonymousUser, as AnonymousUser.groups has qs.model = None
|
||||||
|
empty = u.groups.all()
|
||||||
|
dumped = pickle.dumps(empty)
|
||||||
|
empty = pickle.loads(dumped)
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
empty, [])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user