---
title: "自定义查找"
version: 2.1
locale: zh-hans
source: https://docs.djangoproject.com/zh-hans/2.1/howto/custom-lookups/
canonical: https://djangodocs.dev/zh-hans/2.1/howto/custom-lookups/
---
# 自定义查找

Django提供了各种各样的ref：用于过滤的内置查找（例如，“exact”和“icontains”）。 本文档解释了如何编写自定义查找以及如何更改已有查找的工作方式。 有关lookup的API参考，请参阅：doc：/ ref / models / lookups。

## 一个简单的查找示例

让我们从一个简单的自定义查找开始。我们编写一个自定义查找 ne ，它与 exact 相反。
Author.objects.filter(name\_\_ne='Jack') 将会转换成 SQL:

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

SQL 会自动适配不同的后端, 所以我们不需要对使用不同的数据库担心.

完成此工作需要两个步骤。第一首先我们需要实现查找，第二我们需要将它告知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.fields import Field
Field.register_lookup(NotEqual)
```

查找注册也可以用修饰模式来完成

```
from django.db.models.fields import Field

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

现在我们可以用“foo\_\_ne”来代表“foo”的任意字段。你需要确保在创建任意的queryset之前使用它。你可以在“models.py”文件内设置它，或者在“AppConfig”内使用“ready()”方法注册它。

仔细观察实现过程，最开始我们需要“lookup\_name”这个属性。这个可以保证ORM理解如何编译“name\_ne”和使用“NotEqual”来建立结构化查询语言SQL。按照惯例，这些名字“name\_ne”是小写字母字符串，但是很麻烦的是必须有“\_\_”字符串

之后我们需要定义一个“as\_sql”方法。这方法需要一个“SQLCompiler” 对象, 被叫做编译器，和一个有效的数据库连接。“SQLCompller”对象没有文档，我们只需要知道它有一个compile()方法可以返回一个元组包括SQL字符串，和插入这个字符串的参数。大部分情况下，你不需要直接使用这个对象你可以把它传送给“process\_lhs()”和“process\_rhs()”

“Lookup”工作依靠两个值, “lhs”和“rhs”,代表左右手边，左手是一个字段参考，但它可以是任何来解释ref：应用程序的检索表达。右手是一个用户给的数值。举个例子：`Author.objects.filter(name__ne='Jack')`，左手是一个参考对应着Author模型的名字，“Jack”是右手端。

我们调用“process\_lhs”和“process\_rhs”转化他们成为我们想要的用来检索的值通过之前我们提到的“编译器”。这个方法返回一个元组包含SQL数据库和插入SQL数据库一些参数，刚好就是我们‘as\_sql’需要返回的。使用前面的例子，“process\_lhs”返回\`\`('"author"."name"', \[\])\`\` ，“process\_lhs”返回\`\`('"%s"', \['Jack'\])\`\`.在这个例子里面没有左手边的参数，但是这需要看情况而定，我们还需要包括这些参数当我们返回的时候。

最后，我们将这些部分组合成一个带有\`\`\<\>\`\`的SQL表达式，并提供查询的所有参数。 然后我们返回一个包含生成的SQL字符串和参数的元组。

## 让我们举一个简单的转换器示例

上面的自定义查找没问题，但在某些情况下，您可能希望能够将一些查找链接在一起。 例如，假设我们正在构建一个我们想要制作一个带有\`\`abs（）\`\`运算符的应用程序。 我们有一个\`\`Experiment\`\`模型，它记录起始值，结束值和变化（开始 - 结束）。 我们想找到所有在Experiment模型中change属性等于一定数量的（`Experiment.objects.filter（change__abs = 27）`），或者在Experiment模型中change属性没有超过一定数量的（`Experiment.objects.filter（change__abs__lt= 27）`）。

> **Note**
>
> 这个例子有点刻意，但它很好地演示了以数据库后端独立方式可能实现的功能范围，并且没有重复Django中的功能

我们将从编写一个\`\`AbsoluteValue\`\`变换器开始。 这将使用SQL中的\`\`ABS（）\`\`函数在比较进行之前首先转换值:

```
from django.db.models import Transform

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

下一步, 让我们为其注册 `IntrgerField`:

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

我们现在可以运行之前的查询。 Experiment.objects.filter（change\_\_abs = 27）\`\`将生成以下SQL

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

通过使用\`\`Transform\`\`而不是\`\`Lookup\`\`，这意味着我们可以在之后链接进一步的查找。 所以\`\`Experiment.objects.filter（change\_\_abs\_\_lt = 27）\`\`将生成以下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'）\`\`会生成:

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

在支持字段去重的数据库（例如PostgreSQL）上，语句\`\`Experiment.objects.distinct（'change\_\_abs'）\`\`会生成:

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

> **Changed in Django 2.1**
>
> 上两段所提到的排序与去重的支持被加入了。

当我们在应用\`\`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:

```
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\`\`。 也就是说，我们希望得到\`\`"experiments"."change"```而不是``ABS（“实验”。“改变”）```。 直接引用\`\`self.lhs.lhs\`\`是安全的，因为\`\`AbsoluteValueLessThan\`\`只能从\`\`AbsoluteValue\`\`查找访问，即\`\`lhs\`\`总是\`\`AbsoluteValue\`的实例\`。

另请注意，由于在查询中多次使用双方，所以需要多次包含“lhs\_params”和“rhs\_params”的参数。

最后的查询直接在数据库中进行反转（ 27 到 -27 ）。 这样做的原因是，如果 self.rhs 不是普通的整数值（例如 F() 引用），我们就不能在Python中进行转换。

> **Note**
>
> In fact, most lookups with `__abs` could be implemented as range queries
> like this, and on most database backends it is likely to be more sensible to
> do so as you can make use of the indexes. However with PostgreSQL you may
> want to add an index on `abs(change)` which would allow these queries to
> be very efficient.

## A bilateral transformer example

The `AbsoluteValue` example we discussed previously is a transformation which
applies to the left-hand side of the lookup. There may be some cases where you
want the transformation to be applied to both the left-hand side and the
right-hand side. For instance, if you want to filter a queryset based on the
equality of the left and right-hand side insensitively to some SQL function.

Let's examine the simple example of case-insensitive transformation here. This
transformation isn't very useful in practice as Django already comes with a bunch
of built-in case-insensitive lookups, but it will be a nice demonstration of
bilateral transformations in a database-agnostic way.

We define an `UpperCase` transformer which uses the SQL function `UPPER()` to
transform the values before comparison. We define
[`bilateral = True`](/zh-hans/2.1/ref/models/lookups/#django.db.models.Transform.bilateral) to indicate that
this transformation should apply to both `lhs` and `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")\`\`查询集会生成一个像这样的不区分大小写的查询:

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

## 为现有的查找编写代替实现

Sometimes different database vendors require different SQL for the same
operation. For this example we will rewrite a custom implementation for
MySQL for the NotEqual operator. Instead of `<>` we will be using `!=`
operator. (Note that in reality almost all databases support both, including
all the official databases supported by Django).

我们可以通过使用 `as_mysql` 方法创建 `NotEqual` 的子类来更改特定后端的行为:

```
class MySQLNotEqual(NotEqual):
    def as_mysql(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

Field.register_lookup(MySQLNotEqual)
```

We can then register it with `Field`. It takes the place of the original
`NotEqual` class as it has the same `lookup_name`.

When compiling a query, Django first looks for `as_%s % connection.vendor`
methods, and then falls back to `as_sql`. The vendor names for the in-built
backends are `sqlite`, `postgresql`, `oracle` and `mysql`.

## How Django determines the lookups and transforms which are used

In some cases you may wish to dynamically change which `Transform` or
`Lookup` is returned based on the name passed in, rather than fixing it. As
an example, you could have a field which stores coordinates or an arbitrary
dimension, and wish to allow a syntax like `.filter(coords__x7=4)` to return
the objects where the 7th coordinate has value 4. In order to do this, you
would override `get_lookup` with something like:

```
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)
```

You would then define `get_coordinate_lookup` appropriately to return a
`Lookup` subclass which handles the relevant value of `dimension`.

There is a similarly named method called `get_transform()`. `get_lookup()`
should always return a `Lookup` subclass, and `get_transform()` a
`Transform` subclass. It is important to remember that `Transform`
objects can be further filtered on, and `Lookup` objects cannot.

When filtering, if there is only one lookup name remaining to be resolved, we
will look for a `Lookup`. If there are multiple names, it will look for a
`Transform`. In the situation where there is only one name and a `Lookup`
is not found, we look for a `Transform` and then the `exact` lookup on that
`Transform`. All call sequences always end with a `Lookup`. To clarify:

- `.filter(myfield__mylookup)` 将会调用 `myfield.get_lookup('mylookup')`.
- `.filter(myfield__mytransform__mylookup)` 将会调用 `myfield.get_transform('mytransform')`, 接着调用 `mytransform.get_lookup('mylookup')`.
- `.filter(myfield__mytransform)` will first call
  `myfield.get_lookup('mytransform')`, which will fail, so it will fall back
  to calling `myfield.get_transform('mytransform')` and then
  `mytransform.get_lookup('exact')`.
