diff options
Diffstat (limited to 'django/db/models/fields')
| -rw-r--r-- | django/db/models/fields/related.py | 40 | ||||
| -rw-r--r-- | django/db/models/fields/related_descriptors.py | 2 | ||||
| -rw-r--r-- | django/db/models/fields/related_lookups.py | 10 | ||||
| -rw-r--r-- | django/db/models/fields/reverse_related.py | 25 |
4 files changed, 62 insertions, 15 deletions
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 2e8c87c1b1..8533803ba6 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -447,7 +447,7 @@ class RelatedField(FieldCacheMixin, Field): When filtering against this relation, return the field on the remote model against which the filtering should happen. """ - target_fields = self.get_path_info()[-1].target_fields + target_fields = self.path_infos[-1].target_fields if len(target_fields) > 1: raise exceptions.FieldError( "The relation has multiple target fields, but only single target field was asked for") @@ -499,6 +499,13 @@ class ForeignObject(RelatedField): self.to_fields = to_fields self.swappable = swappable + def __copy__(self): + obj = super().__copy__() + # Remove any cached PathInfo values. + obj.__dict__.pop('path_infos', None) + obj.__dict__.pop('reverse_path_infos', None) + return obj + def check(self, **kwargs): return [ *super().check(**kwargs), @@ -743,6 +750,10 @@ class ForeignObject(RelatedField): filtered_relation=filtered_relation, )] + @cached_property + def path_infos(self): + return self.get_path_info() + def get_reverse_path_info(self, filtered_relation=None): """Get path from the related model to this field's model.""" opts = self.model._meta @@ -757,6 +768,10 @@ class ForeignObject(RelatedField): filtered_relation=filtered_relation, )] + @cached_property + def reverse_path_infos(self): + return self.get_reverse_path_info() + @classmethod @functools.lru_cache(maxsize=None) def get_lookups(cls): @@ -1541,12 +1556,17 @@ class ManyToManyField(RelatedField): linkfield1 = int_model._meta.get_field(self.m2m_field_name()) linkfield2 = int_model._meta.get_field(self.m2m_reverse_field_name()) if direct: - join1infos = linkfield1.get_reverse_path_info() - join2infos = linkfield2.get_path_info(filtered_relation) + join1infos = linkfield1.reverse_path_infos + if filtered_relation: + join2infos = linkfield2.get_path_info(filtered_relation) + else: + join2infos = linkfield2.path_infos else: - join1infos = linkfield2.get_reverse_path_info() - join2infos = linkfield1.get_path_info(filtered_relation) - + join1infos = linkfield2.reverse_path_infos + if filtered_relation: + join2infos = linkfield1.get_path_info(filtered_relation) + else: + join2infos = linkfield1.path_infos # Get join infos between the last model of join 1 and the first model # of join 2. Assume the only reason these may differ is due to model # inheritance. @@ -1564,9 +1584,17 @@ class ManyToManyField(RelatedField): def get_path_info(self, filtered_relation=None): return self._get_path_info(direct=True, filtered_relation=filtered_relation) + @cached_property + def path_infos(self): + return self.get_path_info() + def get_reverse_path_info(self, filtered_relation=None): return self._get_path_info(direct=False, filtered_relation=filtered_relation) + @cached_property + def reverse_path_infos(self): + return self.get_reverse_path_info() + def _get_m2m_db_table(self, opts): """ Function that can be curried to provide the m2m table name for this diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py index cb77a0c476..ef546e844a 100644 --- a/django/db/models/fields/related_descriptors.py +++ b/django/db/models/fields/related_descriptors.py @@ -599,7 +599,7 @@ def create_reverse_many_to_one_manager(superclass, rel): # for related object id. rel_obj_id = tuple([ getattr(self.instance, target_field.attname) - for target_field in self.field.get_path_info()[-1].target_fields + for target_field in self.field.path_infos[-1].target_fields ]) else: rel_obj_id = getattr(self.instance, target_field.attname) diff --git a/django/db/models/fields/related_lookups.py b/django/db/models/fields/related_lookups.py index 34cca8ba5e..50f8b44158 100644 --- a/django/db/models/fields/related_lookups.py +++ b/django/db/models/fields/related_lookups.py @@ -30,7 +30,7 @@ def get_normalized_value(value, lhs): from django.db.models import Model if isinstance(value, Model): value_list = [] - sources = lhs.output_field.get_path_info()[-1].target_fields + sources = lhs.output_field.path_infos[-1].target_fields for source in sources: while not isinstance(value, source.model) and source.remote_field: source = source.remote_field.model._meta.get_field(source.remote_field.field_name) @@ -55,10 +55,10 @@ class RelatedIn(In): # ForeignKey to IntegerField given value 'abc'. The ForeignKey itself # doesn't have validation for non-integers, so we must run validation # using the target field. - if hasattr(self.lhs.output_field, 'get_path_info'): + if hasattr(self.lhs.output_field, 'path_infos'): # Run the target field's get_prep_value. We can safely assume there is # only one as we don't get to the direct value branch otherwise. - target_field = self.lhs.output_field.get_path_info()[-1].target_fields[-1] + target_field = self.lhs.output_field.path_infos[-1].target_fields[-1] self.rhs = [target_field.get_prep_value(v) for v in self.rhs] return super().get_prep_lookup() @@ -113,10 +113,10 @@ class RelatedLookupMixin: # ForeignKey to IntegerField given value 'abc'. The ForeignKey itself # doesn't have validation for non-integers, so we must run validation # using the target field. - if self.prepare_rhs and hasattr(self.lhs.output_field, 'get_path_info'): + if self.prepare_rhs and hasattr(self.lhs.output_field, 'path_infos'): # Get the target field. We can safely assume there is only one # as we don't get to the direct value branch otherwise. - target_field = self.lhs.output_field.get_path_info()[-1].target_fields[-1] + target_field = self.lhs.output_field.path_infos[-1].target_fields[-1] self.rhs = target_field.get_prep_value(self.rhs) return super().get_prep_lookup() diff --git a/django/db/models/fields/reverse_related.py b/django/db/models/fields/reverse_related.py index 65950590e2..6f0c788bbd 100644 --- a/django/db/models/fields/reverse_related.py +++ b/django/db/models/fields/reverse_related.py @@ -71,7 +71,7 @@ class ForeignObjectRel(FieldCacheMixin): When filtering against this relation, return the field on the remote model against which the filtering should happen. """ - target_fields = self.get_path_info()[-1].target_fields + target_fields = self.path_infos[-1].target_fields if len(target_fields) > 1: raise exceptions.FieldError("Can't use target_field for multicolumn relations.") return target_fields[0] @@ -138,6 +138,18 @@ class ForeignObjectRel(FieldCacheMixin): def __hash__(self): return hash(self.identity) + def __getstate__(self): + state = self.__dict__.copy() + # Delete the path_infos cached property because it can be recalculated + # at first invocation after deserialization. The attribute must be + # removed because subclasses like ManyToOneRel may have a PathInfo + # which contains an intermediate M2M table that's been dynamically + # created and doesn't exist in the .models module. + # This is a reverse relation, so there is no reverse_path_infos to + # delete. + state.pop('path_infos', None) + return state + def get_choices( self, include_blank=True, blank_choice=BLANK_CHOICE_DASH, limit_choices_to=None, ordering=(), @@ -195,7 +207,14 @@ class ForeignObjectRel(FieldCacheMixin): return opts.model_name + ('_set' if self.multiple else '') def get_path_info(self, filtered_relation=None): - return self.field.get_reverse_path_info(filtered_relation) + if filtered_relation: + return self.field.get_reverse_path_info(filtered_relation) + else: + return self.field.reverse_path_infos + + @cached_property + def path_infos(self): + return self.get_path_info() def get_cache_name(self): """ @@ -234,7 +253,7 @@ class ManyToOneRel(ForeignObjectRel): self.field_name = field_name def __getstate__(self): - state = self.__dict__.copy() + state = super().__getstate__() state.pop('related_model', None) return state |
