mirror of https://github.com/django/django.git
Fixed #14270 - related manager classes should be cached
Thanks to Alex Gaynor for the report and initial patch, and mrmachine for more work on it. git-svn-id: http://code.djangoproject.com/svn/django/trunk@16916 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
2eadc418af
commit
5009e45dfe
|
@ -9,7 +9,7 @@ from django.db.models.query_utils import QueryWrapper
|
||||||
from django.db.models.deletion import CASCADE
|
from django.db.models.deletion import CASCADE
|
||||||
from django.utils.encoding import smart_unicode
|
from django.utils.encoding import smart_unicode
|
||||||
from django.utils.translation import ugettext_lazy as _, string_concat
|
from django.utils.translation import ugettext_lazy as _, string_concat
|
||||||
from django.utils.functional import curry
|
from django.utils.functional import curry, memoize, cached_property
|
||||||
from django.core import exceptions
|
from django.core import exceptions
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
|
@ -386,8 +386,8 @@ class ForeignRelatedObjectsDescriptor(object):
|
||||||
if instance is None:
|
if instance is None:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
return self.create_manager(instance,
|
manager = self.create_manager(self.related.model._default_manager.__class__)
|
||||||
self.related.model._default_manager.__class__)
|
return manager(instance)
|
||||||
|
|
||||||
def __set__(self, instance, value):
|
def __set__(self, instance, value):
|
||||||
if instance is None:
|
if instance is None:
|
||||||
|
@ -406,22 +406,30 @@ class ForeignRelatedObjectsDescriptor(object):
|
||||||
than the default manager, as returned by __get__). Used by
|
than the default manager, as returned by __get__). Used by
|
||||||
Model.delete().
|
Model.delete().
|
||||||
"""
|
"""
|
||||||
return self.create_manager(instance,
|
manager = self.create_manager(self.related.model._base_manager.__class__)
|
||||||
self.related.model._base_manager.__class__)
|
return manager(instance)
|
||||||
|
|
||||||
def create_manager(self, instance, superclass):
|
def create_manager(self, superclass):
|
||||||
"""
|
"""
|
||||||
Creates the managers used by other methods (__get__() and delete()).
|
Creates the managers used by other methods (__get__() and delete()).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# We use closures for these values so that we only need to memoize this
|
||||||
|
# function on the one argument of 'superclass', and the two places that
|
||||||
|
# call create_manager simply need to pass instance to the manager
|
||||||
|
# __init__
|
||||||
rel_field = self.related.field
|
rel_field = self.related.field
|
||||||
|
rel_model = self.related.model
|
||||||
|
attname = rel_field.rel.get_related_field().attname
|
||||||
|
|
||||||
class RelatedManager(superclass):
|
class RelatedManager(superclass):
|
||||||
def __init__(self, model=None, core_filters=None, instance=None,
|
def __init__(self, instance):
|
||||||
rel_field=None):
|
|
||||||
super(RelatedManager, self).__init__()
|
super(RelatedManager, self).__init__()
|
||||||
self.model = model
|
|
||||||
self.core_filters = core_filters
|
|
||||||
self.instance = instance
|
self.instance = instance
|
||||||
self.rel_field = rel_field
|
self.core_filters = {
|
||||||
|
'%s__%s' % (rel_field.name, attname): getattr(instance, attname)
|
||||||
|
}
|
||||||
|
self.model = rel_model
|
||||||
|
|
||||||
def get_query_set(self):
|
def get_query_set(self):
|
||||||
db = self._db or router.db_for_read(self.model, instance=self.instance)
|
db = self._db or router.db_for_read(self.model, instance=self.instance)
|
||||||
|
@ -431,12 +439,12 @@ class ForeignRelatedObjectsDescriptor(object):
|
||||||
for obj in objs:
|
for obj in objs:
|
||||||
if not isinstance(obj, self.model):
|
if not isinstance(obj, self.model):
|
||||||
raise TypeError("'%s' instance expected" % self.model._meta.object_name)
|
raise TypeError("'%s' instance expected" % self.model._meta.object_name)
|
||||||
setattr(obj, self.rel_field.name, self.instance)
|
setattr(obj, rel_field.name, self.instance)
|
||||||
obj.save()
|
obj.save()
|
||||||
add.alters_data = True
|
add.alters_data = True
|
||||||
|
|
||||||
def create(self, **kwargs):
|
def create(self, **kwargs):
|
||||||
kwargs[self.rel_field.name] = self.instance
|
kwargs[rel_field.name] = self.instance
|
||||||
db = router.db_for_write(self.model, instance=self.instance)
|
db = router.db_for_write(self.model, instance=self.instance)
|
||||||
return super(RelatedManager, self.db_manager(db)).create(**kwargs)
|
return super(RelatedManager, self.db_manager(db)).create(**kwargs)
|
||||||
create.alters_data = True
|
create.alters_data = True
|
||||||
|
@ -444,7 +452,7 @@ class ForeignRelatedObjectsDescriptor(object):
|
||||||
def get_or_create(self, **kwargs):
|
def get_or_create(self, **kwargs):
|
||||||
# Update kwargs with the related object that this
|
# Update kwargs with the related object that this
|
||||||
# ForeignRelatedObjectsDescriptor knows about.
|
# ForeignRelatedObjectsDescriptor knows about.
|
||||||
kwargs[self.rel_field.name] = self.instance
|
kwargs[rel_field.name] = self.instance
|
||||||
db = router.db_for_write(self.model, instance=self.instance)
|
db = router.db_for_write(self.model, instance=self.instance)
|
||||||
return super(RelatedManager, self.db_manager(db)).get_or_create(**kwargs)
|
return super(RelatedManager, self.db_manager(db)).get_or_create(**kwargs)
|
||||||
get_or_create.alters_data = True
|
get_or_create.alters_data = True
|
||||||
|
@ -452,27 +460,22 @@ class ForeignRelatedObjectsDescriptor(object):
|
||||||
# remove() and clear() are only provided if the ForeignKey can have a value of null.
|
# remove() and clear() are only provided if the ForeignKey can have a value of null.
|
||||||
if rel_field.null:
|
if rel_field.null:
|
||||||
def remove(self, *objs):
|
def remove(self, *objs):
|
||||||
val = getattr(self.instance, self.rel_field.rel.get_related_field().attname)
|
val = getattr(self.instance, attname)
|
||||||
for obj in objs:
|
for obj in objs:
|
||||||
# Is obj actually part of this descriptor set?
|
# Is obj actually part of this descriptor set?
|
||||||
if getattr(obj, self.rel_field.attname) == val:
|
if getattr(obj, rel_field.attname) == val:
|
||||||
setattr(obj, self.rel_field.name, None)
|
setattr(obj, rel_field.name, None)
|
||||||
obj.save()
|
obj.save()
|
||||||
else:
|
else:
|
||||||
raise self.rel_field.rel.to.DoesNotExist("%r is not related to %r." % (obj, self.instance))
|
raise rel_field.rel.to.DoesNotExist("%r is not related to %r." % (obj, self.instance))
|
||||||
remove.alters_data = True
|
remove.alters_data = True
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.update(**{self.rel_field.name: None})
|
self.update(**{rel_field.name: None})
|
||||||
clear.alters_data = True
|
clear.alters_data = True
|
||||||
|
|
||||||
attname = rel_field.rel.get_related_field().name
|
return RelatedManager
|
||||||
return RelatedManager(model=self.related.model,
|
create_manager = memoize(create_manager, {}, 2)
|
||||||
core_filters = {'%s__%s' % (rel_field.name, attname):
|
|
||||||
getattr(instance, attname)},
|
|
||||||
instance=instance,
|
|
||||||
rel_field=rel_field,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def create_many_related_manager(superclass, rel):
|
def create_many_related_manager(superclass, rel):
|
||||||
|
@ -663,17 +666,22 @@ class ManyRelatedObjectsDescriptor(object):
|
||||||
def __init__(self, related):
|
def __init__(self, related):
|
||||||
self.related = related # RelatedObject instance
|
self.related = related # RelatedObject instance
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def related_manager_cls(self):
|
||||||
|
# Dynamically create a class that subclasses the related
|
||||||
|
# model's default manager.
|
||||||
|
return create_many_related_manager(
|
||||||
|
self.related.model._default_manager.__class__,
|
||||||
|
self.related.field.rel
|
||||||
|
)
|
||||||
|
|
||||||
def __get__(self, instance, instance_type=None):
|
def __get__(self, instance, instance_type=None):
|
||||||
if instance is None:
|
if instance is None:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
# Dynamically create a class that subclasses the related
|
|
||||||
# model's default manager.
|
|
||||||
rel_model = self.related.model
|
rel_model = self.related.model
|
||||||
superclass = rel_model._default_manager.__class__
|
|
||||||
RelatedManager = create_many_related_manager(superclass, self.related.field.rel)
|
|
||||||
|
|
||||||
manager = RelatedManager(
|
manager = self.related_manager_cls(
|
||||||
model=rel_model,
|
model=rel_model,
|
||||||
core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()},
|
core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()},
|
||||||
instance=instance,
|
instance=instance,
|
||||||
|
@ -716,18 +724,21 @@ class ReverseManyRelatedObjectsDescriptor(object):
|
||||||
# a property to ensure that the fully resolved value is returned.
|
# a property to ensure that the fully resolved value is returned.
|
||||||
return self.field.rel.through
|
return self.field.rel.through
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def related_manager_cls(self):
|
||||||
|
# Dynamically create a class that subclasses the related model's
|
||||||
|
# default manager.
|
||||||
|
return create_many_related_manager(
|
||||||
|
self.field.rel.to._default_manager.__class__,
|
||||||
|
self.field.rel
|
||||||
|
)
|
||||||
|
|
||||||
def __get__(self, instance, instance_type=None):
|
def __get__(self, instance, instance_type=None):
|
||||||
if instance is None:
|
if instance is None:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
# Dynamically create a class that subclasses the related
|
manager = self.related_manager_cls(
|
||||||
# model's default manager.
|
model=self.field.rel.to,
|
||||||
rel_model=self.field.rel.to
|
|
||||||
superclass = rel_model._default_manager.__class__
|
|
||||||
RelatedManager = create_many_related_manager(superclass, self.field.rel)
|
|
||||||
|
|
||||||
manager = RelatedManager(
|
|
||||||
model=rel_model,
|
|
||||||
core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()},
|
core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()},
|
||||||
instance=instance,
|
instance=instance,
|
||||||
symmetrical=self.field.rel.symmetrical,
|
symmetrical=self.field.rel.symmetrical,
|
||||||
|
|
|
@ -28,6 +28,18 @@ def memoize(func, cache, num_args):
|
||||||
return result
|
return result
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
class cached_property(object):
|
||||||
|
"""
|
||||||
|
Decorator that creates converts a method with a single
|
||||||
|
self argument into a property cached on the instance.
|
||||||
|
"""
|
||||||
|
def __init__(self, func):
|
||||||
|
self.func = func
|
||||||
|
|
||||||
|
def __get__(self, instance, type):
|
||||||
|
res = instance.__dict__[self.func.__name__] = self.func(instance)
|
||||||
|
return res
|
||||||
|
|
||||||
class Promise(object):
|
class Promise(object):
|
||||||
"""
|
"""
|
||||||
This is just a base class for the proxy class created in
|
This is just a base class for the proxy class created in
|
||||||
|
@ -288,4 +300,4 @@ def partition(predicate, values):
|
||||||
results = ([], [])
|
results = ([], [])
|
||||||
for item in values:
|
for item in values:
|
||||||
results[predicate(item)].append(item)
|
results[predicate(item)].append(item)
|
||||||
return results
|
return results
|
||||||
|
|
|
@ -399,3 +399,13 @@ class ManyToOneTests(TestCase):
|
||||||
self.assertEqual(repr(a3),
|
self.assertEqual(repr(a3),
|
||||||
repr(Article.objects.get(reporter_id=self.r2.id,
|
repr(Article.objects.get(reporter_id=self.r2.id,
|
||||||
pub_date=datetime(2011, 5, 7))))
|
pub_date=datetime(2011, 5, 7))))
|
||||||
|
|
||||||
|
def test_manager_class_caching(self):
|
||||||
|
r1 = Reporter.objects.create(first_name='Mike')
|
||||||
|
r2 = Reporter.objects.create(first_name='John')
|
||||||
|
|
||||||
|
# Same twice
|
||||||
|
self.assertTrue(r1.article_set.__class__ is r1.article_set.__class__)
|
||||||
|
|
||||||
|
# Same as each other
|
||||||
|
self.assertTrue(r1.article_set.__class__ is r2.article_set.__class__)
|
||||||
|
|
|
@ -73,3 +73,17 @@ class M2MRegressionTests(TestCase):
|
||||||
|
|
||||||
self.assertQuerysetEqual(c1.tags.all(), ["<Tag: t1>", "<Tag: t2>"])
|
self.assertQuerysetEqual(c1.tags.all(), ["<Tag: t1>", "<Tag: t2>"])
|
||||||
self.assertQuerysetEqual(t1.tag_collections.all(), ["<TagCollection: c1>"])
|
self.assertQuerysetEqual(t1.tag_collections.all(), ["<TagCollection: c1>"])
|
||||||
|
|
||||||
|
def test_manager_class_caching(self):
|
||||||
|
e1 = Entry.objects.create()
|
||||||
|
e2 = Entry.objects.create()
|
||||||
|
t1 = Tag.objects.create()
|
||||||
|
t2 = Tag.objects.create()
|
||||||
|
|
||||||
|
# Get same manager twice in a row:
|
||||||
|
self.assertTrue(t1.entry_set.__class__ is t1.entry_set.__class__)
|
||||||
|
self.assertTrue(e1.topics.__class__ is e1.topics.__class__)
|
||||||
|
|
||||||
|
# Get same manager for different instances
|
||||||
|
self.assertTrue(e1.topics.__class__ is e2.topics.__class__)
|
||||||
|
self.assertTrue(t1.entry_set.__class__ is t2.entry_set.__class__)
|
||||||
|
|
Loading…
Reference in New Issue