Loading django/contrib/contenttypes/generic.py +11 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ from django.db import connection from django.db.models import signals from django.db import models, router, DEFAULT_DB_ALIAS from django.db.models.fields.related import RelatedField, Field, ManyToManyRel from django.db.models.related import PathInfo from django.forms import ModelForm from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets Loading Loading @@ -160,6 +161,16 @@ class GenericRelation(RelatedField, Field): kwargs['serialize'] = False Field.__init__(self, **kwargs) def get_path_info(self): from_field = self.model._meta.pk opts = self.rel.to._meta target = opts.get_field_by_name(self.object_id_field_name)[0] # Note that we are using different field for the join_field # than from_field or to_field. This is a hack, but we need the # GenericRelation to generate the extra SQL. return ([PathInfo(from_field, target, self.model._meta, opts, self, True, False)], opts, target, self) def get_choices_default(self): return Field.get_choices(self, include_blank=False) Loading django/db/models/fields/related.py +50 −2 Original line number Diff line number Diff line Loading @@ -5,7 +5,7 @@ from django.db.backends import util from django.db.models import signals, get_model from django.db.models.fields import (AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist) from django.db.models.related import RelatedObject from django.db.models.related import RelatedObject, PathInfo from django.db.models.query import QuerySet from django.db.models.query_utils import QueryWrapper from django.db.models.deletion import CASCADE Loading @@ -16,7 +16,6 @@ from django.utils.functional import curry, cached_property from django.core import exceptions from django import forms RECURSIVE_RELATIONSHIP_CONSTANT = 'self' pending_lookups = {} Loading Loading @@ -1004,6 +1003,31 @@ class ForeignKey(RelatedField, Field): ) Field.__init__(self, **kwargs) def get_path_info(self): """ Get path from this field to the related model. """ opts = self.rel.to._meta target = self.rel.get_related_field() from_opts = self.model._meta return [PathInfo(self, target, from_opts, opts, self, False, True)], opts, target, self def get_reverse_path_info(self): """ Get path from the related model to this field's model. """ opts = self.model._meta from_field = self.rel.get_related_field() from_opts = from_field.model._meta pathinfos = [PathInfo(from_field, self, from_opts, opts, self, not self.unique, False)] if from_field.model is self.model: # Recursive foreign key to self. target = opts.get_field_by_name( self.rel.field_name)[0] else: target = opts.pk return pathinfos, opts, target, self def validate(self, value, model_instance): if self.rel.parent_link: return Loading Loading @@ -1198,6 +1222,30 @@ class ManyToManyField(RelatedField, Field): msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.') self.help_text = string_concat(self.help_text, ' ', msg) def _get_path_info(self, direct=False): """ Called by both direct an indirect m2m traversal. """ pathinfos = [] int_model = self.rel.through linkfield1 = int_model._meta.get_field_by_name(self.m2m_field_name())[0] linkfield2 = int_model._meta.get_field_by_name(self.m2m_reverse_field_name())[0] if direct: join1infos, _, _, _ = linkfield1.get_reverse_path_info() join2infos, opts, target, final_field = linkfield2.get_path_info() else: join1infos, _, _, _ = linkfield2.get_reverse_path_info() join2infos, opts, target, final_field = linkfield1.get_path_info() pathinfos.extend(join1infos) pathinfos.extend(join2infos) return pathinfos, opts, target, final_field def get_path_info(self): return self._get_path_info(direct=True) def get_reverse_path_info(self): return self._get_path_info(direct=False) def get_choices_default(self): return Field.get_choices(self, include_blank=False) Loading django/db/models/related.py +12 −0 Original line number Diff line number Diff line from collections import namedtuple from django.utils.encoding import smart_text from django.db.models.fields import BLANK_CHOICE_DASH # PathInfo is used when converting lookups (fk__somecol). The contents # describe the relation in Model terms (model Options and Fields for both # sides of the relation. The join_field is the field backing the relation. PathInfo = namedtuple('PathInfo', 'from_field to_field from_opts to_opts join_field ' 'm2m direct') class BoundRelatedObject(object): def __init__(self, related_object, field_mapping, original): self.relation = related_object Loading Loading @@ -67,3 +76,6 @@ class RelatedObject(object): def get_cache_name(self): return "_%s_cache" % self.get_accessor_name() def get_path_info(self): return self.field.get_reverse_path_info() django/db/models/sql/constants.py +0 −6 Original line number Diff line number Diff line Loading @@ -26,12 +26,6 @@ JoinInfo = namedtuple('JoinInfo', 'table_name rhs_alias join_type lhs_alias ' 'lhs_join_col rhs_join_col nullable join_field') # PathInfo is used when converting lookups (fk__somecol). The contents # describe the join in Model terms (model Options and Fields for both # sides of the join. The rel_field is the field we are joining along. PathInfo = namedtuple('PathInfo', 'from_field to_field from_opts to_opts join_field') # Pairs of column clauses to select, and (possibly None) field for the clause. SelectInfo = namedtuple('SelectInfo', 'col field') Loading django/db/models/sql/query.py +18 −92 Original line number Diff line number Diff line Loading @@ -18,9 +18,10 @@ from django.db.models.constants import LOOKUP_SEP from django.db.models.expressions import ExpressionNode from django.db.models.fields import FieldDoesNotExist from django.db.models.loading import get_model from django.db.models.related import PathInfo from django.db.models.sql import aggregates as base_aggregates_module from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE, ORDER_PATTERN, JoinInfo, SelectInfo, PathInfo) ORDER_PATTERN, JoinInfo, SelectInfo) from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin from django.db.models.sql.expressions import SQLEvaluator from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode, Loading Loading @@ -1294,7 +1295,6 @@ class Query(object): contain the same value as the final field). """ path = [] multijoin_pos = None for pos, name in enumerate(names): if name == 'pk': name = opts.pk.name Loading Loading @@ -1328,92 +1328,19 @@ class Query(object): target = final_field.rel.get_related_field() opts = int_model._meta path.append(PathInfo(final_field, target, final_field.model._meta, opts, final_field)) # We have five different cases to solve: foreign keys, reverse # foreign keys, m2m fields (also reverse) and non-relational # fields. We are mostly just using the related field API to # fetch the from and to fields. The m2m fields are handled as # two foreign keys, first one reverse, the second one direct. if direct and not field.rel and not m2m: opts, final_field, False, True)) if hasattr(field, 'get_path_info'): pathinfos, opts, target, final_field = field.get_path_info() path.extend(pathinfos) else: # Local non-relational field. final_field = target = field break elif direct and not m2m: # Foreign Key opts = field.rel.to._meta target = field.rel.get_related_field() final_field = field from_opts = field.model._meta path.append(PathInfo(field, target, from_opts, opts, field)) elif not direct and not m2m: # Revere foreign key final_field = to_field = field.field opts = to_field.model._meta from_field = to_field.rel.get_related_field() from_opts = from_field.model._meta path.append( PathInfo(from_field, to_field, from_opts, opts, to_field)) if from_field.model is to_field.model: # Recursive foreign key to self. target = opts.get_field_by_name( field.field.rel.field_name)[0] else: target = opts.pk elif direct and m2m: if not field.rel.through: # Gotcha! This is just a fake m2m field - a generic relation # field). from_field = opts.pk opts = field.rel.to._meta target = opts.get_field_by_name(field.object_id_field_name)[0] final_field = field # Note that we are using different field for the join_field # than from_field or to_field. This is a hack, but we need the # GenericRelation to generate the extra SQL. path.append(PathInfo(from_field, target, field.model._meta, opts, field)) else: # m2m field. We are travelling first to the m2m table along a # reverse relation, then from m2m table to the target table. from_field1 = opts.get_field_by_name( field.m2m_target_field_name())[0] opts = field.rel.through._meta to_field1 = opts.get_field_by_name(field.m2m_field_name())[0] path.append( PathInfo(from_field1, to_field1, from_field1.model._meta, opts, to_field1)) final_field = from_field2 = opts.get_field_by_name( field.m2m_reverse_field_name())[0] opts = field.rel.to._meta target = to_field2 = opts.get_field_by_name( field.m2m_reverse_target_field_name())[0] path.append( PathInfo(from_field2, to_field2, from_field2.model._meta, opts, from_field2)) elif not direct and m2m: # This one is just like above, except we are travelling the # fields in opposite direction. field = field.field from_field1 = opts.get_field_by_name( field.m2m_reverse_target_field_name())[0] int_opts = field.rel.through._meta to_field1 = int_opts.get_field_by_name( field.m2m_reverse_field_name())[0] path.append( PathInfo(from_field1, to_field1, from_field1.model._meta, int_opts, to_field1)) final_field = from_field2 = int_opts.get_field_by_name( field.m2m_field_name())[0] opts = field.opts target = to_field2 = opts.get_field_by_name( field.m2m_target_field_name())[0] path.append(PathInfo(from_field2, to_field2, from_field2.model._meta, opts, from_field2)) if m2m and multijoin_pos is None: multijoin_pos = pos if not direct and not path[-1].to_field.unique and multijoin_pos is None: multijoin_pos = pos multijoin_pos = None for m2mpos, pathinfo in enumerate(path): if pathinfo.m2m: multijoin_pos = m2mpos break if pos != len(names) - 1: if pos == len(names) - 2: Loading Loading @@ -1463,16 +1390,15 @@ class Query(object): # joins at this stage - we will need the information about join type # of the trimmed joins. for pos, join in enumerate(path): from_field, to_field, from_opts, opts, join_field = join direct = join_field == from_field if direct: nullable = self.is_nullable(from_field) opts = join.to_opts if join.direct: nullable = self.is_nullable(join.from_field) else: nullable = True connection = alias, opts.db_table, from_field.column, to_field.column reuse = None if direct or to_field.unique else can_reuse connection = alias, opts.db_table, join.from_field.column, join.to_field.column reuse = can_reuse if join.m2m else None alias = self.join(connection, reuse=reuse, nullable=nullable, join_field=join_field) nullable=nullable, join_field=join.join_field) joins.append(alias) return final_field, target, opts, joins, path Loading Loading
django/contrib/contenttypes/generic.py +11 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ from django.db import connection from django.db.models import signals from django.db import models, router, DEFAULT_DB_ALIAS from django.db.models.fields.related import RelatedField, Field, ManyToManyRel from django.db.models.related import PathInfo from django.forms import ModelForm from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets Loading Loading @@ -160,6 +161,16 @@ class GenericRelation(RelatedField, Field): kwargs['serialize'] = False Field.__init__(self, **kwargs) def get_path_info(self): from_field = self.model._meta.pk opts = self.rel.to._meta target = opts.get_field_by_name(self.object_id_field_name)[0] # Note that we are using different field for the join_field # than from_field or to_field. This is a hack, but we need the # GenericRelation to generate the extra SQL. return ([PathInfo(from_field, target, self.model._meta, opts, self, True, False)], opts, target, self) def get_choices_default(self): return Field.get_choices(self, include_blank=False) Loading
django/db/models/fields/related.py +50 −2 Original line number Diff line number Diff line Loading @@ -5,7 +5,7 @@ from django.db.backends import util from django.db.models import signals, get_model from django.db.models.fields import (AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist) from django.db.models.related import RelatedObject from django.db.models.related import RelatedObject, PathInfo from django.db.models.query import QuerySet from django.db.models.query_utils import QueryWrapper from django.db.models.deletion import CASCADE Loading @@ -16,7 +16,6 @@ from django.utils.functional import curry, cached_property from django.core import exceptions from django import forms RECURSIVE_RELATIONSHIP_CONSTANT = 'self' pending_lookups = {} Loading Loading @@ -1004,6 +1003,31 @@ class ForeignKey(RelatedField, Field): ) Field.__init__(self, **kwargs) def get_path_info(self): """ Get path from this field to the related model. """ opts = self.rel.to._meta target = self.rel.get_related_field() from_opts = self.model._meta return [PathInfo(self, target, from_opts, opts, self, False, True)], opts, target, self def get_reverse_path_info(self): """ Get path from the related model to this field's model. """ opts = self.model._meta from_field = self.rel.get_related_field() from_opts = from_field.model._meta pathinfos = [PathInfo(from_field, self, from_opts, opts, self, not self.unique, False)] if from_field.model is self.model: # Recursive foreign key to self. target = opts.get_field_by_name( self.rel.field_name)[0] else: target = opts.pk return pathinfos, opts, target, self def validate(self, value, model_instance): if self.rel.parent_link: return Loading Loading @@ -1198,6 +1222,30 @@ class ManyToManyField(RelatedField, Field): msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.') self.help_text = string_concat(self.help_text, ' ', msg) def _get_path_info(self, direct=False): """ Called by both direct an indirect m2m traversal. """ pathinfos = [] int_model = self.rel.through linkfield1 = int_model._meta.get_field_by_name(self.m2m_field_name())[0] linkfield2 = int_model._meta.get_field_by_name(self.m2m_reverse_field_name())[0] if direct: join1infos, _, _, _ = linkfield1.get_reverse_path_info() join2infos, opts, target, final_field = linkfield2.get_path_info() else: join1infos, _, _, _ = linkfield2.get_reverse_path_info() join2infos, opts, target, final_field = linkfield1.get_path_info() pathinfos.extend(join1infos) pathinfos.extend(join2infos) return pathinfos, opts, target, final_field def get_path_info(self): return self._get_path_info(direct=True) def get_reverse_path_info(self): return self._get_path_info(direct=False) def get_choices_default(self): return Field.get_choices(self, include_blank=False) Loading
django/db/models/related.py +12 −0 Original line number Diff line number Diff line from collections import namedtuple from django.utils.encoding import smart_text from django.db.models.fields import BLANK_CHOICE_DASH # PathInfo is used when converting lookups (fk__somecol). The contents # describe the relation in Model terms (model Options and Fields for both # sides of the relation. The join_field is the field backing the relation. PathInfo = namedtuple('PathInfo', 'from_field to_field from_opts to_opts join_field ' 'm2m direct') class BoundRelatedObject(object): def __init__(self, related_object, field_mapping, original): self.relation = related_object Loading Loading @@ -67,3 +76,6 @@ class RelatedObject(object): def get_cache_name(self): return "_%s_cache" % self.get_accessor_name() def get_path_info(self): return self.field.get_reverse_path_info()
django/db/models/sql/constants.py +0 −6 Original line number Diff line number Diff line Loading @@ -26,12 +26,6 @@ JoinInfo = namedtuple('JoinInfo', 'table_name rhs_alias join_type lhs_alias ' 'lhs_join_col rhs_join_col nullable join_field') # PathInfo is used when converting lookups (fk__somecol). The contents # describe the join in Model terms (model Options and Fields for both # sides of the join. The rel_field is the field we are joining along. PathInfo = namedtuple('PathInfo', 'from_field to_field from_opts to_opts join_field') # Pairs of column clauses to select, and (possibly None) field for the clause. SelectInfo = namedtuple('SelectInfo', 'col field') Loading
django/db/models/sql/query.py +18 −92 Original line number Diff line number Diff line Loading @@ -18,9 +18,10 @@ from django.db.models.constants import LOOKUP_SEP from django.db.models.expressions import ExpressionNode from django.db.models.fields import FieldDoesNotExist from django.db.models.loading import get_model from django.db.models.related import PathInfo from django.db.models.sql import aggregates as base_aggregates_module from django.db.models.sql.constants import (QUERY_TERMS, ORDER_DIR, SINGLE, ORDER_PATTERN, JoinInfo, SelectInfo, PathInfo) ORDER_PATTERN, JoinInfo, SelectInfo) from django.db.models.sql.datastructures import EmptyResultSet, Empty, MultiJoin from django.db.models.sql.expressions import SQLEvaluator from django.db.models.sql.where import (WhereNode, Constraint, EverythingNode, Loading Loading @@ -1294,7 +1295,6 @@ class Query(object): contain the same value as the final field). """ path = [] multijoin_pos = None for pos, name in enumerate(names): if name == 'pk': name = opts.pk.name Loading Loading @@ -1328,92 +1328,19 @@ class Query(object): target = final_field.rel.get_related_field() opts = int_model._meta path.append(PathInfo(final_field, target, final_field.model._meta, opts, final_field)) # We have five different cases to solve: foreign keys, reverse # foreign keys, m2m fields (also reverse) and non-relational # fields. We are mostly just using the related field API to # fetch the from and to fields. The m2m fields are handled as # two foreign keys, first one reverse, the second one direct. if direct and not field.rel and not m2m: opts, final_field, False, True)) if hasattr(field, 'get_path_info'): pathinfos, opts, target, final_field = field.get_path_info() path.extend(pathinfos) else: # Local non-relational field. final_field = target = field break elif direct and not m2m: # Foreign Key opts = field.rel.to._meta target = field.rel.get_related_field() final_field = field from_opts = field.model._meta path.append(PathInfo(field, target, from_opts, opts, field)) elif not direct and not m2m: # Revere foreign key final_field = to_field = field.field opts = to_field.model._meta from_field = to_field.rel.get_related_field() from_opts = from_field.model._meta path.append( PathInfo(from_field, to_field, from_opts, opts, to_field)) if from_field.model is to_field.model: # Recursive foreign key to self. target = opts.get_field_by_name( field.field.rel.field_name)[0] else: target = opts.pk elif direct and m2m: if not field.rel.through: # Gotcha! This is just a fake m2m field - a generic relation # field). from_field = opts.pk opts = field.rel.to._meta target = opts.get_field_by_name(field.object_id_field_name)[0] final_field = field # Note that we are using different field for the join_field # than from_field or to_field. This is a hack, but we need the # GenericRelation to generate the extra SQL. path.append(PathInfo(from_field, target, field.model._meta, opts, field)) else: # m2m field. We are travelling first to the m2m table along a # reverse relation, then from m2m table to the target table. from_field1 = opts.get_field_by_name( field.m2m_target_field_name())[0] opts = field.rel.through._meta to_field1 = opts.get_field_by_name(field.m2m_field_name())[0] path.append( PathInfo(from_field1, to_field1, from_field1.model._meta, opts, to_field1)) final_field = from_field2 = opts.get_field_by_name( field.m2m_reverse_field_name())[0] opts = field.rel.to._meta target = to_field2 = opts.get_field_by_name( field.m2m_reverse_target_field_name())[0] path.append( PathInfo(from_field2, to_field2, from_field2.model._meta, opts, from_field2)) elif not direct and m2m: # This one is just like above, except we are travelling the # fields in opposite direction. field = field.field from_field1 = opts.get_field_by_name( field.m2m_reverse_target_field_name())[0] int_opts = field.rel.through._meta to_field1 = int_opts.get_field_by_name( field.m2m_reverse_field_name())[0] path.append( PathInfo(from_field1, to_field1, from_field1.model._meta, int_opts, to_field1)) final_field = from_field2 = int_opts.get_field_by_name( field.m2m_field_name())[0] opts = field.opts target = to_field2 = opts.get_field_by_name( field.m2m_target_field_name())[0] path.append(PathInfo(from_field2, to_field2, from_field2.model._meta, opts, from_field2)) if m2m and multijoin_pos is None: multijoin_pos = pos if not direct and not path[-1].to_field.unique and multijoin_pos is None: multijoin_pos = pos multijoin_pos = None for m2mpos, pathinfo in enumerate(path): if pathinfo.m2m: multijoin_pos = m2mpos break if pos != len(names) - 1: if pos == len(names) - 2: Loading Loading @@ -1463,16 +1390,15 @@ class Query(object): # joins at this stage - we will need the information about join type # of the trimmed joins. for pos, join in enumerate(path): from_field, to_field, from_opts, opts, join_field = join direct = join_field == from_field if direct: nullable = self.is_nullable(from_field) opts = join.to_opts if join.direct: nullable = self.is_nullable(join.from_field) else: nullable = True connection = alias, opts.db_table, from_field.column, to_field.column reuse = None if direct or to_field.unique else can_reuse connection = alias, opts.db_table, join.from_field.column, join.to_field.column reuse = can_reuse if join.m2m else None alias = self.join(connection, reuse=reuse, nullable=nullable, join_field=join_field) nullable=nullable, join_field=join.join_field) joins.append(alias) return final_field, target, opts, joins, path Loading