Django/参照処理を読む(関連の設定とJOIN)
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
[[Djangoを読む]]
#contents
*はじめに [#g9093d2c]
というわけで引き続き、
# And vice versa: Question objects get access to Choice ...
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choic...
を見ていきます。前に見たallですが今度は複数テーブルになっ...
*django.db.models.fields.related_descriptors [#b5c171be]
前回も見たRelatedManager、この中で検索に関係のありそうな...
#code(Python){{
def get_queryset(self):
try:
return self.instance._prefetched_objects_...
except (AttributeError, KeyError):
queryset = super(RelatedManager, self).ge...
return self._apply_rel_filters(queryset)
}}
キャッシュはされてないとして、_apply_rel_filtersに進みま...
#code(Python){{
def _apply_rel_filters(self, queryset):
"""
Filter the queryset for the instance this man...
"""
db = self._db or router.db_for_read(self.mode...
empty_strings_as_null = connections[db].featu...
queryset._add_hints(instance=self.instance)
if self._db:
queryset = queryset.using(self._db)
queryset = queryset.filter(**self.core_filters)
for field in self.field.foreign_related_fields:
val = getattr(self.instance, field.attname)
if val is None or (val == '' and empty_st...
return queryset.none()
queryset._known_related_objects = {self.field...
return queryset
}}
いろいろやっていますが、鍵となるのは真ん中あたりにあるfil...
#code(Python){{
def __init__(self, instance):
super(RelatedManager, self).__init__()
self.instance = instance
self.model = rel.related_model
self.field = rel.field
self.core_filters = {self.field.name: instance}
}}
relはManyToOneRelオブジェクトでfieldとはquestion、instanc...
{'question': Questionオブジェクト}
というフィルタがchoice_set経由だと常に設定されるというこ...
*Query [#gf44aa37]
ここまでわかったらQueryクラスについて前回は端折った関連、...
filter→add_q→_add_q→build_filterと前に見たフローを進んで...
#code(Python){{
def setup_joins(self, names, opts, alias, can_reuse=N...
"""
Compute the necessary table joins for the passage...
given in 'names'. 'opts' is the Options class for...
(which gives the table we are starting from), 'al...
the table to start the joining from.
The 'can_reuse' defines the reverse foreign key j...
can be None in which case all joins are reusable ...
that can be reused. Note that non-reverse foreign...
reusable when using setup_joins().
If 'allow_many' is False, then any reverse foreig...
generate a MultiJoin exception.
Returns the final field involved in the joins, th...
for any 'where' constraint), the final 'opts' val...
field path travelled to generate the joins.
The target field is the field containing the conc...
field can be something different, for example for...
that value. Final field is needed for example in ...
conversions (convert 'obj' in fk__id=obj to pk va...
key field for example).
"""
joins = [alias]
# First, generate the path for the names
path, final_field, targets, rest = self.names_to_...
names, opts, allow_many, fail_on_missing=True)
# Then, add the path to the query's joins. Note t...
# joins at this stage - we will need the informat...
# of the trimmed joins.
for join in path:
opts = join.to_opts
if join.direct:
nullable = self.is_nullable(join.join_fie...
else:
nullable = True
connection = Join(opts.db_table, alias, None,...
reuse = can_reuse if join.m2m else None
alias = self.join(connection, reuse=reuse)
joins.append(alias)
return final_field, targets, opts, joins, path
}}
**names_to_path [#wee88086]
前はnames_to_pathもほぼ無視しましたが今回はこれが効いてき...
#code(Python){{
def names_to_path(self, names, opts, allow_many=True,...
"""
Walks the list of names and turns them into PathI...
a single name in 'names' can generate multiple Pa...
example).
'names' is the path of names to travel, 'opts' is...
start the name resolving from, 'allow_many' is as...
If fail_on_missing is set to True, then a name th...
will generate a FieldError.
Returns a list of PathInfo tuples. In addition re...
(the last used join field), and target (which is ...
contain the same value as the final field). Final...
those names that weren't found (which are likely ...
final lookup).
"""
path, names_with_path = [], []
for pos, name in enumerate(names):
cur_names_with_path = (name, [])
field = None
try:
field = opts.get_field(name)
except FieldDoesNotExist:
# 省略
# 親クラスのフィールドだった場合の処理
if hasattr(field, 'get_path_info'):
pathinfos = field.get_path_info()
if not allow_many:
# 省略
last = pathinfos[-1]
path.extend(pathinfos)
final_field = last.join_field
opts = last.to_opts
targets = last.target_fields
cur_names_with_path[1].extend(pathinfos)
names_with_path.append(cur_names_with_path)
else:
# 省略
return path, final_field, targets, names[pos + 1:]
}}
前回は見なかった方、つまり、Fieldクラス(のサブクラス)が...
#code(Python){{
def get_path_info(self):
"""
Get path from this field to the related model.
"""
opts = self.remote_field.model._meta
from_opts = self.model._meta
return [PathInfo(from_opts, opts, self.foreign_re...
}}
PathInfoはdjango/db/models/query_utils.pyで定義されている...
#code(Python){{
# PathInfo is used when converting lookups (fk__somecol)....
# describe the relation in Model terms (model Options and...
# sides of the relation. The join_field is the field back...
PathInfo = namedtuple('PathInfo', 'from_opts to_opts targ...
}}
foreign_related_fieldsはプロパティで、追いかけていくと、
+related_fieldsプロパティ
+resolve_related_fieldsメソッド
と進み、from(Choiceのquestionフィールド)からto(Questio...
話をnames_to_pathメソッドに戻して処理を追いかけていくと結...
:path|[PathInfo(ChoiceのOptions, QuestionのOptions, Quest...
:final_field|Choiceのquestionフィールド
:targets:[Questionのidフィールド]
:names|[]
**setup_joins [#k0b39577]
さて、setup_joinsに戻ってnames_to_path呼び出しの後を再掲、
#code(Python){{
for join in path:
opts = join.to_opts
if join.direct:
nullable = self.is_nullable(join.join_fie...
else:
nullable = True
connection = Join(opts.db_table, alias, None,...
reuse = can_reuse if join.m2m else None
alias = self.join(connection, reuse=reuse)
joins.append(alias)
return final_field, targets, opts, joins, path
}}
Joinクラスはdjango/db/models/sql/datastructures.pyに記述...
joinメソッド。メソッドドキュメントは引数の説明が古いので...
#code(Python){{
def join(self, join, reuse=None):
reuse = [a for a, j in self.alias_map.items()
if (reuse is None or a in reuse) and j =...
if reuse:
self.ref_alias(reuse[0])
return reuse[0]
# No reuse is possible, so we need a new alias.
alias, _ = self.table_alias(join.table_name, crea...
if join.join_type:
if self.alias_map[join.parent_alias].join_typ...
join_type = LOUTER
else:
join_type = INNER
join.join_type = join_type
join.table_alias = alias
self.alias_map[alias] = join
return alias
}}
aliasはまだ設定されてないので後半に進みます。途中、join_t...
setup_joinsメソッドに戻ると、ループは一周だけなので結果、...
:final_field||Choiceのquestionフィールド
:targets|[Questionのidフィールド]
:opts|QuestionのOptions
:joins|['polls_choice', 'polls_question']
:path|[PathInfo(ChoiceのOptions, QuestionのOptions, Quest...
**build_filter残り [#a594f459]
build_filterメソッドに戻って、trim_joinsはまあtrimされる...
#code(Python){{
targets, alias, join_list = self.trim_joins(sourc...
}}
その後、lookupを取得しているところ、関連フィールドなので...
#code(Python){{
if field.is_relation:
lookup_class = field.get_lookup(lookups[0])
if len(targets) == 1:
lhs = targets[0].get_col(alias, field)
else:
lhs = MultiColSource(alias, targets, sour...
condition = lookup_class(lhs, value)
lookup_type = lookup_class.lookup_name
else:
# 省略
}}
field、実体はForeignKey、get_lookupメソッド自体はForeignO...
#code(Python){{
def get_lookup(self, lookup_name):
if lookup_name == 'in':
return RelatedIn
elif lookup_name == 'exact':
return RelatedExact
# 以下略
}}
RelatedExtractはrelated_lookups.pyに書かれています。
#code(Python){{
class RelatedExact(RelatedLookupMixin, Exact):
pass
}}
targets[0]はQuestionのidフィールド、ですがget_colメソッド...
#code(Python){{
def get_col(self, alias, output_field=None):
if output_field is None:
output_field = self
if alias != self.model._meta.db_table or output_f...
from django.db.models.expressions import Col
return Col(alias, self, output_field)
else:
return self.cached_col
}}
前はelseに行きましたが今回はifの方が実行されます。つまり、
:alias|'polls_question'
:target|Questionのidフィールド
:output_field||Choiceのquestionフィールド
と設定されます。
***RelatedLookupMixin [#g46ac610]
RelatedLookupMixin、get_prep_lookupメソッドがオーバーライ...
#code(Python){{
class RelatedLookupMixin(object):
def get_prep_lookup(self):
if not isinstance(self.lhs, MultiColSource) and s...
# If we get here, we are dealing with single-...
self.rhs = get_normalized_value(self.rhs, sel...
# We need to run the related field's get_prep...
# ForeignKey to IntegerField given value 'abc...
# doesn't have validation for non-integers, s...
# using the target field.
if self.prepare_rhs and hasattr(self.lhs.outp...
# Get the target field. We can safely ass...
# as we don't get to the direct value bra...
target_field = self.lhs.output_field.get_...
self.rhs = target_field.get_prep_value(se...
return super(RelatedLookupMixin, self).get_prep_l...
}}
get_normalized_valueは関数です。
#code(Python){{
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].ta...
for source in sources:
while not isinstance(value, source.model) and...
source = source.remote_field.model._meta....
try:
value_list.append(getattr(value, source.a...
except AttributeError:
# A case like Restaurant.objects.filter(p...
# where place is a OneToOneField and the ...
return (value.pk,)
return tuple(value_list)
if not isinstance(value, tuple):
return (value,)
return value
}}
lhsは先ほど出てきたColオブジェクト、whileは回らないはずで...
この後、JOINの方法を外部結合にするのか内部結合にするのか...
*SQLCompiler [#a722cd6c]
**get_from_clause [#c337ba4c]
さて、Queryオブジェクトが構築されたので次はSQLCompilerで...
#code(Python){{
def get_from_clause(self):
"""
Returns a list of strings that are joined togethe...
"FROM" part of the query, as well as a list any e...
need to be included. Sub-classes, can override th...
from-clause via a "select".
This should only be called after any SQL construc...
might change the tables we need. This means the s...
ordering and distinct must be done first.
"""
result = []
params = []
for alias in self.query.tables:
try:
from_clause = self.query.alias_map[alias]
except KeyError:
# Extra tables can end up in self.tables,...
# alias_map if they aren't in a join. Tha...
continue
clause_sql, clause_params = self.compile(from...
result.append(clause_sql)
params.extend(clause_params)
return result, params
}}
今回はquery.tablesは2つ、['polls_choice', 'polls_question...
後は、whereに設定されているRelatedExact(もう関連モデルを...
*おわりに [#w302ec5d]
今回はchoice_set経由での検索の際にどうやってQuestionイン...
さて、長く見てきたモデル(チュートリアル2)もようやく終わ...
終了行:
[[Djangoを読む]]
#contents
*はじめに [#g9093d2c]
というわけで引き続き、
# And vice versa: Question objects get access to Choice ...
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choic...
を見ていきます。前に見たallですが今度は複数テーブルになっ...
*django.db.models.fields.related_descriptors [#b5c171be]
前回も見たRelatedManager、この中で検索に関係のありそうな...
#code(Python){{
def get_queryset(self):
try:
return self.instance._prefetched_objects_...
except (AttributeError, KeyError):
queryset = super(RelatedManager, self).ge...
return self._apply_rel_filters(queryset)
}}
キャッシュはされてないとして、_apply_rel_filtersに進みま...
#code(Python){{
def _apply_rel_filters(self, queryset):
"""
Filter the queryset for the instance this man...
"""
db = self._db or router.db_for_read(self.mode...
empty_strings_as_null = connections[db].featu...
queryset._add_hints(instance=self.instance)
if self._db:
queryset = queryset.using(self._db)
queryset = queryset.filter(**self.core_filters)
for field in self.field.foreign_related_fields:
val = getattr(self.instance, field.attname)
if val is None or (val == '' and empty_st...
return queryset.none()
queryset._known_related_objects = {self.field...
return queryset
}}
いろいろやっていますが、鍵となるのは真ん中あたりにあるfil...
#code(Python){{
def __init__(self, instance):
super(RelatedManager, self).__init__()
self.instance = instance
self.model = rel.related_model
self.field = rel.field
self.core_filters = {self.field.name: instance}
}}
relはManyToOneRelオブジェクトでfieldとはquestion、instanc...
{'question': Questionオブジェクト}
というフィルタがchoice_set経由だと常に設定されるというこ...
*Query [#gf44aa37]
ここまでわかったらQueryクラスについて前回は端折った関連、...
filter→add_q→_add_q→build_filterと前に見たフローを進んで...
#code(Python){{
def setup_joins(self, names, opts, alias, can_reuse=N...
"""
Compute the necessary table joins for the passage...
given in 'names'. 'opts' is the Options class for...
(which gives the table we are starting from), 'al...
the table to start the joining from.
The 'can_reuse' defines the reverse foreign key j...
can be None in which case all joins are reusable ...
that can be reused. Note that non-reverse foreign...
reusable when using setup_joins().
If 'allow_many' is False, then any reverse foreig...
generate a MultiJoin exception.
Returns the final field involved in the joins, th...
for any 'where' constraint), the final 'opts' val...
field path travelled to generate the joins.
The target field is the field containing the conc...
field can be something different, for example for...
that value. Final field is needed for example in ...
conversions (convert 'obj' in fk__id=obj to pk va...
key field for example).
"""
joins = [alias]
# First, generate the path for the names
path, final_field, targets, rest = self.names_to_...
names, opts, allow_many, fail_on_missing=True)
# Then, add the path to the query's joins. Note t...
# joins at this stage - we will need the informat...
# of the trimmed joins.
for join in path:
opts = join.to_opts
if join.direct:
nullable = self.is_nullable(join.join_fie...
else:
nullable = True
connection = Join(opts.db_table, alias, None,...
reuse = can_reuse if join.m2m else None
alias = self.join(connection, reuse=reuse)
joins.append(alias)
return final_field, targets, opts, joins, path
}}
**names_to_path [#wee88086]
前はnames_to_pathもほぼ無視しましたが今回はこれが効いてき...
#code(Python){{
def names_to_path(self, names, opts, allow_many=True,...
"""
Walks the list of names and turns them into PathI...
a single name in 'names' can generate multiple Pa...
example).
'names' is the path of names to travel, 'opts' is...
start the name resolving from, 'allow_many' is as...
If fail_on_missing is set to True, then a name th...
will generate a FieldError.
Returns a list of PathInfo tuples. In addition re...
(the last used join field), and target (which is ...
contain the same value as the final field). Final...
those names that weren't found (which are likely ...
final lookup).
"""
path, names_with_path = [], []
for pos, name in enumerate(names):
cur_names_with_path = (name, [])
field = None
try:
field = opts.get_field(name)
except FieldDoesNotExist:
# 省略
# 親クラスのフィールドだった場合の処理
if hasattr(field, 'get_path_info'):
pathinfos = field.get_path_info()
if not allow_many:
# 省略
last = pathinfos[-1]
path.extend(pathinfos)
final_field = last.join_field
opts = last.to_opts
targets = last.target_fields
cur_names_with_path[1].extend(pathinfos)
names_with_path.append(cur_names_with_path)
else:
# 省略
return path, final_field, targets, names[pos + 1:]
}}
前回は見なかった方、つまり、Fieldクラス(のサブクラス)が...
#code(Python){{
def get_path_info(self):
"""
Get path from this field to the related model.
"""
opts = self.remote_field.model._meta
from_opts = self.model._meta
return [PathInfo(from_opts, opts, self.foreign_re...
}}
PathInfoはdjango/db/models/query_utils.pyで定義されている...
#code(Python){{
# PathInfo is used when converting lookups (fk__somecol)....
# describe the relation in Model terms (model Options and...
# sides of the relation. The join_field is the field back...
PathInfo = namedtuple('PathInfo', 'from_opts to_opts targ...
}}
foreign_related_fieldsはプロパティで、追いかけていくと、
+related_fieldsプロパティ
+resolve_related_fieldsメソッド
と進み、from(Choiceのquestionフィールド)からto(Questio...
話をnames_to_pathメソッドに戻して処理を追いかけていくと結...
:path|[PathInfo(ChoiceのOptions, QuestionのOptions, Quest...
:final_field|Choiceのquestionフィールド
:targets:[Questionのidフィールド]
:names|[]
**setup_joins [#k0b39577]
さて、setup_joinsに戻ってnames_to_path呼び出しの後を再掲、
#code(Python){{
for join in path:
opts = join.to_opts
if join.direct:
nullable = self.is_nullable(join.join_fie...
else:
nullable = True
connection = Join(opts.db_table, alias, None,...
reuse = can_reuse if join.m2m else None
alias = self.join(connection, reuse=reuse)
joins.append(alias)
return final_field, targets, opts, joins, path
}}
Joinクラスはdjango/db/models/sql/datastructures.pyに記述...
joinメソッド。メソッドドキュメントは引数の説明が古いので...
#code(Python){{
def join(self, join, reuse=None):
reuse = [a for a, j in self.alias_map.items()
if (reuse is None or a in reuse) and j =...
if reuse:
self.ref_alias(reuse[0])
return reuse[0]
# No reuse is possible, so we need a new alias.
alias, _ = self.table_alias(join.table_name, crea...
if join.join_type:
if self.alias_map[join.parent_alias].join_typ...
join_type = LOUTER
else:
join_type = INNER
join.join_type = join_type
join.table_alias = alias
self.alias_map[alias] = join
return alias
}}
aliasはまだ設定されてないので後半に進みます。途中、join_t...
setup_joinsメソッドに戻ると、ループは一周だけなので結果、...
:final_field||Choiceのquestionフィールド
:targets|[Questionのidフィールド]
:opts|QuestionのOptions
:joins|['polls_choice', 'polls_question']
:path|[PathInfo(ChoiceのOptions, QuestionのOptions, Quest...
**build_filter残り [#a594f459]
build_filterメソッドに戻って、trim_joinsはまあtrimされる...
#code(Python){{
targets, alias, join_list = self.trim_joins(sourc...
}}
その後、lookupを取得しているところ、関連フィールドなので...
#code(Python){{
if field.is_relation:
lookup_class = field.get_lookup(lookups[0])
if len(targets) == 1:
lhs = targets[0].get_col(alias, field)
else:
lhs = MultiColSource(alias, targets, sour...
condition = lookup_class(lhs, value)
lookup_type = lookup_class.lookup_name
else:
# 省略
}}
field、実体はForeignKey、get_lookupメソッド自体はForeignO...
#code(Python){{
def get_lookup(self, lookup_name):
if lookup_name == 'in':
return RelatedIn
elif lookup_name == 'exact':
return RelatedExact
# 以下略
}}
RelatedExtractはrelated_lookups.pyに書かれています。
#code(Python){{
class RelatedExact(RelatedLookupMixin, Exact):
pass
}}
targets[0]はQuestionのidフィールド、ですがget_colメソッド...
#code(Python){{
def get_col(self, alias, output_field=None):
if output_field is None:
output_field = self
if alias != self.model._meta.db_table or output_f...
from django.db.models.expressions import Col
return Col(alias, self, output_field)
else:
return self.cached_col
}}
前はelseに行きましたが今回はifの方が実行されます。つまり、
:alias|'polls_question'
:target|Questionのidフィールド
:output_field||Choiceのquestionフィールド
と設定されます。
***RelatedLookupMixin [#g46ac610]
RelatedLookupMixin、get_prep_lookupメソッドがオーバーライ...
#code(Python){{
class RelatedLookupMixin(object):
def get_prep_lookup(self):
if not isinstance(self.lhs, MultiColSource) and s...
# If we get here, we are dealing with single-...
self.rhs = get_normalized_value(self.rhs, sel...
# We need to run the related field's get_prep...
# ForeignKey to IntegerField given value 'abc...
# doesn't have validation for non-integers, s...
# using the target field.
if self.prepare_rhs and hasattr(self.lhs.outp...
# Get the target field. We can safely ass...
# as we don't get to the direct value bra...
target_field = self.lhs.output_field.get_...
self.rhs = target_field.get_prep_value(se...
return super(RelatedLookupMixin, self).get_prep_l...
}}
get_normalized_valueは関数です。
#code(Python){{
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].ta...
for source in sources:
while not isinstance(value, source.model) and...
source = source.remote_field.model._meta....
try:
value_list.append(getattr(value, source.a...
except AttributeError:
# A case like Restaurant.objects.filter(p...
# where place is a OneToOneField and the ...
return (value.pk,)
return tuple(value_list)
if not isinstance(value, tuple):
return (value,)
return value
}}
lhsは先ほど出てきたColオブジェクト、whileは回らないはずで...
この後、JOINの方法を外部結合にするのか内部結合にするのか...
*SQLCompiler [#a722cd6c]
**get_from_clause [#c337ba4c]
さて、Queryオブジェクトが構築されたので次はSQLCompilerで...
#code(Python){{
def get_from_clause(self):
"""
Returns a list of strings that are joined togethe...
"FROM" part of the query, as well as a list any e...
need to be included. Sub-classes, can override th...
from-clause via a "select".
This should only be called after any SQL construc...
might change the tables we need. This means the s...
ordering and distinct must be done first.
"""
result = []
params = []
for alias in self.query.tables:
try:
from_clause = self.query.alias_map[alias]
except KeyError:
# Extra tables can end up in self.tables,...
# alias_map if they aren't in a join. Tha...
continue
clause_sql, clause_params = self.compile(from...
result.append(clause_sql)
params.extend(clause_params)
return result, params
}}
今回はquery.tablesは2つ、['polls_choice', 'polls_question...
後は、whereに設定されているRelatedExact(もう関連モデルを...
*おわりに [#w302ec5d]
今回はchoice_set経由での検索の際にどうやってQuestionイン...
さて、長く見てきたモデル(チュートリアル2)もようやく終わ...
ページ名: