# Django 4.1.8.dev20230308103347 ドキュメント

# How to write custom lookups

Django には (たとえば `exact` や `icontains` などの) フィルタリングを行う built-in lookups が数多くあります。このドキュメントでは、カスタムルックアップをどのように作るかや、既存のルックアップの動作をどのようにして変更するかについて説明します。ルックアップの API リファレンスについては Lookup API reference を参照してください。

## ルックアップの例

簡単なカスタムルックアップから始めましょう。``exact``と逆の動作をするカスタムルックアップ``ne``を実装していきます。``Author.objects.filter(name__ne='Jack')``は次のSQLに変換されます。

    "author"."name" <> 'Jack'

この SQL はバックエンドに依存しない書き方になっているため、別のデータベースについて心配する必要はありません。

カスタムルックアップを動作させるためには2つのステップが必要です。まず最初にルックアップを実装し、続いてDjangoに実装したルックアップを認識させる必要があります。

    from django.db.models import Lookup

    class NotEqual(Lookup):
        lookup_name = 'ne'

        def as_sql(self, compiler, connection):
            lhs, lhs_params = self.process_lhs(compiler, connection)
            rhs, rhs_params = self.process_rhs(compiler, connection)
            params = lhs_params + rhs_params
            return '%s <> %s' % (lhs, rhs), params

`NotEqual` ルックアップを登録するために、ルックアップを有効にしたいフィールドクラスの `register_lookup` を呼び出す必要があります。今回実装したルックアップは `Field` クラスの全てのサブクラスに適用できるため、 `Field` クラスに直接登録しましょう。

    from django.db.models import Field
    Field.register_lookup(NotEqual)

ルックアップは、デコレータパタンを使っても登録できます。

    from django.db.models import Field

    @Field.register_lookup
    class NotEqualLookup(Lookup):
        # ...

これ以降、どのようなフィールドであっても `foo` に対しては `foo__ne` が利用できます。なお、これを用いるクエリーセットを生成するならば、それ以前にルックアップの登録を行っておく必要があります。実装する場所は `models.py` ファイル内で行ってもよいし、`AppConfig` 内の `ready()` メソッドにてルックアップの登録を行うのでも構いません。

実装の詳細について見ると、最初に必要な属性は `lookup_name` です。この属性があると、ORM が `name_ne` を解釈できるようになり、`NotEqual` を使って SQL を生成できます。慣習として、こうした名前は常に小文字のアルファベットのみからなる文字列にしますが、必ず守らなければならない制約は、文字列 `__` を決して含んではならないということです。

次に、"as_sql" メソッドを定義する必要があります。これには "compiler" という"SQL Compiler" オブジェクトと、アクティブなデータベース接続を必要とします。"SQL Compiler" オブジェクトについてのドキュメントはありませんが、それがSQL文字列を含むタプルと文字列に挿入されるパラメータを返す``compile()`` メソッドを持つ、ということだけ知っていれば十分です。ほとんどの場合、これを直接用いる必要はなく、`process_lhs()` および `process_rhs()` に渡すことができます。

2つの値に対して動作するルックアップ、 `lhs` と `rhs` はleft-hand side（左辺）とright-hand side（右辺） を表します。左辺は通常フィールド参照ですが、 query expression API を実装するものであれば何でもかまいません。右辺はユーザーから与えられた値です。 `Author.objects.filter(name__ne='Jack')` の例では、左辺は `Author` モデルの `name` フィールドへの参照で、右辺は `'Jack'` です。

`process_lhs` と``process_rhs`` を呼び出して、前述の `compiler` オブジェクトを使用してSQLに必要な値に変換します。 これらのメソッドは、 `as_sql` メソッドから戻る必要があるのと同じように、SQLとそのSQLに補間されるパラメーターを含むタプルを返します。 上記の例では、 `process_lhs` は `('"author"."name"', [])` を返し、`process_rhs` は `('"%s"', ['Jack'])` を返します 。 この例では、左側にパラメーターはありませんが、これは所有するオブジェクトに依存するため、返すパラメーターにそれらを含める必要があります。

最後に、これらの部分を `` <> `` を使ってSQL式にまとめ、クエリのすべてのパラメータを指定します。その後、生成されたSQL文字列とパラメータを含むタプルを返します。

## 変換の例

上記のカスタムルックアップは素晴らしいですが、場合によってはルックアップを一緒にチェーンできるようにしたいことがあります。 たとえば、`abs()` 演算子を利用したいアプリケーションを構築しているとしましょう。 開始値、終了値モデルおよび変更 (start - end) を記録する `Experiment` モデルがあります。 変化が一定の量に等しい (`Experiment.objects.filter(change__abs=27)`)、またはそれが一定の量を超えなかった (`Experiment.objects.filter(change__abs__lt=27)`) すべての Experiment を見つけたいと思います。

注釈

この例は多少工夫がされていますが、データベースバックエンドに依存しない方法で、そしてすでにDjangoにある機能を複製することなく可能な機能の範囲をうまく示しています。

私たちは `AbsoluteValue` 変換器を書くことから始めましょう。これはSQL関数 `ABS()` を使って比較の前に値を変換します:

    from django.db.models import Transform

    class AbsoluteValue(Transform):
        lookup_name = 'abs'
        function = 'ABS'

次に、`IntegerField` に登録しましょう:

    from django.db.models import IntegerField
    IntegerField.register_lookup(AbsoluteValue)

これまでの処理で、クエリを使うことができるようになりました。 `Experiment.objects.filter(change__abs=27)` は次のSQLを生成します。

    SELECT ... WHERE ABS("experiments"."change") = 27

By using `Transform` instead of `Lookup` it means we are able to chain further lookups afterward. So `Experiment.objects.filter(change__abs__lt=27)` will generate the following SQL:

    SELECT ... WHERE ABS("experiments"."change") < 27

他にルックアップが指定されていない場合、Djangoは `change__abs=27` を `change__abs__exact=27` として解釈します。

これにより``ORDER BY``句と`DISTINCT ON``句の使用も可能です。例えば``Experiment.objects.order_by('change__abs')``は以下のSQLを生成します。

    SELECT ... ORDER BY ABS("experiments"."change") ASC

そして、PosgreSQLのようにフィールドをDISTINCTすることもサポートしています。 ``Experiment.objects.distinct('change__abs')``は以下のSQLを生成します。

    SELECT ... DISTINCT ON ABS("experiments"."change")

`Transform``が適用された後、どのルックアップが許可されるかを探すとき、Djangoは ``output_field` 属性を使用します。 ここでは変更しないため、これを指定する必要はありませんでしたが、より複雑な型（たとえば、原点に関連する点や複素数）を表すフィールドに `AbsoluteValue` を適用すると仮定します。 次に、変換がさらなる検索のために `FloatField` 型を返すように指定したいかもしれません。 これは、トランスフォームに `output_field` 属性を追加することで実行できます:

    from django.db.models import FloatField, Transform

    class AbsoluteValue(Transform):
        lookup_name = 'abs'
        function = 'ABS'

        @property
        def output_field(self):
            return FloatField()

これは``abs__lte``のようなそれ以降の検索が``FloatField``の場合と同じように振る舞うことを保証します。

## 効率的な `abs__lt` 検索を書く

上記の `abs` ルックアップを使用するとき、生成されたSQLはインデックスを効率的に使用しないことがあります。特に、`change__abs__lt=27` を使うとき、これは `change__gt=-27` と `change__lt=27` と同じです。（ `` lte`` の場合はSQLの `BETWEEN` を使うことができます）。

そのため、次のようなSQLを生成するためには、`Experiment.objects.filter(change__abs__lt=27)` を使います。

    SELECT .. WHERE "experiments"."change" < 27 AND "experiments"."change" > -27

実装は次のようになります。

    from django.db.models import Lookup

    class AbsoluteValueLessThan(Lookup):
        lookup_name = 'lt'

        def as_sql(self, compiler, connection):
            lhs, lhs_params = compiler.compile(self.lhs.lhs)
            rhs, rhs_params = self.process_rhs(compiler, connection)
            params = lhs_params + rhs_params + lhs_params + rhs_params
            return '%s < %s AND %s > -%s' % (lhs, rhs, lhs, rhs), params

    AbsoluteValue.register_lookup(AbsoluteValueLessThan)

注目すべきことがいくつかあります。 まず、`AbsoluteValueLessThan` は `process_lhs()` を呼び出していません。 代わりに、 `AbsoluteValue` によって行われた `lhs` の変換をスキップし、元の `lhs` を使用します。 つまり、`ABS("experiments"."change")` ではなく、 `"experiments"."change"` を取得する必要があります。`AbsoluteValueLessThan` は `AbsoluteValue` ルックアップからのみアクセスできるため、 `self.lhs.lhs` を直接参照することは安全です。つまり、 `lhs` は常に `AbsoluteValue` のインスタンスです 。

また、クエリで両側が複数回使用されているので、paramsは `lhs_params` と `rhs_params` を複数回含む必要があることにも注意してください。

最後の問い合わせはデータベース内で直接反転（`27` から `-27`）を行います。これを行う理由は、 `self.rhs` が普通の整数値（例えば `F()` 参照）以外のものである場合、Pythonでは変換ができないためです。

注釈

実際のところ、 `__abs` を使った検索は、このように範囲クエリとして実装することができます。そして、ほとんどのデータベースバックエンドでは、インデックスを利用できるようにするほうが賢明です。しかしPostgreSQLでは、 `abs(change)` にインデックスを追加することをお勧めします。これにより、これらのクエリは非常に効率的になります。

## 双方向変換の例

前に説明した `AbsoluteValue` の例は、ルックアップの左側に適用される変換です。 変換を左側と右側の両方に適用したい場合があります。 たとえば、一部のSQL関数に左右されない左側と右側の同等性に基づいてクエリセットをフィルタリングする場合です。

ここで、case-insensitiveな変換を試してみましょう。Djangoには既にcase-insensitiveな検索が多数組み込まれているので、プラクティスとしては良い例ではありません。しかし、データベースにとらわれない双方向変換としては良い例でしょう。

SQL関数 `UPPER()` を使用して比較前に値を変換する `UpperCase` トランスフォーマーを定義します。`bilateral = True` を定義して、この変換が `lhs` と ``rhs``の両方に適用されることを示します:

    from django.db.models import Transform

    class UpperCase(Transform):
        lookup_name = 'upper'
        function = 'UPPER'
        bilateral = True

次に、登録しましょう:

    from django.db.models import CharField, TextField
    CharField.register_lookup(UpperCase)
    TextField.register_lookup(UpperCase)

そして、``Author.objects.filter(name__upper="doe")`は次のようなcase-insensitiveなクエリを生成します。

    SELECT ... WHERE UPPER("author"."name") = UPPER('doe')

## 既存の検索向けに代替実装を書く

異なるデータベースベンダーは、同じ操作に対して異なるSQLを必要とする場合があります。 この例では、MySQLのNotEqual演算子のカスタム実装を書き換えます。`<>` の代わりに `!=` 演算子を使用します。 （実際には、Djangoがサポートするすべての公式データベースを含め、ほとんどすべてのデータベースが両方をサポートしていることに注意してください）。

`as_mysql` メソッドで `NotEqual` のサブクラスを作成することで、特定のバックエンドの振る舞いを変えることができます。

    class MySQLNotEqual(NotEqual):
        def as_mysql(self, compiler, connection, **extra_context):
            lhs, lhs_params = self.process_lhs(compiler, connection)
            rhs, rhs_params = self.process_rhs(compiler, connection)
            params = lhs_params + rhs_params
            return '%s != %s' % (lhs, rhs), params

    Field.register_lookup(MySQLNotEqual)

そしてそれを `Field` に登録することができます。元の `NotEqual` クラスが同じ `lookup_name` を持つので代わりに使われます。

クエリをコンパイルするとき、Djangoは最初に `as_%s % connection.vendor` メソッドを探し、それから `as_sql` にフォールバックします。組み込みのバックエンドのベンダ名は `sqlite`、`postgresql`、`oracle` および `mysql` です。

## Django がルックアップと変換のいずれを使うかを決定する仕組み

場合によっては、修正するのではなく、渡された名前に基づいて、どの `Transform` または `Lookup` を返すかを動的に変更したい場合があります。 例として、座標または任意の次元を格納するフィールドがあり、 `.filter(coords__x7=4)` のような構文で、7番目の座標の値が4であるオブジェクトを返すことができます。 これを行うには、次のようなもので``get_lookup`` をオーバーライドします:

    class CoordinatesField(Field):
        def get_lookup(self, lookup_name):
            if lookup_name.startswith('x'):
                try:
                    dimension = int(lookup_name[1:])
                except ValueError:
                    pass
                else:
                    return get_coordinate_lookup(dimension)
            return super().get_lookup(lookup_name)

それから、適切な `dimension` の値を扱う `Lookup` サブクラスを返すように `get_coordinate_lookup` を適切に定義します。

`get_transform()` と呼ばれる同様の名前のメソッドがあります。`get_lookup()` は常に `Lookup` サブクラスを返し、 `get_transform()` は``Transform`` サブクラスを返す必要があります。 `Transform` オブジェクトはさらにフィルター処理でき、`Lookup` オブジェクトはフィルター処理できないことに注意してください。

フィルタリングするときに、解決するルックアップ名が1つだけ残っている場合、`Lookup` を探します。 複数の名前がある場合、`Transform` を探します。 名前が1つしかなく、`Lookup` が見つからない状況では、`Transform` を探してから、その `Transform` で `正確な` ルックアップを探します。 すべての呼び出しシーケンスは常に``Lookup`` で終わります。 明確にするためです:

- `.filter(myfield__mylookup)` は `myfield.get_lookup('mylookup')` を呼び出します。
- `.filter(myfield__mytransform__mylookup)` は ``myfield.get_transform('mytransform')``を呼び出し、次に ``mytransform.get_lookup('mylookup')``を呼び出します。
- `.filter(myfield__mytransform)` は最初に `myfield.get_lookup('mytransform')` を呼び出しますが、これは失敗するので、`myfield.get_transform('mytransform')` と `mytransform.get_lookup('exact')` をフォールバックとして呼び出します。