“Django/docs/3.0.x/howto/custom-lookups”的版本间差异

来自菜鸟教程
Django/docs/3.0.x/howto/custom-lookups
跳转至:导航、​搜索
(autoload)
 
(Page commit)
 
第1行: 第1行:
 +
{{DISPLAYTITLE:自定义查找 — Django 文档}}
 
<div id="custom-lookups" class="section">
 
<div id="custom-lookups" class="section">
  
= 自定义查询器 =
+
= 自定义查找 =
  
Django 提供了各种各样的 [[../../ref/models/querysets#field-lookups|<span class="std std-ref">内置查询器</span>]] (例如, <code>exact</code> 和 <code>icontains</code> )。本文档解释了如何编写自定义查询器以及如何更改已有查询器的工作方式。 有关 lookup 的 API 参考,请参阅 [[../../ref/models/lookups|<span class="doc">Lookup API reference</span>]]。
+
Django 提供了各种各样的 [[../../ref/models/querysets#field-lookups|内置查找]] 用于过滤(例如,<code>exact</code> 和 <code>icontains</code>)。 本文档解释了如何编写自定义查找以及如何更改现有查找的工作方式。 有关查找的 API 参考,请参阅 [[../../ref/models/lookups|Lookup API 参考]] 。
  
 
<div id="a-lookup-example" class="section">
 
<div id="a-lookup-example" class="section">
  
== 一个查询器示例。 ==
+
== 查找示例 ==
  
让我们以一个小巧的自定义查询器为例。我们将书写一个名为 <code>ne</code> 的自定义查询器,它的效果与 <code>exact</code> 相反。语句 <code>Author.objects.filter(name__ne='Jack')</code>  将会翻译为下面的 SQL 语句:
+
让我们从一个小的自定义查找开始。 我们将编写一个自定义查找 <code>ne</code>,其作用与 <code>exact</code> 相反。 <code>Author.objects.filter(name__ne='Jack')</code> 将转换为 SQL:
  
 
<div class="highlight-sql notranslate">
 
<div class="highlight-sql notranslate">
第15行: 第16行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>&quot;author&quot;.&quot;name&quot; &lt;&gt; 'Jack'</pre>
+
<syntaxhighlight lang="sql">"author"."name" <> 'Jack'</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
SQL 会自动适配不同的后端,所以我们不需要为使用不同的数据库而担心。
+
这个 SQL 是后端独立的,所以我们不需要担心不同的数据库。
  
要让它生效需要两个步骤,首先我们需要实现该查询器,然后我们需要告诉 Django 有关它的信息。
+
完成这项工作有两个步骤。 首先我们需要实现查找,然后我们需要告诉 Django:
  
 
<div class="highlight-default notranslate">
 
<div class="highlight-default notranslate">
第28行: 第29行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>from django.db.models import Lookup
+
<syntaxhighlight lang="python">from django.db.models import Lookup
  
 
class NotEqual(Lookup):
 
class NotEqual(Lookup):
第37行: 第38行:
 
         rhs, rhs_params = self.process_rhs(compiler, connection)
 
         rhs, rhs_params = self.process_rhs(compiler, connection)
 
         params = lhs_params + rhs_params
 
         params = lhs_params + rhs_params
         return '%s &lt;&gt; %s' % (lhs, rhs), params</pre>
+
         return '%s <> %s' % (lhs, rhs), params</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
为了注册 <code>NotEqual</code> 查询器,我们需要在对应需要该查询器的字段类中调用 <code>register_lookup</code> 方法。在该情形下,该查询器作用在所有的 <code>Field</code> 子类,所以我们直接将它注册在 <code>Field</code> 中:
+
要注册 <code>NotEqual</code> 查找,我们需要在我们希望查找可用的字段类上调用 <code>register_lookup</code>。 在这种情况下,查找对所有 <code>Field</code> 子类都有意义,所以我们直接用 <code>Field</code> 注册它:
  
 
<div class="highlight-default notranslate">
 
<div class="highlight-default notranslate">
第48行: 第49行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>from django.db.models.fields import Field
+
<syntaxhighlight lang="python">from django.db.models.fields import Field
Field.register_lookup(NotEqual)</pre>
+
Field.register_lookup(NotEqual)</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
查询器注册也可以用修饰模式来完成:
+
查找注册也可以使用装饰器模式完成:
  
 
<div class="highlight-default notranslate">
 
<div class="highlight-default notranslate">
第60行: 第61行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>from django.db.models.fields import Field
+
<syntaxhighlight lang="python">from django.db.models.fields import Field
  
 
@Field.register_lookup
 
@Field.register_lookup
 
class NotEqualLookup(Lookup):
 
class NotEqualLookup(Lookup):
     # ...</pre>
+
     # ...</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
现在我们可以用 <code>foo__ne</code> 来代表 <code>foo</code> 的任意字段。你需要确保注册行为发生在创建任意的 queryset 之前。你可以在 <code>models.py</code> 文件内设置它,或者在 <code>AppConfig</code> 的 <code>ready()</code> 方法中注册它。
+
我们现在可以将 <code>foo__ne</code> 用于任何字段 <code>foo</code>。 在尝试使用它创建任何查询集之前,您需要确保此注册发生。 您可以将实现放在 <code>models.py</code> 文件中,或在 <code>AppConfig</code> 的 <code>ready()</code> 方法中注册查找。
  
仔细观察实现过程,第一个要求的属性是 <code>lookup_name</code>。它能让 ORM 理解如何编译 <code>name_ne</code> 并使用 <code>NotEqual</code> 来建立 SQL 语句。按照惯例,这些名字应该总是仅包含小写字母的字符串,但是绝对不能包含双下划线 <code>__</code>。
+
仔细查看实现,第一个必需的属性是 <code>lookup_name</code>。 这允许 ORM 了解如何解释 <code>name__ne</code> 并使用 <code>NotEqual</code> 生成 SQL。 按照惯例,这些名称总是只包含字母的小写字符串,但唯一的硬性要求是它不能包含字符串 <code>__</code>。
  
之后我们需要定义 <code>as_sql</code> 方法。此方法需要一个 <code>SQLCompiler</code> 对象, 被叫做 <code>compiler</code>,和一个有效的数据库连接。<code>SQLCompller</code> 对象没有文档,我们只需要知道它有一个 <code>compile()</code> 方法可以返回一个包括 SQL 字符串的元组,和插入这个字符串的参数。大部分情况下,你不需要直接使用这个对象你可以把它传送给 <code>process_lhs()</code> 和 <code>process_rhs()</code>。
+
然后我们需要定义 <code>as_sql</code> 方法。 这需要一个名为 <code>compiler</code> <code>SQLCompiler</code> 对象和活动的数据库连接。 <code>SQLCompiler</code> 对象没有记录,但我们唯一需要知道的是它们有一个 <code>compile()</code> 方法,该方法返回一个包含 SQL 字符串的元组,以及要插入其中的参数细绳。 在大多数情况下,您不需要直接使用它,可以将其传递给 <code>process_lhs()</code> 和 <code>process_rhs()</code>。
  
<code>Lookup</code> 工作依靠两个值,<code>lhs</code> 和 <code>rhs</code>,代表左右两边,左边是一个字段参考,它可以是任何实现了 [[../../ref/models/lookups#query-expression|<span class="std std-ref">查询表达式 API</span>]] 的实例。右边是一个用户给定的数值。举个例子: <code>Author.objects.filter(name__ne='Jack')</code>,左边是 <code>Author</code> 模型的 <code>name</code> 字段,右边是 <code>'Jack'</code>
+
<code>Lookup</code> 对两个值起作用,<code>lhs</code> 和 <code>rhs</code>,分别代表左侧和右侧。 左侧通常是字段引用,但它可以是实现 [[../../ref/models/lookups#query-expression|查询表达式 API]] 的任何内容。 右边是用户给出的值。 在示例<code>Author.objects.filter(name__ne='Jack')</code>中,左侧是对<code>Author</code>型号的<code>name</code>字段的引用,而<code>'Jack'</code>是右侧.
  
我们利用 <code>process_lhs</code> 和 <code>process_rhs</code> 将他们转换为我们期望值,用于之前介绍的 <code>compiler</code> 对象执行 SQL。这俩方法返回一个元组,包含一些 SQL 语句和插入 SQL 语句一些参数,就像是 <code>as_sql</code> 方法需要返回的。前文所述的例子中,<code>process_lhs</code> 返回 <code>('&quot;author&quot;.&quot;name&quot;', [])</code>,<code>process_lhs</code> 返回 <code>('&quot;%s&quot;', ['Jack'])</code>。在这个例子里面没有手边的参数,这需要看情况而定,所以我们仍需要在返回结果时包括这些参数。
+
我们调用 <code>process_lhs</code> 和 <code>process_rhs</code> 将它们转换为我们使用前面描述的 <code>compiler</code> 对象的 SQL 所需的值。 这些方法返回包含一些 SQL 和要插入到该 SQL 中的参数的元组,就像我们需要从 <code>as_sql</code> 方法返回一样。 在上面的例子中,<code>process_lhs</code> 返回 <code>('&quot;author&quot;.&quot;name&quot;', [])</code>,<code>process_rhs</code> 返回 <code>('&quot;%s&quot;', ['Jack'])</code>。 在这个例子中,左侧没有参数,但这取决于我们拥有的对象,所以我们仍然需要将它们包含在我们返回的参数中。
  
 
最后,我们将这些部分组合成一个带有 <code>&lt;&gt;</code> 的 SQL 表达式,并提供查询的所有参数。 然后我们返回一个包含生成的 SQL 字符串和参数的元组。
 
最后,我们将这些部分组合成一个带有 <code>&lt;&gt;</code> 的 SQL 表达式,并提供查询的所有参数。 然后我们返回一个包含生成的 SQL 字符串和参数的元组。
第85行: 第86行:
 
<div id="a-transformer-example" class="section">
 
<div id="a-transformer-example" class="section">
  
== 一个转换器示例。 ==
+
== 变压器示例 ==
  
上面的自定义查询器没问题,但在某些情况下,您可能希望能够将一些查询器链接在一起。 例如,假设我们正在构建一个使用 <code>abs()</code> 运算的应用程序。我们有一个 <code>Experiment</code> 模型,它记录起始值,结束值和差值(起始 - 结束)。 我们想找到 change 属性等于某个数值的所有 Experiment 对象(<code>Experiment.objects.filter(change__abs = 27)</code>),change属性没有超过一定数量的 Experiment 对象(<code>Experiment.objects.filter(change__abs__lt= 27)</code>)
+
上面的自定义查找很棒,但在某些情况下,您可能希望能够将查找链接在一起。 例如,假设我们正在构建一个要使用 <code>abs()</code> 运算符的应用程序。 我们有一个 <code>Experiment</code> 模型,它记录开始值、结束值和变化(开始 - 结束)。 我们想找出变化等于一定量 (<code>Experiment.objects.filter(change__abs=27)</code>) 或不超过一定量 (<code>Experiment.objects.filter(change__abs__lt=27)</code>) 的所有实验。
  
 
<div class="admonition note">
 
<div class="admonition note">
  
注解
+
笔记
  
这个例子有点刻意,但它很好地演示了以数据库后端独立方式可能实现的功能范围,并且没有重复 Django 中的功能。
+
这个例子有点人为,但它很好地展示了以数据库后端独立方式可能的功能范围,并且没有复制 Django 中已有的功能。
  
  
 
</div>
 
</div>
我们将从编写一个 <code>AbsoluteValue</code> 变换器开始。 这将使用 SQL 中的 <code>ABS()</code> 函数在比较进行之前转换值:
+
我们将首先编写一个 <code>AbsoluteValue</code> 转换器。 这将使用 SQL 函数 <code>ABS()</code> 在比较之前转换值:
  
 
<div class="highlight-default notranslate">
 
<div class="highlight-default notranslate">
第103行: 第104行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>from django.db.models import Transform
+
<syntaxhighlight lang="python">from django.db.models import Transform
  
 
class AbsoluteValue(Transform):
 
class AbsoluteValue(Transform):
 
     lookup_name = 'abs'
 
     lookup_name = 'abs'
     function = 'ABS'</pre>
+
     function = 'ABS'</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
下一步,让我们为其注册 <code>IntrgerField</code>:
+
接下来,让我们将其注册为 <code>IntegerField</code>
  
 
<div class="highlight-default notranslate">
 
<div class="highlight-default notranslate">
第118行: 第119行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>from django.db.models import IntegerField
+
<syntaxhighlight lang="python">from django.db.models import IntegerField
IntegerField.register_lookup(AbsoluteValue)</pre>
+
IntegerField.register_lookup(AbsoluteValue)</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
现在可以运行我们先前已有的查询了。<code>Experiment.objects.filter(change__abs=27)</code> 将生成下面的 SQL 语句:
+
我们现在可以运行之前的查询。 <code>Experiment.objects.filter(change__abs=27)</code> 将生成以下 SQL:
  
 
<div class="highlight-sql notranslate">
 
<div class="highlight-sql notranslate">
第130行: 第131行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>SELECT ... WHERE ABS(&quot;experiments&quot;.&quot;change&quot;) = 27</pre>
+
<syntaxhighlight lang="sql">SELECT ... WHERE ABS("experiments"."change") = 27</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
使用 <code>Transform</code> 代替 <code>Lookup</code> 意味着我们可以在后面联锁更多的 lookups,所以 <code>Experiment.objects.filter(change__abs__lt=27)</code> 将会生成下面的 SQL:
+
通过使用 <code>Transform</code> 而不是 <code>Lookup</code>,这意味着我们可以在之后链接进一步的查找。 所以 <code>Experiment.objects.filter(change__abs__lt=27)</code> 会生成如下 SQL:
  
 
<div class="highlight-sql notranslate">
 
<div class="highlight-sql notranslate">
第141行: 第142行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>SELECT ... WHERE ABS(&quot;experiments&quot;.&quot;change&quot;) &lt; 27</pre>
+
<syntaxhighlight lang="sql">SELECT ... WHERE ABS("experiments"."change") < 27</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
请注意,如果没有指定其他查找定义,Django则会将 <code>change__abs=27</code> 解析为 <code>change__abs__exact=27</code>。
+
请注意,如果没有指定其他查找,Django 会将 <code>change__abs=27</code> 解释为 <code>change__abs__exact=27</code>。
  
这也允许把结果用在 <code>ORDER BY</code> 和 <code>DISTINCT ON</code> 子句中。例如 <code>Experiment.objects.order_by('change__abs')</code> 生成:
+
这也允许在 <code>ORDER BY</code> 和 <code>DISTINCT ON</code> 子句中使用结果。 例如 <code>Experiment.objects.order_by('change__abs')</code> 生成:
  
 
<div class="highlight-sql notranslate">
 
<div class="highlight-sql notranslate">
第154行: 第155行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>SELECT ... ORDER BY ABS(&quot;experiments&quot;.&quot;change&quot;) ASC</pre>
+
<syntaxhighlight lang="sql">SELECT ... ORDER BY ABS("experiments"."change") ASC</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
And on databases that support distinct on fields (such as PostgreSQL),
+
在支持不同字段的数据库(如 PostgreSQL)上,<code>Experiment.objects.distinct('change__abs')</code> 生成:
<code>Experiment.objects.distinct('change__abs')</code> generates:
 
  
 
<div class="highlight-sql notranslate">
 
<div class="highlight-sql notranslate">
第166行: 第166行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>SELECT ... DISTINCT ON ABS(&quot;experiments&quot;.&quot;change&quot;)</pre>
+
<syntaxhighlight lang="sql">SELECT ... DISTINCT ON ABS("experiments"."change")</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
当我们在应用 <code>Transform</code> 之后查找允许执行哪些查找时,Django 使用 <code>output_field</code> 属性。 我们不需要在这里指定它,因为它没有改变,但假设我们将 <code>AbsoluteValue</code> 应用于某个字段,该字段表示更复杂的类型(例如,相对于原点的点或复数) 那么我们可能想要指定转换返回一个 <code>FloatField</code> 类型以进行进一步的查找。 这可以通过在变换中添加 <code>output_field</code> 属性来完成:
+
在应用 <code>Transform</code> 后查找允许的查找时,Django 使用 <code>output_field</code> 属性。 我们不需要在这里指定它,因为它没有改变,但假设我们将 <code>AbsoluteValue</code> 应用于表示更复杂类型的某个字段(例如相对于原点的点,或复数) 那么我们可能想要指定转换返回 <code>FloatField</code> 类型以供进一步查找。 这可以通过向转换添加 <code>output_field</code> 属性来完成:
  
 
<div class="highlight-default notranslate">
 
<div class="highlight-default notranslate">
第177行: 第177行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>from django.db.models import FloatField, Transform
+
<syntaxhighlight lang="python">from django.db.models import FloatField, Transform
  
 
class AbsoluteValue(Transform):
 
class AbsoluteValue(Transform):
第185行: 第185行:
 
     @property
 
     @property
 
     def output_field(self):
 
     def output_field(self):
         return FloatField()</pre>
+
         return FloatField()</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
这确保了像 <code>abs__lte</code> 这样的进一步查找与对 <code>FloatField</code> 一致。
+
这确保了 <code>abs__lte</code> 等进一步查找的行为与 <code>FloatField</code> 的行为相同。
  
  
第196行: 第196行:
 
<div id="writing-an-efficient-abs-lt-lookup" class="section">
 
<div id="writing-an-efficient-abs-lt-lookup" class="section">
  
== 编写一个高效的 <code>abs__lt</code> 查找 ==
+
== 编写高效的 abs__lt 查找 ==
  
当使用上面写的 <code>abs</code> 查找时,生成的 SQL 在某些情况下不会有效地使用索引。 特别是,当我们使用 <code>change__abs__lt=27</code> 时,这相当于 <code>change__gt=-27</code> <code>change__lt=27</code>。(对于 <code>lte</code> 情况,我们可以使用 SQL <code>BETWEEN</code>)。
+
当使用上面写的 <code>abs</code> 查找时,生成的 SQL 在某些情况下不会有效地使用索引。 特别是,当我们使用 <code>change__abs__lt=27</code> 时,这相当于 <code>change__gt=-27</code> AND <code>change__lt=27</code>。 (对于 <code>lte</code> 的情况,我们可以使用 SQL <code>BETWEEN</code>)。
  
所以我们期望 <code>Experiment.objects.filter(change__abs__lt=27)</code>  会生成下列 SQL 语句
+
所以我们希望 <code>Experiment.objects.filter(change__abs__lt=27)</code> 生成以下 SQL:
  
 
<div class="highlight-sql notranslate">
 
<div class="highlight-sql notranslate">
第206行: 第206行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>SELECT .. WHERE &quot;experiments&quot;.&quot;change&quot; &lt; 27 AND &quot;experiments&quot;.&quot;change&quot; &gt; -27</pre>
+
<syntaxhighlight lang="sql">SELECT .. WHERE "experiments"."change" < 27 AND "experiments"."change" > -27</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
实现方式是:
+
实现是:
  
 
<div class="highlight-default notranslate">
 
<div class="highlight-default notranslate">
第217行: 第217行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>from django.db.models import Lookup
+
<syntaxhighlight lang="python">from django.db.models import Lookup
  
 
class AbsoluteValueLessThan(Lookup):
 
class AbsoluteValueLessThan(Lookup):
第226行: 第226行:
 
         rhs, rhs_params = self.process_rhs(compiler, connection)
 
         rhs, rhs_params = self.process_rhs(compiler, connection)
 
         params = lhs_params + rhs_params + lhs_params + rhs_params
 
         params = lhs_params + rhs_params + lhs_params + rhs_params
         return '%s &lt; %s AND %s &gt; -%s' % (lhs, rhs, lhs, rhs), params
+
         return '%s < %s AND %s > -%s' % (lhs, rhs, lhs, rhs), params
  
AbsoluteValue.register_lookup(AbsoluteValueLessThan)</pre>
+
AbsoluteValue.register_lookup(AbsoluteValueLessThan)</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
这里有几件值得注意的事情。首先,<code>AbsoluteValueLessThan</code> 没有调用 <code>process_lhs()</code>。 相反,它会跳过由 <code>AbsoluteValue</code> 完成的 <code>lhs</code> 的转换,并使用原始的 <code>lhs</code>。也就是说,我们希望得到 <code>&quot;experiments&quot;.&quot;change&quot;</code>,而不是 <code>ABS(&quot;experiments&quot;.&quot;change&quot;)</code> 。直接引用 <code>self.lhs.lhs</code> 是安全的,因为 <code>AbsoluteValueLessThan</code> 只能从 <code>AbsoluteValue</code> lookup 访问,即 <code>lhs</code> 总是 <code>AbsoluteValue</code> 的实例。
+
有几件值得注意的事情正在发生。 首先,<code>AbsoluteValueLessThan</code> 没有调用 <code>process_lhs()</code>。 相反,它跳过了由 <code>AbsoluteValue</code> 完成的 <code>lhs</code> 的转换,并使用原始的 <code>lhs</code>。 也就是说,我们想要得到 <code>&quot;experiments&quot;.&quot;change&quot;</code> 而不是 <code>ABS(&quot;experiments&quot;.&quot;change&quot;)</code>。 直接引用 <code>self.lhs.lhs</code> 是安全的,因为 <code>AbsoluteValueLessThan</code> 只能从 <code>AbsoluteValue</code> 查找中访问,即 <code>lhs</code> 始终是 [ X155X]。
  
另请注意,由于在查询中多次使用两边,所以需要多次包含 <code>lhs_params</code> 和 <code>rhs_params</code> 参数。
+
另请注意,由于在查询中多次使用双方,因此参数需要多次包含 <code>lhs_params</code> 和 <code>rhs_params</code>
  
最后的查询直接在数据库中进行反转( 27 到 -27 )。 这样做的原因是,如果 <code>self.rhs</code> 不是普通的整数值(例如 F() 引用),我们就不能在 Python 中进行转换。
+
最后的查询直接在数据库中进行反演(<code>27</code> <code>-27</code>)。 这样做的原因是,如果 <code>self.rhs</code> 不是一个普通的整数值(例如 <code>F()</code> 引用),我们就不能在 Python 中进行转换。
  
 
<div class="admonition note">
 
<div class="admonition note">
  
注解
+
笔记
  
实际上,大多数的利用 <code>__abs</code> 的查找都可以被转换为类似此的范围查找,且在大多数数据库后端来说,这样做能更好的利用索引。不过,对于 PostgreSQL,你可能会为 <code>abs(change)</code> 添加索引,这会使查找更加高效。
+
事实上,大多数使用 <code>__abs</code> 的查找都可以作为这样的范围查询来实现,并且在大多数数据库后端,这样做可能更明智,因为您可以使用索引。 但是,对于 PostgreSQL,您可能希望在 <code>abs(change)</code> 上添加索引,这将使这些查询非常有效。
  
  
第251行: 第251行:
 
<div id="a-bilateral-transformer-example" class="section">
 
<div id="a-bilateral-transformer-example" class="section">
  
== 一个双向转换器示例 ==
+
== 双边变压器示例 ==
  
前文所述的 <code>AbsoluteValue</code> 例子实现了左侧查询。在某些场景下,你期望转换器同时作用于左侧和右侧。例如,如果你想在左侧基于等式进行过滤,而右侧对于某些 SQL 函数不敏感。
+
我们之前讨论的 <code>AbsoluteValue</code> 示例是应用于查找左侧的转换。 在某些情况下,您可能希望将转换应用于左侧和右侧。 例如,如果您想根据左侧和右侧的相等性对某个 SQL 函数不敏感地过滤查询集。
  
让我们在此测试这个大小写转换器。实际上这个转换器不是非常实用,因为 Django 已经内置了一系列大小写敏感相关的查询器,但它将是双向转换的一个很好的演示,且通过与数据库无关的方式来演示。
+
让我们在这里检查不区分大小写的转换。 这种转换在实践中并不是很有用,因为 Django 已经附带了一堆内置的不区分大小写的查找,但它将以与数据库无关的方式很好地演示双边转换。
  
我们定义了一个 <code>UpperCase</code> 转换器,使用了 SQL 函数 <code>UPPER()</code>,在比较之前转换值。我们定义了:attr:bilateral = True &lt;django.db.models.Transform.bilateral&gt; 指明此转换应同时用于 <code>lhs</code> 和 <code>rhs</code>:
+
我们定义了一个 <code>UpperCase</code> 转换器,它使用 SQL 函数 <code>UPPER()</code> 在比较之前转换值。 我们定义 [[../../ref/models/lookups#django.db.models.Transform|bilateral = True]] 来表明这个变换应该同时应用于 <code>lhs</code> 和 <code>rhs</code>
  
 
<div class="highlight-default notranslate">
 
<div class="highlight-default notranslate">
第263行: 第263行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>from django.db.models import Transform
+
<syntaxhighlight lang="python">from django.db.models import Transform
  
 
class UpperCase(Transform):
 
class UpperCase(Transform):
 
     lookup_name = 'upper'
 
     lookup_name = 'upper'
 
     function = 'UPPER'
 
     function = 'UPPER'
     bilateral = True</pre>
+
     bilateral = True</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
下一步, 让我们注册它:
+
接下来,让我们注册它:
  
 
<div class="highlight-default notranslate">
 
<div class="highlight-default notranslate">
第279行: 第279行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>from django.db.models import CharField, TextField
+
<syntaxhighlight lang="python">from django.db.models import CharField, TextField
 
CharField.register_lookup(UpperCase)
 
CharField.register_lookup(UpperCase)
TextField.register_lookup(UpperCase)</pre>
+
TextField.register_lookup(UpperCase)</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
Now, the queryset <code>Author.objects.filter(name__upper=&quot;doe&quot;)</code> will generate a case
+
现在,查询集 <code>Author.objects.filter(name__upper=&quot;doe&quot;)</code> 将生成一个不区分大小写的查询,如下所示:
insensitive query like this:
 
  
 
<div class="highlight-sql notranslate">
 
<div class="highlight-sql notranslate">
第293行: 第292行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>SELECT ... WHERE UPPER(&quot;author&quot;.&quot;name&quot;) = UPPER('doe')</pre>
+
<syntaxhighlight lang="sql">SELECT ... WHERE UPPER("author"."name") = UPPER('doe')</syntaxhighlight>
  
 
</div>
 
</div>
第302行: 第301行:
 
<div id="writing-alternative-implementations-for-existing-lookups" class="section">
 
<div id="writing-alternative-implementations-for-existing-lookups" class="section">
  
== 为现有的查找编写代替实现 ==
+
== 为现有查找编写替代实现 ==
  
有时候,不同的数据库提供商对相同的操作要求不同的 SQL 语句。针对此例子,我们会为 MySQL 重写 NotEqual 操作符。使用 <code>!=</code> 操作符替代 <code>&lt;&gt;</code>。(注意,实际上几乎所有的数据两者都支持,包括 Django 支持的所有正式数据库)。
+
有时,不同的数据库供应商对同一操作需要不同的 SQL。 对于此示例,我们将为 NotEqual 运算符重写 MySQL 的自定义实现。 我们将使用 <code>!=</code> 运算符而不是 <code>&lt;&gt;</code>。 (注意,实际上几乎所有的数据库都支持两者,包括 Django 支持的所有官方数据库)。
  
我们可以通过使用 <code>as_mysql</code> 方法创建 <code>NotEqual</code> 的子类来更改特定后端的行为:
+
我们可以通过使用 <code>as_mysql</code> 方法创建 <code>NotEqual</code> 的子类来更改特定后端的行为:
  
 
<div class="highlight-default notranslate">
 
<div class="highlight-default notranslate">
第312行: 第311行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>class MySQLNotEqual(NotEqual):
+
<syntaxhighlight lang="python">class MySQLNotEqual(NotEqual):
 
     def as_mysql(self, compiler, connection, **extra_context):
 
     def as_mysql(self, compiler, connection, **extra_context):
 
         lhs, lhs_params = self.process_lhs(compiler, connection)
 
         lhs, lhs_params = self.process_lhs(compiler, connection)
第319行: 第318行:
 
         return '%s != %s' % (lhs, rhs), params
 
         return '%s != %s' % (lhs, rhs), params
  
Field.register_lookup(MySQLNotEqual)</pre>
+
Field.register_lookup(MySQLNotEqual)</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
接着,我们可以里利用 <code>Field</code> 注册它。它会替换之前的 <code>NotEqual</code> 类,因为拥有相同的 <code>lookup_name</code>。
+
然后我们可以用 <code>Field</code> 注册它。 它取代了原来的 <code>NotEqual</code> 类,因为它具有相同的 <code>lookup_name</code>。
  
编译查询指令是,Django 先查找 <code>as_%s % connection.vendor</code> 方法,其次 <code>as_sql</code>。内置后端的提供商名为 <code>sqlite</code><code>postgresql</code><code>oracle</code> 和 <code>mysql</code>。
+
在编译查询时,Django 首先查找 <code>as_%s % connection.vendor</code> 方法,然后回退到 <code>as_sql</code>。 内置后端的供应商名称为 <code>sqlite</code><code>postgresql</code><code>oracle</code> 和 <code>mysql</code>。
  
  
第332行: 第331行:
 
<div id="how-django-determines-the-lookups-and-transforms-which-are-used" class="section">
 
<div id="how-django-determines-the-lookups-and-transforms-which-are-used" class="section">
  
== Django 是如何取舍查询器和转换器的 ==
+
== Django 如何确定使用的查找和转换 ==
  
某些场景下,你可能期望基于传入的名字动态地返回 <code>Transform</code> 或 <code>Lookup</code>,而不是指定。例如,有一个字段,存储了一些坐标或尺寸,期望使用以下语法 <code>.filter(coords__x7=4)</code> 返回第七个值为 4 的坐标。为此,你需要用以下内容重写 <code>get_lookup</code>:
+
在某些情况下,您可能希望根据传入的名称动态更改返回的 <code>Transform</code> 或 <code>Lookup</code>,而不是修复它。 例如,您可以有一个存储坐标或任意维度的字段,并希望允许像 <code>.filter(coords__x7=4)</code> 这样的语法返回第 7 个坐标值为 4 的对象。 为此,您可以使用以下内容覆盖 <code>get_lookup</code>
  
 
<div class="highlight-default notranslate">
 
<div class="highlight-default notranslate">
第340行: 第339行:
 
<div class="highlight">
 
<div class="highlight">
  
<pre>class CoordinatesField(Field):
+
<syntaxhighlight lang="python">class CoordinatesField(Field):
 
     def get_lookup(self, lookup_name):
 
     def get_lookup(self, lookup_name):
 
         if lookup_name.startswith('x'):
 
         if lookup_name.startswith('x'):
第349行: 第348行:
 
             else:
 
             else:
 
                 return get_coordinate_lookup(dimension)
 
                 return get_coordinate_lookup(dimension)
         return super().get_lookup(lookup_name)</pre>
+
         return super().get_lookup(lookup_name)</syntaxhighlight>
  
 
</div>
 
</div>
  
 
</div>
 
</div>
随后你需要定义 <code>get_coordinate_lookup</code> 正确地返回一个 <code>Lookup</code> 子类,用于处理 <code>dimension</code> 的相关值。
+
然后,您将适当地定义 <code>get_coordinate_lookup</code> 以返回一个 <code>Lookup</code> 子类,该子类处理 <code>dimension</code> 的相关值。
  
有个类似的名字叫做 <code>get_transform()</code>。<code>get_lookup()</code> 总是要返回 <code>Lookup</code> 子类,而 <code>get_transform</code> 要返回 <code>Transform</code> 子类。千万牢记,<code>Transform</code> 对象能被进一步过滤,而 <code>Lookup</code> 对象不能。
+
有一个名称类似的方法,称为 <code>get_transform()</code>。 <code>get_lookup()</code> 应该总是返回一个 <code>Lookup</code> 子类,而 <code>get_transform()</code> 一个 <code>Transform</code> 子类。 重要的是要记住 <code>Transform</code> 对象可以进一步过滤,而 <code>Lookup</code> 对象不能。
  
过滤时,若只能找到一个名字,我们会查找 <code>Lookup</code>。如果有多个名字,将会寻找 <code>Transform</code>。在某种情况下,仅有一个名字,且未找到 <code>Lookup</code>,我们将查找 <code>Transform</code>,并附加 <code>exact</code> 查询器。所以的系列调用都以一个 <code>Lookup</code> 结束。简单说明:
+
过滤时,如果只剩下一个查找名称需要解析,我们将查找一个<code>Lookup</code>。 如果有多个名称,它将查找 <code>Transform</code>。 在只有一个名称且未找到 <code>Lookup</code> 的情况下,我们查找 <code>Transform</code>,然后在该 <code>Transform</code> 上查找 <code>exact</code>。 所有调用序列总是以 <code>Lookup</code> 结尾。 澄清:
  
* <code>.filter(myfield__mylookup)</code> 将会调用 <code>myfield.get_lookup('mylookup')</code>。
+
* <code>.filter(myfield__mylookup)</code> 将调用 <code>myfield.get_lookup('mylookup')</code>。
* <code>.filter(myfield__mytransform__mylookup)</code> 将会调用 <code>myfield.get_transform('mytransform')</code>, 接着调用 <code>mytransform.get_lookup('mylookup')</code>。
+
* <code>.filter(myfield__mytransform__mylookup)</code> 将调用 <code>myfield.get_transform('mytransform')</code>,然后是 <code>mytransform.get_lookup('mylookup')</code>。
* <code>.filter(myfield__mytransform)</code> 会先调用 <code>myfield.get_lookup('mytransform')</code>,失败,然后回滚调用 <code>myfield.get_transform('mytransform')</code>,随后返回 <code>mytransform.get_lookup('exact')</code>。
+
* <code>.filter(myfield__mytransform)</code>会先调用<code>myfield.get_lookup('mytransform')</code>,会失败,所以回退到调用<code>myfield.get_transform('mytransform')</code>,然后再调用<code>mytransform.get_lookup('exact')</code>。
  
  
第368行: 第367行:
  
 
</div>
 
</div>
 +
<div class="clearer">
  
[[Category:Django 3.0.x 中文文档]]
+
 
 +
 
 +
</div>
 +
 
 +
[[Category:Django 3.0.x 文档]]

2021年10月31日 (日) 04:08的最新版本

自定义查找

Django 提供了各种各样的 内置查找 用于过滤(例如,exacticontains)。 本文档解释了如何编写自定义查找以及如何更改现有查找的工作方式。 有关查找的 API 参考,请参阅 Lookup API 参考

查找示例

让我们从一个小的自定义查找开始。 我们将编写一个自定义查找 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。 在尝试使用它创建任何查询集之前,您需要确保此注册发生。 您可以将实现放在 models.py 文件中,或在 AppConfigready() 方法中注册查找。

仔细查看实现,第一个必需的属性是 lookup_name。 这允许 ORM 了解如何解释 name__ne 并使用 NotEqual 生成 SQL。 按照惯例,这些名称总是只包含字母的小写字符串,但唯一的硬性要求是它不能包含字符串 __

然后我们需要定义 as_sql 方法。 这需要一个名为 compilerSQLCompiler 对象和活动的数据库连接。 SQLCompiler 对象没有记录,但我们唯一需要知道的是它们有一个 compile() 方法,该方法返回一个包含 SQL 字符串的元组,以及要插入其中的参数细绳。 在大多数情况下,您不需要直接使用它,可以将其传递给 process_lhs()process_rhs()

Lookup 对两个值起作用,lhsrhs,分别代表左侧和右侧。 左侧通常是字段引用,但它可以是实现 查询表达式 API 的任何内容。 右边是用户给出的值。 在示例Author.objects.filter(name__ne='Jack')中,左侧是对Author型号的name字段的引用,而'Jack'是右侧.

我们调用 process_lhsprocess_rhs 将它们转换为我们使用前面描述的 compiler 对象的 SQL 所需的值。 这些方法返回包含一些 SQL 和要插入到该 SQL 中的参数的元组,就像我们需要从 as_sql 方法返回一样。 在上面的例子中,process_lhs 返回 ('"author"."name"', [])process_rhs 返回 ('"%s"', ['Jack'])。 在这个例子中,左侧没有参数,但这取决于我们拥有的对象,所以我们仍然需要将它们包含在我们返回的参数中。

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


变压器示例

上面的自定义查找很棒,但在某些情况下,您可能希望能够将查找链接在一起。 例如,假设我们正在构建一个要使用 abs() 运算符的应用程序。 我们有一个 Experiment 模型,它记录开始值、结束值和变化(开始 - 结束)。 我们想找出变化等于一定量 (Experiment.objects.filter(change__abs=27)) 或不超过一定量 (Experiment.objects.filter(change__abs__lt=27)) 的所有实验。

笔记

这个例子有点人为,但它很好地展示了以数据库后端独立方式可能的功能范围,并且没有复制 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

通过使用 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 BYDISTINCT 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")

在应用 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 AND 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("experiments"."change")。 直接引用 self.lhs.lhs 是安全的,因为 AbsoluteValueLessThan 只能从 AbsoluteValue 查找中访问,即 lhs 始终是 [ X155X]。

另请注意,由于在查询中多次使用双方,因此参数需要多次包含 lhs_paramsrhs_params

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

笔记

事实上,大多数使用 __abs 的查找都可以作为这样的范围查询来实现,并且在大多数数据库后端,这样做可能更明智,因为您可以使用索引。 但是,对于 PostgreSQL,您可能希望在 abs(change) 上添加索引,这将使这些查询非常有效。


双边变压器示例

我们之前讨论的 AbsoluteValue 示例是应用于查找左侧的转换。 在某些情况下,您可能希望将转换应用于左侧和右侧。 例如,如果您想根据左侧和右侧的相等性对某个 SQL 函数不敏感地过滤查询集。

让我们在这里检查不区分大小写的转换。 这种转换在实践中并不是很有用,因为 Django 已经附带了一堆内置的不区分大小写的查找,但它将以与数据库无关的方式很好地演示双边转换。

我们定义了一个 UpperCase 转换器,它使用 SQL 函数 UPPER() 在比较之前转换值。 我们定义 bilateral = True 来表明这个变换应该同时应用于 lhsrhs

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

为现有查找编写替代实现

有时,不同的数据库供应商对同一操作需要不同的 SQL。 对于此示例,我们将为 NotEqual 运算符重写 MySQL 的自定义实现。 我们将使用 != 运算符而不是 <>。 (注意,实际上几乎所有的数据库都支持两者,包括 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。 内置后端的供应商名称为 sqlitepostgresqloraclemysql


Django 如何确定使用的查找和转换

在某些情况下,您可能希望根据传入的名称动态更改返回的 TransformLookup,而不是修复它。 例如,您可以有一个存储坐标或任意维度的字段,并希望允许像 .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)

然后,您将适当地定义 get_coordinate_lookup 以返回一个 Lookup 子类,该子类处理 dimension 的相关值。

有一个名称类似的方法,称为 get_transform()get_lookup() 应该总是返回一个 Lookup 子类,而 get_transform() 一个 Transform 子类。 重要的是要记住 Transform 对象可以进一步过滤,而 Lookup 对象不能。

过滤时,如果只剩下一个查找名称需要解析,我们将查找一个Lookup。 如果有多个名称,它将查找 Transform。 在只有一个名称且未找到 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')