summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorKeryn Knight <keryn@kerynknight.com>2021-08-01 12:13:35 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-11-08 08:44:12 +0100
commit4f8c7fd9d91b35e2c2922de4bb50c8c8066cbbc6 (patch)
tree8852a9111b57708f644f9a792e1cd06597cdb61a /django
parentba9ced3e9a643a05bc521f0a2e6d02e3569de374 (diff)
Fixed #32980 -- Made models cache related managers.
Diffstat (limited to 'django')
-rw-r--r--django/contrib/contenttypes/fields.py8
-rw-r--r--django/db/models/base.py27
-rw-r--r--django/db/models/fields/related_descriptors.py26
3 files changed, 55 insertions, 6 deletions
diff --git a/django/contrib/contenttypes/fields.py b/django/contrib/contenttypes/fields.py
index b0b77b01d4..278b5bb585 100644
--- a/django/contrib/contenttypes/fields.py
+++ b/django/contrib/contenttypes/fields.py
@@ -505,6 +505,14 @@ class ReverseGenericManyToOneDescriptor(ReverseManyToOneDescriptor):
self.rel,
)
+ @cached_property
+ def related_manager_cache_key(self):
+ # By default, GenericRel instances will be marked as hidden unless
+ # related_query_name is given (their accessor name being "+" when
+ # hidden), which would cause multiple GenericRelations declared on a
+ # single model to collide, so always use the remote field's name.
+ return self.field.get_cache_name()
+
def create_generic_related_manager(superclass, rel):
"""
diff --git a/django/db/models/base.py b/django/db/models/base.py
index cc03ae1252..29bbde5e6b 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -382,11 +382,18 @@ class ModelBase(type):
return cls._meta.default_manager
-class ModelStateFieldsCacheDescriptor:
+class ModelStateCacheDescriptor:
+ """
+ Upon first access, replace itself with an empty dictionary on the instance.
+ """
+
+ def __set_name__(self, owner, name):
+ self.attribute_name = name
+
def __get__(self, instance, cls=None):
if instance is None:
return self
- res = instance.fields_cache = {}
+ res = instance.__dict__[self.attribute_name] = {}
return res
@@ -398,7 +405,20 @@ class ModelState:
# explicit (non-auto) PKs. This impacts validation only; it has no effect
# on the actual save.
adding = True
- fields_cache = ModelStateFieldsCacheDescriptor()
+ fields_cache = ModelStateCacheDescriptor()
+ related_managers_cache = ModelStateCacheDescriptor()
+
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ if 'fields_cache' in state:
+ state['fields_cache'] = self.fields_cache.copy()
+ # Manager instances stored in related_managers_cache won't necessarily
+ # be deserializable if they were dynamically created via an inner
+ # scope, e.g. create_forward_many_to_many_manager() and
+ # create_generic_related_manager().
+ if 'related_managers_cache' in state:
+ state['related_managers_cache'] = {}
+ return state
class Model(metaclass=ModelBase):
@@ -552,7 +572,6 @@ class Model(metaclass=ModelBase):
"""Hook to allow choosing the attributes to pickle."""
state = self.__dict__.copy()
state['_state'] = copy.copy(state['_state'])
- state['_state'].fields_cache = state['_state'].fields_cache.copy()
return state
def __setstate__(self, state):
diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py
index ef546e844a..d5aa968400 100644
--- a/django/db/models/fields/related_descriptors.py
+++ b/django/db/models/fields/related_descriptors.py
@@ -512,6 +512,14 @@ class ReverseManyToOneDescriptor:
self.field = rel.field
@cached_property
+ def related_manager_cache_key(self):
+ # Being able to access the manager instance precludes it from being
+ # hidden. The rel's accessor name is used to allow multiple managers
+ # to the same model to coexist. e.g. post.attached_comment_set and
+ # post.attached_link_set are separately cached.
+ return self.rel.get_cache_name()
+
+ @cached_property
def related_manager_cls(self):
related_model = self.rel.related_model
@@ -532,8 +540,11 @@ class ReverseManyToOneDescriptor:
"""
if instance is None:
return self
-
- return self.related_manager_cls(instance)
+ key = self.related_manager_cache_key
+ instance_cache = instance._state.related_managers_cache
+ if key not in instance_cache:
+ instance_cache[key] = self.related_manager_cls(instance)
+ return instance_cache[key]
def _get_set_deprecation_msg_params(self):
return (
@@ -797,6 +808,17 @@ class ManyToManyDescriptor(ReverseManyToOneDescriptor):
reverse=self.reverse,
)
+ @cached_property
+ def related_manager_cache_key(self):
+ if self.reverse:
+ # Symmetrical M2Ms won't have an accessor name, but should never
+ # end up in the reverse branch anyway, as the related_name ends up
+ # being hidden, and no public manager is created.
+ return self.rel.get_cache_name()
+ else:
+ # For forward managers, defer to the field name.
+ return self.field.get_cache_name()
+
def _get_set_deprecation_msg_params(self):
return (
'%s side of a many-to-many set' % ('reverse' if self.reverse else 'forward'),