---
title: "カスタムのルックアップを書く"
version: 5.0
locale: ja
source: https://docs.djangoproject.com/ja/5.0/howto/custom-lookups/
canonical: https://djangodocs.dev/ja/5.0/howto/custom-lookups/
---
# カスタムのルックアップを書く

Django には (たとえば `exact` や `icontains` などの) フィルタリングを行う [組み込みのルックアップ](/ja/5.0/ref/models/querysets/#field-lookups) がたくさんあります。このドキュメントでは、カスタムルックアップを作る方法や、既存のルックアップの動作を変更する方法について説明します。ルックアップの API リファレンスについては [ルックアップ API リファレンス](/ja/5.0/ref/models/lookups/) を参照してください。

## ルックアップの例

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

```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 (右辺) を表します。左辺は通常フィールド参照ですが、[クエリ式 API](/ja/5.0/ref/models/lookups/#query-expression) を実装するものであれば何でもかまいません。右辺はユーザーから与えられた値です。`Author.objects.filter(name__ne='Jack')` の例では、左辺は `Author` モデルの `name` フィールドへの参照で、右辺は `'Jack'` です。

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

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

## トランスフォーム（`Transform`: 変換）の例

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

> **Note**
>
> この例は多少工夫がされていますが、データベースバックエンドに依存しない方法で、そしてすでに 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を生成します。

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

`LookUp` の代わりに `Transform` を使うことで、それ以降のルックアップを連結できます。つまり、`Experiment.objects.filter(change__abs__lt=27)` は以下のようなSQLを生成します。

```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 を生成します。

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

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

```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` を使うことができます)。

そのため、`Experiment.objects.filter(change__abs__lt=27)` に次のような SQL を生成させたいです。

```sql
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では変換ができないためです。

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

## 双方向のトランスフォームの例

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

ここで、大文字小文字を区別しないトランスフォームを試してみましょう。Djangoには既に大文字小文字を区別しないルックアップが多数組み込まれているので、プラクティスとしては良い例ではありません。しかし、データベースにとらわれない双方向のトランスフォームとしては良い例でしょう。

SQL関数 `UPPER()` を使用して比較前に値を変換する `UpperCase` トランスフォーマーを定義します。 [`bilateral = True`](/ja/5.0/ref/models/lookups/#django.db.models.Transform.bilateral) を定義して、この変換が `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")` は次のような大文字小文字を区別しないクエリを生成するようになります。

```sql
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.removeprefix("x"))
            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` に対して `exact` ルックアップを行います。すべての呼び出しシーケンスは常に `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')` をフォールバックとして呼び出すこと。
