Django/モデル検索時の処理を読む(filter、単独テーブル)
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
[[Djangoを読む]]
#contents
*はじめに [#s96ea057]
前回はobjects.all()について見ました。条件がない場合でもか...
というわけで、今回は条件指定、filterを呼び出した時(と、...
>>> Question.objects.filter(question_text__startswith='W...
<QuerySet [<Question: What's up?>]>
について見ていきます。
*QuerySet [#mb5e499d]
というわけで例によってQuerySetクラスから開始です。
#code(Python){{
def filter(self, *args, **kwargs):
"""
Returns a new QuerySet instance with the args AND...
set.
"""
return self._filter_or_exclude(False, *args, **kw...
}}
すぐ下にはexcludeが書いてあって、ほぼ同じ処理なので共通化...
#code(Python){{
def _filter_or_exclude(self, negate, *args, **kwargs):
clone = self._clone()
if negate:
clone.query.add_q(~Q(*args, **kwargs))
else:
clone.query.add_q(Q(*args, **kwargs))
return clone
}}
Q。非常に怪しげです(笑)
Qクラスはdjango/db/models/query_utils.pyに書かれています。
#code(Python){{
class Q(tree.Node):
"""
Encapsulates filters as objects that can then be comb...
`&` and `|`).
"""
# Connection types
AND = 'AND'
OR = 'OR'
default = AND
def __init__(self, *args, **kwargs):
super(Q, self).__init__(children=list(args) + lis...
}}
treeモジュールはdjango.utilsにあります。
#code(Python){{
class Node(object):
"""
A single internal node in the tree graph. A Node shou...
connection (the root) with the children being either ...
Node instances.
"""
def __init__(self, children=None, connector=None, neg...
"""
Constructs a new Node. If no connector is given, ...
used.
"""
self.children = children[:] if children else []
self.connector = connector or self.default
self.negated = negated
}}
普通のツリーのノードです。
*Query [#sbd7e47d]
というわけで、Qオブジェクトが何者なのかわかったのでQuery...
#code(Python){{
def add_q(self, q_object):
"""
A preprocessor for the internal _add_q(). Respons...
join promotion.
"""
# For join promotion this case is doing an AND fo...
# and existing conditions. So, any existing inner...
# type to remain inner. Existing outer joins can ...
# (Consider case where rel_a is LOUTER and rel_a_...
# rel_a doesn't produce any rows, then the whole ...
# So, demotion is OK.
existing_inner = set(
(a for a in self.alias_map if self.alias_map[...
clause, _ = self._add_q(q_object, self.used_alias...
if clause:
self.where.add(clause, AND)
self.demote_joins(existing_inner)
}}
alias_mapは前回も出てきましたが、今の状況では空なはずなの...
また、used_aliasesも現時点では空です。
_add_qに進む前に、whereが何者なのか確認しておきます。Quer...
#code(Python){{
def __init__(self, model, where=WhereNode):
# 省略
self.where = where()
self.where_class = where
}}
WhereNodeはdjango/db/models/sql/where.pyに定義されていま...
#code(Python){{
class WhereNode(tree.Node):
}}
というわけで、WHEREもツリーとして表されているようです。
**_add_q [#we0a9eae]
では改めて_add_qメソッド
#code(Python){{
def _add_q(self, q_object, used_aliases, branch_negat...
current_negated=False, allow_joins=True, s...
"""
Adds a Q-object to the current filter.
"""
connector = q_object.connector
current_negated = current_negated ^ q_object.nega...
branch_negated = branch_negated or q_object.negated
target_clause = self.where_class(connector=connec...
negated=q_object...
joinpromoter = JoinPromoter(q_object.connector, l...
for child in q_object.children:
if isinstance(child, Node):
child_clause, needed_inner = self._add_q(
child, used_aliases, branch_negated,
current_negated, allow_joins, split_s...
joinpromoter.add_votes(needed_inner)
else:
child_clause, needed_inner = self.build_f...
child, can_reuse=used_aliases, branch...
current_negated=current_negated, conn...
allow_joins=allow_joins, split_subq=s...
)
joinpromoter.add_votes(needed_inner)
if child_clause:
target_clause.add(child_clause, connector)
needed_inner = joinpromoter.update_join_types(self)
return target_clause, needed_inner
}}
うーん、いきなりややこしくなった。とりあえずnegatedと名の...
次にJoinPromoter、今回のケースではJOINは起こらないので無...
というわけで、ポイントとなるのは以下になります。
#code(Python){{
for child in q_object.children:
if isinstance(child, Node):
# こっちじゃない
else:
child_clause, needed_inner = self.build_f...
child, can_reuse=used_aliases, branch...
current_negated=current_negated, conn...
allow_joins=allow_joins, split_subq=s...
)
joinpromoter.add_votes(needed_inner)
if child_clause:
target_clause.add(child_clause, connector)
}}
build_filterメソッドを呼んでる引数を確認しておくと次の通...
:child|('question_text__startswith', 'What')
:can_reuse|set()
:branch_negated|False
:current_negated|False
:connector|'AND'
:allow_joins|True
:split_subq|True
**build_filter [#f76b4e25]
build_filterは長いのでところどころ省略
#code(Python){{
def build_filter(self, filter_expr, branch_negated=Fa...
can_reuse=None, connector=AND, allow...
arg, value = filter_expr
lookups, parts, reffed_expression = self.solve_lo...
# Work out the lookup type and remove it from the...
# if necessary.
value, lookups, used_joins = self.prepare_lookup_...
clause = self.where_class()
opts = self.get_meta()
alias = self.get_initial_alias()
allow_many = not branch_negated or not split_subq
try:
field, sources, opts, join_list, path = self....
parts, opts, alias, can_reuse=can_reuse, ...
except MultiJoin as e:
# 省略
if can_reuse is not None:
can_reuse.update(join_list)
used_joins = set(used_joins).union(set(join_list))
targets, alias, join_list = self.trim_joins(sourc...
if field.is_relation:
# 省略
else:
col = targets[0].get_col(alias, field)
condition = self.build_lookup(lookups, col, v...
lookup_type = condition.lookup_name
clause.add(condition, AND)
require_outer = lookup_type == 'isnull' and value...
return clause, used_joins if not require_outer el...
}}
filterでは関連オブジェクトのフィールドが○○の値、という条...
-solve_lookup_type
-prepare_lookup_value
-setup_joins
-trim_joins
-build_lookup
***solve_lookup_type [#f762283c]
ではまずsolve_lookup_typeです。一部省略
#code(Python){{
def solve_lookup_type(self, lookup):
"""
Solve the lookup type from the lookup (eg: 'fooba...
"""
lookup_splitted = lookup.split(LOOKUP_SEP)
_, field, _, lookup_parts = self.names_to_path(lo...
field_parts = lookup_splitted[0:len(lookup_splitt...
if len(lookup_parts) == 0:
lookup_parts = ['exact']
return lookup_parts, field_parts, False
}}
LOOKUP_SEPは'__'、渡されているのは'question_text__startsw...
で、下請けの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):
field = None
try:
field = opts.get_field(name)
except FieldDoesNotExist:
# 省略
if hasattr(field, 'get_path_info'):
# 省略
else:
# Local non-relational field.
final_field = field
targets = (field,)
break
return path, final_field, targets, names[pos + 1:]
}}
関連オブジェクトのフィールドを参照しない場合は結局これだ...
['startswith'], ['question_text'], False
という情報を返すことになります。
***prepare_lookup_value [#eb43ff2b]
次にprepare_lookup_valueです。いつも通り一部省略
#code(Python){{
def prepare_lookup_value(self, value, lookups, can_re...
# Default lookup if none given is exact.
used_joins = []
# Interpret '__exact=None' as the sql 'is NULL'; ...
# uses of None as a query value.
if value is None:
if lookups[-1] not in ('exact', 'iexact'):
raise ValueError("Cannot use None as a qu...
lookups[-1] = 'isnull'
value = True
return value, lookups, used_joins
}}
valueがNoneの時はSQLをIS NULLにする処理が行われていますが...
'What', ['startswith'], []
という情報が返されます。
***setup_joins [#ea9d2415]
setup_joinsが呼ばれる前に、前回も出てきたget_initial_alia...
で、setup_joins。とは言うものの、今回はJOINに関係している...
#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:
# 省略
return final_field, targets, opts, joins, path
}}
結局、names_to_pathの戻り値がほぼそのまま返されます。
question_textのCharField, (question_textのCharField,), Q...
***trim_joins [#u97d9401]
trim_joins。同じくfor文は無視します。
#code(Python){{
def trim_joins(self, targets, joins, path):
"""
The 'target' parameter is the final field being j...
is the full list of join aliases. The 'path' cont...
used to create the joins.
Returns the final target field and table alias an...
joins.
We will always trim any direct join if we have th...
available already in the previous table. Reverse ...
trimmed as we don't know if there is anything on ...
the join.
"""
joins = joins[:]
for pos, info in enumerate(reversed(path)):
# 省略
return targets, joins[-1], joins
}}
(question_textのCharField,), QuestionのBaseTable, [Quest...
が返されます。
***build_lookup [#s13390bf]
最後にbuild_lookupです。
#code(Python){{
def build_lookup(self, lookups, lhs, rhs):
"""
Tries to extract transforms and lookup from given...
The lhs value is something that works like SQLExp...
The rhs value is what the lookup is going to comp...
The lookups is a list of names to extract using g...
and get_transform().
"""
lookups = lookups[:]
while lookups:
name = lookups[0]
# If there is just one part left, try first g...
# that if the lhs supports both transform and...
# name, then lookup will be picked.
if len(lookups) == 1:
final_lookup = lhs.get_lookup(name)
if not final_lookup:
# We didn't find a lookup. We are goi...
# the name as transform, and do an Ex...
# it.
lhs = self.try_transform(lhs, name, l...
final_lookup = lhs.get_lookup('exact')
return final_lookup(lhs, rhs)
lhs = self.try_transform(lhs, name, lookups)
lookups = lookups[1:]
}}
lhsはCharFieldなのでとget_lookupを探しても定義はありませ...
#code(Python){{
class Field(RegisterLookupMixin):
}}
というわけでこちらに書いてありそうです。query_utilsモジュ...
#code(Python){{
def get_lookup(self, lookup_name):
from django.db.models.lookups import Lookup
found = self._get_lookup(lookup_name)
if found is None and hasattr(self, 'output_field'):
return self.output_field.get_lookup(lookup_na...
if found is not None and not issubclass(found, Lo...
return None
return found
def _get_lookup(self, lookup_name):
try:
return self.class_lookups[lookup_name]
except KeyError:
# 省略
except AttributeError:
# This class didn't have any class_lookups
pass
return None
}}
class_lookupsへの登録はクラスメソッドとして定義されていま...
#code(Python){{
@classmethod
def register_lookup(cls, lookup, lookup_name=None):
if lookup_name is None:
lookup_name = lookup.lookup_name
if 'class_lookups' not in cls.__dict__:
cls.class_lookups = {}
cls.class_lookups[lookup_name] = lookup
return lookup
}}
次の疑問は、「仕組みはわかったけど、じゃあいつlookupが登...
#code(Python){{
class StartsWith(PatternLookup):
lookup_name = 'startswith'
prepare_rhs = False
def process_rhs(self, qn, connection):
rhs, params = super(StartsWith, self).process_rhs...
if params and not self.bilateral_transforms:
params[0] = "%s%%" % connection.ops.prep_for_...
return rhs, params
Field.register_lookup(StartsWith)
}}
というわけで、startswithに対応するLookupが登録されます。
で、StartsWithオブジェクトが作られてようやくfilterメソッ...
構築されたStartsWithオブジェクトはbuild_filter→_add_q→add...
self.where
StartsWith(lhs=question_textのCol, rhs='What')
*WhereNode [#c85bf6f1]
オブジェクトが構築できたら、後は前回見たように処理が行わ...
#code(Python){{
def as_sql(self, compiler, connection):
"""
Returns the SQL version of the where clause and t...
substituted in. Returns '', [] if this node match...
None, [] if this node is empty, and raises EmptyR...
node can't match anything.
"""
result = []
result_params = []
if self.connector == AND:
full_needed, empty_needed = len(self.children...
else:
full_needed, empty_needed = 1, len(self.child...
for child in self.children:
try:
sql, params = compiler.compile(child)
except EmptyResultSet:
empty_needed -= 1
else:
if sql:
result.append(sql)
result_params.extend(params)
else:
full_needed -= 1
# 省略
conn = ' %s ' % self.connector
sql_string = conn.join(result)
if sql_string:
if self.negated:
# Some backends (Oracle at least) need pa...
# around the inner SQL in the negated cas...
# inner SQL contains just a single expres...
sql_string = 'NOT (%s)' % sql_string
elif len(result) > 1:
sql_string = '(%s)' % sql_string
return sql_string, result_params
}}
子ノードをcompileしてつなげているだけです。
**StartsWith [#e915ee33]
では子ノードのStartsWithについて見ていきましょう。まずは...
#code(Python){{
class StartsWith(PatternLookup):
class PatternLookup(BuiltinLookup):
class BuiltinLookup(Lookup):
}}
BuildinLookupまで行くとas_sqlメソッドが書かれています。
#code(Python){{
def as_sql(self, compiler, connection):
lhs_sql, params = self.process_lhs(compiler, conn...
rhs_sql, rhs_params = self.process_rhs(compiler, ...
params.extend(rhs_params)
rhs_sql = self.get_rhs_op(connection, rhs_sql)
return '%s %s' % (lhs_sql, rhs_sql), params
}}
process_lhsはas_sqlのすぐ上に書かれています。DBMSに応じた...
process_rhs。StartsWithでオーバーライドされています。bila...
#code(Python){{
def process_rhs(self, qn, connection):
rhs, params = super(StartsWith, self).process_rhs...
if params and not self.bilateral_transforms:
params[0] = "%s%%" % connection.ops.prep_for_...
return rhs, params
}}
で親クラス、Lookupの方のprocess_rhs。ただの文字列なのでge...
#code(Python){{
def process_rhs(self, compiler, connection):
value = self.rhs
if self.bilateral_transforms:
# 省略
# Due to historical reasons there are a couple of...
# ways to produce sql here. get_compiler is likel...
# instance, _as_sql QuerySet and as_sql just some...
# as_sql. Finally the value can of course be just...
# Python value.
if hasattr(value, 'get_compiler'):
value = value.get_compiler(connection=connect...
if hasattr(value, 'as_sql'):
sql, params = compiler.compile(value)
return '(' + sql + ')', params
if hasattr(value, '_as_sql'):
sql, params = value._as_sql(connection=connec...
return '(' + sql + ')', params
else:
return self.get_db_prep_lookup(value, connect...
def get_db_prep_lookup(self, value, connection):
return ('%s', [value])
}}
get_rhs_opはPatternLookupにも書かれていますがBuiltinLooku...
#code(Python){{
def get_rhs_op(self, connection, rhs):
return connection.operators[self.lookup_name] % rhs
}}
sqlite3の場合、operatorsは以下の通りです。つまり、LIKEを...
#code(Python){{
operators = {
'exact': '= %s',
'iexact': "LIKE %s ESCAPE '\\'",
'contains': "LIKE %s ESCAPE '\\'",
'icontains': "LIKE %s ESCAPE '\\'",
'regex': 'REGEXP %s',
'iregex': "REGEXP '(?i)' || %s",
'gt': '> %s',
'gte': '>= %s',
'lt': '< %s',
'lte': '<= %s',
'startswith': "LIKE %s ESCAPE '\\'",
'endswith': "LIKE %s ESCAPE '\\'",
'istartswith': "LIKE %s ESCAPE '\\'",
'iendswith': "LIKE %s ESCAPE '\\'",
}
}}
最終的に、WhereNodeがコンパイルされると以下の文字列(とパ...
'"polls_question"."question_text" LIKE %s ESCAPE \'\\\''...
*おわりに [#id175bb9]
今回はfilterメソッドを使い検索条件が指定された時の処理を...
今回(と前回)、JOINについては実際には発生しないのでばっ...
終了行:
[[Djangoを読む]]
#contents
*はじめに [#s96ea057]
前回はobjects.all()について見ました。条件がない場合でもか...
というわけで、今回は条件指定、filterを呼び出した時(と、...
>>> Question.objects.filter(question_text__startswith='W...
<QuerySet [<Question: What's up?>]>
について見ていきます。
*QuerySet [#mb5e499d]
というわけで例によってQuerySetクラスから開始です。
#code(Python){{
def filter(self, *args, **kwargs):
"""
Returns a new QuerySet instance with the args AND...
set.
"""
return self._filter_or_exclude(False, *args, **kw...
}}
すぐ下にはexcludeが書いてあって、ほぼ同じ処理なので共通化...
#code(Python){{
def _filter_or_exclude(self, negate, *args, **kwargs):
clone = self._clone()
if negate:
clone.query.add_q(~Q(*args, **kwargs))
else:
clone.query.add_q(Q(*args, **kwargs))
return clone
}}
Q。非常に怪しげです(笑)
Qクラスはdjango/db/models/query_utils.pyに書かれています。
#code(Python){{
class Q(tree.Node):
"""
Encapsulates filters as objects that can then be comb...
`&` and `|`).
"""
# Connection types
AND = 'AND'
OR = 'OR'
default = AND
def __init__(self, *args, **kwargs):
super(Q, self).__init__(children=list(args) + lis...
}}
treeモジュールはdjango.utilsにあります。
#code(Python){{
class Node(object):
"""
A single internal node in the tree graph. A Node shou...
connection (the root) with the children being either ...
Node instances.
"""
def __init__(self, children=None, connector=None, neg...
"""
Constructs a new Node. If no connector is given, ...
used.
"""
self.children = children[:] if children else []
self.connector = connector or self.default
self.negated = negated
}}
普通のツリーのノードです。
*Query [#sbd7e47d]
というわけで、Qオブジェクトが何者なのかわかったのでQuery...
#code(Python){{
def add_q(self, q_object):
"""
A preprocessor for the internal _add_q(). Respons...
join promotion.
"""
# For join promotion this case is doing an AND fo...
# and existing conditions. So, any existing inner...
# type to remain inner. Existing outer joins can ...
# (Consider case where rel_a is LOUTER and rel_a_...
# rel_a doesn't produce any rows, then the whole ...
# So, demotion is OK.
existing_inner = set(
(a for a in self.alias_map if self.alias_map[...
clause, _ = self._add_q(q_object, self.used_alias...
if clause:
self.where.add(clause, AND)
self.demote_joins(existing_inner)
}}
alias_mapは前回も出てきましたが、今の状況では空なはずなの...
また、used_aliasesも現時点では空です。
_add_qに進む前に、whereが何者なのか確認しておきます。Quer...
#code(Python){{
def __init__(self, model, where=WhereNode):
# 省略
self.where = where()
self.where_class = where
}}
WhereNodeはdjango/db/models/sql/where.pyに定義されていま...
#code(Python){{
class WhereNode(tree.Node):
}}
というわけで、WHEREもツリーとして表されているようです。
**_add_q [#we0a9eae]
では改めて_add_qメソッド
#code(Python){{
def _add_q(self, q_object, used_aliases, branch_negat...
current_negated=False, allow_joins=True, s...
"""
Adds a Q-object to the current filter.
"""
connector = q_object.connector
current_negated = current_negated ^ q_object.nega...
branch_negated = branch_negated or q_object.negated
target_clause = self.where_class(connector=connec...
negated=q_object...
joinpromoter = JoinPromoter(q_object.connector, l...
for child in q_object.children:
if isinstance(child, Node):
child_clause, needed_inner = self._add_q(
child, used_aliases, branch_negated,
current_negated, allow_joins, split_s...
joinpromoter.add_votes(needed_inner)
else:
child_clause, needed_inner = self.build_f...
child, can_reuse=used_aliases, branch...
current_negated=current_negated, conn...
allow_joins=allow_joins, split_subq=s...
)
joinpromoter.add_votes(needed_inner)
if child_clause:
target_clause.add(child_clause, connector)
needed_inner = joinpromoter.update_join_types(self)
return target_clause, needed_inner
}}
うーん、いきなりややこしくなった。とりあえずnegatedと名の...
次にJoinPromoter、今回のケースではJOINは起こらないので無...
というわけで、ポイントとなるのは以下になります。
#code(Python){{
for child in q_object.children:
if isinstance(child, Node):
# こっちじゃない
else:
child_clause, needed_inner = self.build_f...
child, can_reuse=used_aliases, branch...
current_negated=current_negated, conn...
allow_joins=allow_joins, split_subq=s...
)
joinpromoter.add_votes(needed_inner)
if child_clause:
target_clause.add(child_clause, connector)
}}
build_filterメソッドを呼んでる引数を確認しておくと次の通...
:child|('question_text__startswith', 'What')
:can_reuse|set()
:branch_negated|False
:current_negated|False
:connector|'AND'
:allow_joins|True
:split_subq|True
**build_filter [#f76b4e25]
build_filterは長いのでところどころ省略
#code(Python){{
def build_filter(self, filter_expr, branch_negated=Fa...
can_reuse=None, connector=AND, allow...
arg, value = filter_expr
lookups, parts, reffed_expression = self.solve_lo...
# Work out the lookup type and remove it from the...
# if necessary.
value, lookups, used_joins = self.prepare_lookup_...
clause = self.where_class()
opts = self.get_meta()
alias = self.get_initial_alias()
allow_many = not branch_negated or not split_subq
try:
field, sources, opts, join_list, path = self....
parts, opts, alias, can_reuse=can_reuse, ...
except MultiJoin as e:
# 省略
if can_reuse is not None:
can_reuse.update(join_list)
used_joins = set(used_joins).union(set(join_list))
targets, alias, join_list = self.trim_joins(sourc...
if field.is_relation:
# 省略
else:
col = targets[0].get_col(alias, field)
condition = self.build_lookup(lookups, col, v...
lookup_type = condition.lookup_name
clause.add(condition, AND)
require_outer = lookup_type == 'isnull' and value...
return clause, used_joins if not require_outer el...
}}
filterでは関連オブジェクトのフィールドが○○の値、という条...
-solve_lookup_type
-prepare_lookup_value
-setup_joins
-trim_joins
-build_lookup
***solve_lookup_type [#f762283c]
ではまずsolve_lookup_typeです。一部省略
#code(Python){{
def solve_lookup_type(self, lookup):
"""
Solve the lookup type from the lookup (eg: 'fooba...
"""
lookup_splitted = lookup.split(LOOKUP_SEP)
_, field, _, lookup_parts = self.names_to_path(lo...
field_parts = lookup_splitted[0:len(lookup_splitt...
if len(lookup_parts) == 0:
lookup_parts = ['exact']
return lookup_parts, field_parts, False
}}
LOOKUP_SEPは'__'、渡されているのは'question_text__startsw...
で、下請けの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):
field = None
try:
field = opts.get_field(name)
except FieldDoesNotExist:
# 省略
if hasattr(field, 'get_path_info'):
# 省略
else:
# Local non-relational field.
final_field = field
targets = (field,)
break
return path, final_field, targets, names[pos + 1:]
}}
関連オブジェクトのフィールドを参照しない場合は結局これだ...
['startswith'], ['question_text'], False
という情報を返すことになります。
***prepare_lookup_value [#eb43ff2b]
次にprepare_lookup_valueです。いつも通り一部省略
#code(Python){{
def prepare_lookup_value(self, value, lookups, can_re...
# Default lookup if none given is exact.
used_joins = []
# Interpret '__exact=None' as the sql 'is NULL'; ...
# uses of None as a query value.
if value is None:
if lookups[-1] not in ('exact', 'iexact'):
raise ValueError("Cannot use None as a qu...
lookups[-1] = 'isnull'
value = True
return value, lookups, used_joins
}}
valueがNoneの時はSQLをIS NULLにする処理が行われていますが...
'What', ['startswith'], []
という情報が返されます。
***setup_joins [#ea9d2415]
setup_joinsが呼ばれる前に、前回も出てきたget_initial_alia...
で、setup_joins。とは言うものの、今回はJOINに関係している...
#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:
# 省略
return final_field, targets, opts, joins, path
}}
結局、names_to_pathの戻り値がほぼそのまま返されます。
question_textのCharField, (question_textのCharField,), Q...
***trim_joins [#u97d9401]
trim_joins。同じくfor文は無視します。
#code(Python){{
def trim_joins(self, targets, joins, path):
"""
The 'target' parameter is the final field being j...
is the full list of join aliases. The 'path' cont...
used to create the joins.
Returns the final target field and table alias an...
joins.
We will always trim any direct join if we have th...
available already in the previous table. Reverse ...
trimmed as we don't know if there is anything on ...
the join.
"""
joins = joins[:]
for pos, info in enumerate(reversed(path)):
# 省略
return targets, joins[-1], joins
}}
(question_textのCharField,), QuestionのBaseTable, [Quest...
が返されます。
***build_lookup [#s13390bf]
最後にbuild_lookupです。
#code(Python){{
def build_lookup(self, lookups, lhs, rhs):
"""
Tries to extract transforms and lookup from given...
The lhs value is something that works like SQLExp...
The rhs value is what the lookup is going to comp...
The lookups is a list of names to extract using g...
and get_transform().
"""
lookups = lookups[:]
while lookups:
name = lookups[0]
# If there is just one part left, try first g...
# that if the lhs supports both transform and...
# name, then lookup will be picked.
if len(lookups) == 1:
final_lookup = lhs.get_lookup(name)
if not final_lookup:
# We didn't find a lookup. We are goi...
# the name as transform, and do an Ex...
# it.
lhs = self.try_transform(lhs, name, l...
final_lookup = lhs.get_lookup('exact')
return final_lookup(lhs, rhs)
lhs = self.try_transform(lhs, name, lookups)
lookups = lookups[1:]
}}
lhsはCharFieldなのでとget_lookupを探しても定義はありませ...
#code(Python){{
class Field(RegisterLookupMixin):
}}
というわけでこちらに書いてありそうです。query_utilsモジュ...
#code(Python){{
def get_lookup(self, lookup_name):
from django.db.models.lookups import Lookup
found = self._get_lookup(lookup_name)
if found is None and hasattr(self, 'output_field'):
return self.output_field.get_lookup(lookup_na...
if found is not None and not issubclass(found, Lo...
return None
return found
def _get_lookup(self, lookup_name):
try:
return self.class_lookups[lookup_name]
except KeyError:
# 省略
except AttributeError:
# This class didn't have any class_lookups
pass
return None
}}
class_lookupsへの登録はクラスメソッドとして定義されていま...
#code(Python){{
@classmethod
def register_lookup(cls, lookup, lookup_name=None):
if lookup_name is None:
lookup_name = lookup.lookup_name
if 'class_lookups' not in cls.__dict__:
cls.class_lookups = {}
cls.class_lookups[lookup_name] = lookup
return lookup
}}
次の疑問は、「仕組みはわかったけど、じゃあいつlookupが登...
#code(Python){{
class StartsWith(PatternLookup):
lookup_name = 'startswith'
prepare_rhs = False
def process_rhs(self, qn, connection):
rhs, params = super(StartsWith, self).process_rhs...
if params and not self.bilateral_transforms:
params[0] = "%s%%" % connection.ops.prep_for_...
return rhs, params
Field.register_lookup(StartsWith)
}}
というわけで、startswithに対応するLookupが登録されます。
で、StartsWithオブジェクトが作られてようやくfilterメソッ...
構築されたStartsWithオブジェクトはbuild_filter→_add_q→add...
self.where
StartsWith(lhs=question_textのCol, rhs='What')
*WhereNode [#c85bf6f1]
オブジェクトが構築できたら、後は前回見たように処理が行わ...
#code(Python){{
def as_sql(self, compiler, connection):
"""
Returns the SQL version of the where clause and t...
substituted in. Returns '', [] if this node match...
None, [] if this node is empty, and raises EmptyR...
node can't match anything.
"""
result = []
result_params = []
if self.connector == AND:
full_needed, empty_needed = len(self.children...
else:
full_needed, empty_needed = 1, len(self.child...
for child in self.children:
try:
sql, params = compiler.compile(child)
except EmptyResultSet:
empty_needed -= 1
else:
if sql:
result.append(sql)
result_params.extend(params)
else:
full_needed -= 1
# 省略
conn = ' %s ' % self.connector
sql_string = conn.join(result)
if sql_string:
if self.negated:
# Some backends (Oracle at least) need pa...
# around the inner SQL in the negated cas...
# inner SQL contains just a single expres...
sql_string = 'NOT (%s)' % sql_string
elif len(result) > 1:
sql_string = '(%s)' % sql_string
return sql_string, result_params
}}
子ノードをcompileしてつなげているだけです。
**StartsWith [#e915ee33]
では子ノードのStartsWithについて見ていきましょう。まずは...
#code(Python){{
class StartsWith(PatternLookup):
class PatternLookup(BuiltinLookup):
class BuiltinLookup(Lookup):
}}
BuildinLookupまで行くとas_sqlメソッドが書かれています。
#code(Python){{
def as_sql(self, compiler, connection):
lhs_sql, params = self.process_lhs(compiler, conn...
rhs_sql, rhs_params = self.process_rhs(compiler, ...
params.extend(rhs_params)
rhs_sql = self.get_rhs_op(connection, rhs_sql)
return '%s %s' % (lhs_sql, rhs_sql), params
}}
process_lhsはas_sqlのすぐ上に書かれています。DBMSに応じた...
process_rhs。StartsWithでオーバーライドされています。bila...
#code(Python){{
def process_rhs(self, qn, connection):
rhs, params = super(StartsWith, self).process_rhs...
if params and not self.bilateral_transforms:
params[0] = "%s%%" % connection.ops.prep_for_...
return rhs, params
}}
で親クラス、Lookupの方のprocess_rhs。ただの文字列なのでge...
#code(Python){{
def process_rhs(self, compiler, connection):
value = self.rhs
if self.bilateral_transforms:
# 省略
# Due to historical reasons there are a couple of...
# ways to produce sql here. get_compiler is likel...
# instance, _as_sql QuerySet and as_sql just some...
# as_sql. Finally the value can of course be just...
# Python value.
if hasattr(value, 'get_compiler'):
value = value.get_compiler(connection=connect...
if hasattr(value, 'as_sql'):
sql, params = compiler.compile(value)
return '(' + sql + ')', params
if hasattr(value, '_as_sql'):
sql, params = value._as_sql(connection=connec...
return '(' + sql + ')', params
else:
return self.get_db_prep_lookup(value, connect...
def get_db_prep_lookup(self, value, connection):
return ('%s', [value])
}}
get_rhs_opはPatternLookupにも書かれていますがBuiltinLooku...
#code(Python){{
def get_rhs_op(self, connection, rhs):
return connection.operators[self.lookup_name] % rhs
}}
sqlite3の場合、operatorsは以下の通りです。つまり、LIKEを...
#code(Python){{
operators = {
'exact': '= %s',
'iexact': "LIKE %s ESCAPE '\\'",
'contains': "LIKE %s ESCAPE '\\'",
'icontains': "LIKE %s ESCAPE '\\'",
'regex': 'REGEXP %s',
'iregex': "REGEXP '(?i)' || %s",
'gt': '> %s',
'gte': '>= %s',
'lt': '< %s',
'lte': '<= %s',
'startswith': "LIKE %s ESCAPE '\\'",
'endswith': "LIKE %s ESCAPE '\\'",
'istartswith': "LIKE %s ESCAPE '\\'",
'iendswith': "LIKE %s ESCAPE '\\'",
}
}}
最終的に、WhereNodeがコンパイルされると以下の文字列(とパ...
'"polls_question"."question_text" LIKE %s ESCAPE \'\\\''...
*おわりに [#id175bb9]
今回はfilterメソッドを使い検索条件が指定された時の処理を...
今回(と前回)、JOINについては実際には発生しないのでばっ...
ページ名: