diff options
| author | Keryn Knight <keryn@kerynknight.com> | 2021-08-01 12:13:35 +0100 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2021-11-08 08:44:12 +0100 |
| commit | 4f8c7fd9d91b35e2c2922de4bb50c8c8066cbbc6 (patch) | |
| tree | 8852a9111b57708f644f9a792e1cd06597cdb61a /django | |
| parent | ba9ced3e9a643a05bc521f0a2e6d02e3569de374 (diff) | |
Fixed #32980 -- Made models cache related managers.
Diffstat (limited to 'django')
| -rw-r--r-- | django/contrib/contenttypes/fields.py | 8 | ||||
| -rw-r--r-- | django/db/models/base.py | 27 | ||||
| -rw-r--r-- | django/db/models/fields/related_descriptors.py | 26 |
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'), |
