{"title":"如何编写自定义的查询器","version":"4.1","locale":"zh-hans","docname":"howto/custom-lookups","url":"/zh-hans/4.1/howto/custom-lookups/","canonical":"https://djangodocs.dev/zh-hans/4.1/howto/custom-lookups/","summary":"Django 提供了各种各样的内置查询器（例如， exact 和 icontains ）。本文档解释了如何编写自定义查询器以及如何更改已有查询器的工作方式。","html":"<h1>如何编写自定义的查询器<a class=\"heading-anchor\" href=\"#how-to-write-custom-lookups\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h1>\n<p>Django 提供了各种各样的 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/models/querysets/#field-lookups\"><span class=\"std std-ref\">内置查询器</span></a> （例如， <code class=\"docutils literal notranslate\">exact</code> 和 <code class=\"docutils literal notranslate\">icontains</code> ）。本文档解释了如何编写自定义查询器以及如何更改已有查询器的工作方式。 有关 lookup 的 API 参考，请参阅 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/models/lookups/\"><span class=\"doc\">查找 API 参考</span></a>。</p>\n<section id=\"a-lookup-example\">\n<h2>一个查询器示例。<a class=\"heading-anchor\" href=\"#a-lookup-example\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>让我们以一个小巧的自定义查询器为例。我们将书写一个名为 <code class=\"docutils literal notranslate\">ne</code> 的自定义查询器，它的效果与 <code class=\"docutils literal notranslate\">exact</code> 相反。语句 <code class=\"docutils literal notranslate\">Author.objects.filter(name__ne='Jack')</code>  将会翻译为下面的 SQL 语句：</p>\n<div class=\"code-block\" data-language=\"sql\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">SQL</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"SQL code\"><code><span class=\"ss\">&quot;author&quot;</span><span class=\"p\">.</span><span class=\"ss\">&quot;name&quot;</span> <span class=\"o\">&lt;&gt;</span> <span class=\"s1\">&#39;Jack&#39;</span>\n</code></pre></div>\n<p>SQL 会自动适配不同的后端，所以我们不需要为使用不同的数据库而担心。</p>\n<p>要让它生效需要两个步骤，首先我们需要实现该查询器，然后我们需要告诉 Django 有关它的信息。</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"kn\">from</span> <span class=\"nn\">django.db.models</span> <span class=\"kn\">import</span> Lookup\n\n<span class=\"k\">class</span> <span class=\"nc\">NotEqual</span><span class=\"p\">(</span>Lookup<span class=\"p\">):</span>\n    lookup_name <span class=\"o\">=</span> <span class=\"s1\">&#39;ne&#39;</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">as_sql</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> compiler<span class=\"p\">,</span> connection<span class=\"p\">):</span>\n        lhs<span class=\"p\">,</span> lhs_params <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span>process_lhs<span class=\"p\">(</span>compiler<span class=\"p\">,</span> connection<span class=\"p\">)</span>\n        rhs<span class=\"p\">,</span> rhs_params <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span>process_rhs<span class=\"p\">(</span>compiler<span class=\"p\">,</span> connection<span class=\"p\">)</span>\n        params <span class=\"o\">=</span> lhs_params <span class=\"o\">+</span> rhs_params\n        <span class=\"k\">return</span> <span class=\"s1\">&#39;</span><span class=\"si\">%s</span><span class=\"s1\"> &lt;&gt; </span><span class=\"si\">%s</span><span class=\"s1\">&#39;</span> <span class=\"o\">%</span> <span class=\"p\">(</span>lhs<span class=\"p\">,</span> rhs<span class=\"p\">),</span> params\n</code></pre></div>\n<p>为了注册  <code class=\"docutils literal notranslate\">NotEqual</code> 查询器，我们需要在对应需要该查询器的字段类中调用  <code class=\"docutils literal notranslate\">register_lookup</code> 方法。在该情形下，该查询器作用在所有的 <code class=\"docutils literal notranslate\">Field</code> 子类，所以我们直接将它注册在 <code class=\"docutils literal notranslate\">Field</code> 中:</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"kn\">from</span> <span class=\"nn\">django.db.models</span> <span class=\"kn\">import</span> Field\nField<span class=\"o\">.</span>register_lookup<span class=\"p\">(</span>NotEqual<span class=\"p\">)</span>\n</code></pre></div>\n<p>查询器注册也可以用修饰模式来完成:</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"kn\">from</span> <span class=\"nn\">django.db.models</span> <span class=\"kn\">import</span> Field\n\n<span class=\"nd\">@Field</span><span class=\"o\">.</span>register_lookup\n<span class=\"k\">class</span> <span class=\"nc\">NotEqualLookup</span><span class=\"p\">(</span>Lookup<span class=\"p\">):</span>\n    <span class=\"c1\"># ...</span>\n</code></pre></div>\n<p>现在我们可以用 <code class=\"docutils literal notranslate\">foo__ne</code> 来代表 <code class=\"docutils literal notranslate\">foo</code> 的任意字段。你需要确保注册行为发生在创建任意的 queryset 之前。你可以在 <code class=\"docutils literal notranslate\">models.py</code> 文件内设置它，或者在 <code class=\"docutils literal notranslate\">AppConfig</code> 的 <code class=\"docutils literal notranslate\">ready()</code> 方法中注册它。</p>\n<p>仔细观察实现过程，第一个要求的属性是 <code class=\"docutils literal notranslate\">lookup_name</code>。它能让 ORM 理解如何编译 <code class=\"docutils literal notranslate\">name_ne</code> 并使用 <code class=\"docutils literal notranslate\">NotEqual</code> 来建立 SQL 语句。按照惯例，这些名字应该总是仅包含小写字母的字符串，但是绝对不能包含双下划线 <code class=\"docutils literal notranslate\">__</code>。</p>\n<p>之后我们需要定义 <code class=\"docutils literal notranslate\">as_sql</code> 方法。此方法需要一个 <code class=\"docutils literal notranslate\">SQLCompiler</code> 对象, 被叫做 <code class=\"docutils literal notranslate\">compiler</code>，和一个有效的数据库连接。<code class=\"docutils literal notranslate\">SQLCompller</code> 对象没有文档，我们只需要知道它有一个 <code class=\"docutils literal notranslate\">compile()</code> 方法可以返回一个包括 SQL 字符串的元组，和插入这个字符串的参数。大部分情况下，你不需要直接使用这个对象你可以把它传送给 <code class=\"docutils literal notranslate\">process_lhs()</code> 和 <code class=\"docutils literal notranslate\">process_rhs()</code>。</p>\n<p><code class=\"docutils literal notranslate\">Lookup</code> 工作依靠两个值，<code class=\"docutils literal notranslate\">lhs</code> 和 <code class=\"docutils literal notranslate\">rhs</code>，代表左右两边，左边是一个字段参考，它可以是任何实现了 <a class=\"reference internal\" href=\"/zh-hans/4.1/ref/models/lookups/#query-expression\"><span class=\"std std-ref\">查询表达式 API</span></a> 的实例。右边是一个用户给定的数值。举个例子： <code class=\"docutils literal notranslate\">Author.objects.filter(name__ne='Jack')</code>，左边是 <code class=\"docutils literal notranslate\">Author</code> 模型的 <code class=\"docutils literal notranslate\">name</code> 字段，右边是 <code class=\"docutils literal notranslate\">'Jack'</code>。</p>\n<p>我们利用 <code class=\"docutils literal notranslate\">process_lhs</code> 和 <code class=\"docutils literal notranslate\">process_rhs</code> 将他们转换为我们期望值，用于之前介绍的 <code class=\"docutils literal notranslate\">compiler</code> 对象执行 SQL。这俩方法返回一个元组，包含一些 SQL 语句和插入 SQL 语句一些参数，就像是 <code class=\"docutils literal notranslate\">as_sql</code> 方法需要返回的。前文所述的例子中，<code class=\"docutils literal notranslate\">process_lhs</code> 返回 <code class=\"docutils literal notranslate\">('&quot;author&quot;.&quot;name&quot;', [])</code>，<code class=\"docutils literal notranslate\">process_lhs</code> 返回 <code class=\"docutils literal notranslate\">('&quot;%s&quot;', ['Jack'])</code>。在这个例子里面没有手边的参数，这需要看情况而定，所以我们仍需要在返回结果时包括这些参数。</p>\n<p>最后，我们将这些部分组合成一个带有 <code class=\"docutils literal notranslate\">&lt;&gt;</code> 的 SQL 表达式，并提供查询的所有参数。 然后我们返回一个包含生成的 SQL 字符串和参数的元组。</p>\n</section>\n<section id=\"a-transformer-example\">\n<h2>一个转换器示例。<a class=\"heading-anchor\" href=\"#a-transformer-example\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>上面的自定义查询器没问题，但在某些情况下，您可能希望能够将一些查询器链接在一起。 例如，假设我们正在构建一个使用 <code class=\"docutils literal notranslate\">abs()</code> 运算的应用程序。我们有一个 <code class=\"docutils literal notranslate\">Experiment</code> 模型，它记录起始值，结束值和差值（起始 - 结束）。 我们想找到 change 属性等于某个数值的所有 Experiment 对象(<code class=\"docutils literal notranslate\">Experiment.objects.filter(change__abs = 27)</code>)，change属性没有超过一定数量的 Experiment 对象(<code class=\"docutils literal notranslate\">Experiment.objects.filter(change__abs__lt= 27)</code>)。</p>\n<aside class=\"admonition admonition-note\" role=\"note\">\n<p class=\"admonition-title\">Note</p>\n<p>这个例子有点刻意，但它很好地演示了以数据库后端独立方式可能实现的功能范围，并且没有重复 Django 中的功能。</p>\n</aside>\n<p>我们将从编写一个 <code class=\"docutils literal notranslate\">AbsoluteValue</code> 变换器开始。 这将使用 SQL 中的 <code class=\"docutils literal notranslate\">ABS()</code> 函数在比较进行之前转换值:</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"kn\">from</span> <span class=\"nn\">django.db.models</span> <span class=\"kn\">import</span> Transform\n\n<span class=\"k\">class</span> <span class=\"nc\">AbsoluteValue</span><span class=\"p\">(</span>Transform<span class=\"p\">):</span>\n    lookup_name <span class=\"o\">=</span> <span class=\"s1\">&#39;abs&#39;</span>\n    function <span class=\"o\">=</span> <span class=\"s1\">&#39;ABS&#39;</span>\n</code></pre></div>\n<p>下一步，让我们为其注册 <code class=\"docutils literal notranslate\">IntrgerField</code>:</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"kn\">from</span> <span class=\"nn\">django.db.models</span> <span class=\"kn\">import</span> IntegerField\nIntegerField<span class=\"o\">.</span>register_lookup<span class=\"p\">(</span>AbsoluteValue<span class=\"p\">)</span>\n</code></pre></div>\n<p>现在可以运行我们先前已有的查询了。<code class=\"docutils literal notranslate\">Experiment.objects.filter(change__abs=27)</code> 将生成下面的 SQL 语句:</p>\n<div class=\"code-block\" data-language=\"sql\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">SQL</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"SQL code\"><code><span class=\"k\">SELECT</span> <span class=\"p\">...</span> <span class=\"k\">WHERE</span> <span class=\"k\">ABS</span><span class=\"p\">(</span><span class=\"ss\">&quot;experiments&quot;</span><span class=\"p\">.</span><span class=\"ss\">&quot;change&quot;</span><span class=\"p\">)</span> <span class=\"o\">=</span> <span class=\"mi\">27</span>\n</code></pre></div>\n<p>使用 <code class=\"docutils literal notranslate\">Transform</code> 代替 <code class=\"docutils literal notranslate\">Lookup</code> 意味着我们可以在后面联锁更多的查询，所以 <code class=\"docutils literal notranslate\">Experiment.objects.filter(change__abs__lt=27)</code> 将会生成下面的 SQL：</p>\n<div class=\"code-block\" data-language=\"sql\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">SQL</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"SQL code\"><code><span class=\"k\">SELECT</span> <span class=\"p\">...</span> <span class=\"k\">WHERE</span> <span class=\"k\">ABS</span><span class=\"p\">(</span><span class=\"ss\">&quot;experiments&quot;</span><span class=\"p\">.</span><span class=\"ss\">&quot;change&quot;</span><span class=\"p\">)</span> <span class=\"o\">&lt;</span> <span class=\"mi\">27</span>\n</code></pre></div>\n<p>请注意，如果没有指定其他查找定义，Django则会将 <code class=\"docutils literal notranslate\">change__abs=27</code> 解析为 <code class=\"docutils literal notranslate\">change__abs__exact=27</code>。</p>\n<p>这也允许把结果用在 <code class=\"docutils literal notranslate\">ORDER BY</code> 和 <code class=\"docutils literal notranslate\">DISTINCT ON</code> 子句中。例如 <code class=\"docutils literal notranslate\">Experiment.objects.order_by('change__abs')</code> 生成:</p>\n<div class=\"code-block\" data-language=\"sql\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">SQL</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"SQL code\"><code><span class=\"k\">SELECT</span> <span class=\"p\">...</span> <span class=\"k\">ORDER</span> <span class=\"k\">BY</span> <span class=\"k\">ABS</span><span class=\"p\">(</span><span class=\"ss\">&quot;experiments&quot;</span><span class=\"p\">.</span><span class=\"ss\">&quot;change&quot;</span><span class=\"p\">)</span> <span class=\"k\">ASC</span>\n</code></pre></div>\n<p>并且在支持对字段使用 distinct 的数据库中（比如 PostgreSQL），<code class=\"docutils literal notranslate\">Experiment.objects.distinct('change__abs')</code> 会产生：</p>\n<div class=\"code-block\" data-language=\"sql\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">SQL</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"SQL code\"><code><span class=\"k\">SELECT</span> <span class=\"p\">...</span> <span class=\"k\">DISTINCT</span> <span class=\"k\">ON</span> <span class=\"k\">ABS</span><span class=\"p\">(</span><span class=\"ss\">&quot;experiments&quot;</span><span class=\"p\">.</span><span class=\"ss\">&quot;change&quot;</span><span class=\"p\">)</span>\n</code></pre></div>\n<p>当我们在应用 <code class=\"docutils literal notranslate\">Transform</code> 之后查找允许执行哪些查找时，Django 使用 <code class=\"docutils literal notranslate\">output_field</code> 属性。 我们不需要在这里指定它，因为它没有改变，但假设我们将 <code class=\"docutils literal notranslate\">AbsoluteValue</code> 应用于某个字段，该字段表示更复杂的类型（例如，相对于原点的点或复数） 那么我们可能想要指定转换返回一个 <code class=\"docutils literal notranslate\">FloatField</code> 类型以进行进一步的查找。 这可以通过在变换中添加 <code class=\"docutils literal notranslate\">output_field</code> 属性来完成:</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"kn\">from</span> <span class=\"nn\">django.db.models</span> <span class=\"kn\">import</span> FloatField<span class=\"p\">,</span> Transform\n\n<span class=\"k\">class</span> <span class=\"nc\">AbsoluteValue</span><span class=\"p\">(</span>Transform<span class=\"p\">):</span>\n    lookup_name <span class=\"o\">=</span> <span class=\"s1\">&#39;abs&#39;</span>\n    function <span class=\"o\">=</span> <span class=\"s1\">&#39;ABS&#39;</span>\n\n    <span class=\"nd\">@property</span>\n    <span class=\"k\">def</span> <span class=\"nf\">output_field</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n        <span class=\"k\">return</span> FloatField<span class=\"p\">()</span>\n</code></pre></div>\n<p>这确保了像 <code class=\"docutils literal notranslate\">abs__lte</code> 这样的进一步查找与对 <code class=\"docutils literal notranslate\">FloatField</code> 一致。</p>\n</section>\n<section id=\"writing-an-efficient-abs-lt-lookup\">\n<h2>编写一个高效的 <code class=\"docutils literal notranslate\">abs__lt</code> 查找<a class=\"heading-anchor\" href=\"#writing-an-efficient-abs-lt-lookup\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>当使用上面写的 <code class=\"docutils literal notranslate\">abs</code> 查找时，生成的 SQL 在某些情况下不会有效地使用索引。 特别是，当我们使用 <code class=\"docutils literal notranslate\">change__abs__lt=27</code> 时，这相当于 <code class=\"docutils literal notranslate\">change__gt=-27</code> 和 <code class=\"docutils literal notranslate\">change__lt=27</code>。（对于 <code class=\"docutils literal notranslate\">lte</code> 情况，我们可以使用 SQL <code class=\"docutils literal notranslate\">BETWEEN</code>）。</p>\n<p>所以我们期望 <code class=\"docutils literal notranslate\">Experiment.objects.filter(change__abs__lt=27)</code>  会生成下列 SQL 语句</p>\n<div class=\"code-block\" data-language=\"sql\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">SQL</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"SQL code\"><code><span class=\"k\">SELECT</span> <span class=\"p\">..</span> <span class=\"k\">WHERE</span> <span class=\"ss\">&quot;experiments&quot;</span><span class=\"p\">.</span><span class=\"ss\">&quot;change&quot;</span> <span class=\"o\">&lt;</span> <span class=\"mi\">27</span> <span class=\"k\">AND</span> <span class=\"ss\">&quot;experiments&quot;</span><span class=\"p\">.</span><span class=\"ss\">&quot;change&quot;</span> <span class=\"o\">&gt;</span> <span class=\"o\">-</span><span class=\"mi\">27</span>\n</code></pre></div>\n<p>实现方式是:</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"kn\">from</span> <span class=\"nn\">django.db.models</span> <span class=\"kn\">import</span> Lookup\n\n<span class=\"k\">class</span> <span class=\"nc\">AbsoluteValueLessThan</span><span class=\"p\">(</span>Lookup<span class=\"p\">):</span>\n    lookup_name <span class=\"o\">=</span> <span class=\"s1\">&#39;lt&#39;</span>\n\n    <span class=\"k\">def</span> <span class=\"nf\">as_sql</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> compiler<span class=\"p\">,</span> connection<span class=\"p\">):</span>\n        lhs<span class=\"p\">,</span> lhs_params <span class=\"o\">=</span> compiler<span class=\"o\">.</span>compile<span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"o\">.</span>lhs<span class=\"o\">.</span>lhs<span class=\"p\">)</span>\n        rhs<span class=\"p\">,</span> rhs_params <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span>process_rhs<span class=\"p\">(</span>compiler<span class=\"p\">,</span> connection<span class=\"p\">)</span>\n        params <span class=\"o\">=</span> lhs_params <span class=\"o\">+</span> rhs_params <span class=\"o\">+</span> lhs_params <span class=\"o\">+</span> rhs_params\n        <span class=\"k\">return</span> <span class=\"s1\">&#39;</span><span class=\"si\">%s</span><span class=\"s1\"> &lt; </span><span class=\"si\">%s</span><span class=\"s1\"> AND </span><span class=\"si\">%s</span><span class=\"s1\"> &gt; -</span><span class=\"si\">%s</span><span class=\"s1\">&#39;</span> <span class=\"o\">%</span> <span class=\"p\">(</span>lhs<span class=\"p\">,</span> rhs<span class=\"p\">,</span> lhs<span class=\"p\">,</span> rhs<span class=\"p\">),</span> params\n\nAbsoluteValue<span class=\"o\">.</span>register_lookup<span class=\"p\">(</span>AbsoluteValueLessThan<span class=\"p\">)</span>\n</code></pre></div>\n<p>这里有几件值得注意的事情。首先，<code class=\"docutils literal notranslate\">AbsoluteValueLessThan</code> 没有调用 <code class=\"docutils literal notranslate\">process_lhs()</code>。 相反，它会跳过由 <code class=\"docutils literal notranslate\">AbsoluteValue</code> 完成的 <code class=\"docutils literal notranslate\">lhs</code> 的转换，并使用原始的 <code class=\"docutils literal notranslate\">lhs</code>。也就是说，我们希望得到 <code class=\"docutils literal notranslate\">&quot;experiments&quot;.&quot;change&quot;</code>，而不是 <code class=\"docutils literal notranslate\">ABS(&quot;experiments&quot;.&quot;change&quot;)</code> 。直接引用 <code class=\"docutils literal notranslate\">self.lhs.lhs</code> 是安全的，因为 <code class=\"docutils literal notranslate\">AbsoluteValueLessThan</code> 只能从 <code class=\"docutils literal notranslate\">AbsoluteValue</code> lookup 访问，即 <code class=\"docutils literal notranslate\">lhs</code> 总是 <code class=\"docutils literal notranslate\">AbsoluteValue</code> 的实例。</p>\n<p>另请注意，由于在查询中多次使用两边，所以需要多次包含 <code class=\"docutils literal notranslate\">lhs_params</code> 和 <code class=\"docutils literal notranslate\">rhs_params</code> 参数。</p>\n<p>最后的查询直接在数据库中进行反转（ 27 到 -27 ）。 这样做的原因是，如果 <code class=\"docutils literal notranslate\">self.rhs</code> 不是普通的整数值（例如 F() 引用），我们就不能在 Python 中进行转换。</p>\n<aside class=\"admonition admonition-note\" role=\"note\">\n<p class=\"admonition-title\">Note</p>\n<p>实际上，大多数的利用 <code class=\"docutils literal notranslate\">__abs</code> 的查找都可以被转换为类似此的范围查找，且在大多数数据库后端来说，这样做能更好的利用索引。不过，对于 PostgreSQL，你可能会为 <code class=\"docutils literal notranslate\">abs(change)</code> 添加索引，这会使查找更加高效。</p>\n</aside>\n</section>\n<section id=\"a-bilateral-transformer-example\">\n<h2>一个双向转换器示例<a class=\"heading-anchor\" href=\"#a-bilateral-transformer-example\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>前文所述的 <code class=\"docutils literal notranslate\">AbsoluteValue</code> 例子实现了左侧查询。在某些场景下，你期望转换器同时作用于左侧和右侧。例如，如果你想在左侧基于等式进行过滤，而右侧对于某些 SQL 函数不敏感。</p>\n<p>让我们在此测试这个大小写转换器。实际上这个转换器不是非常实用，因为 Django 已经内置了一系列大小写敏感相关的查询器，但它将是双向转换的一个很好的演示，且通过与数据库无关的方式来演示。</p>\n<p>我们定义了一个 <code class=\"docutils literal notranslate\">UpperCase</code> 转换器，使用了 SQL 函数 <code class=\"docutils literal notranslate\">UPPER()</code>，在比较之前转换值。我们定义了:attr:bilateral = True &lt;django.db.models.Transform.bilateral&gt; 指明此转换应同时用于 <code class=\"docutils literal notranslate\">lhs</code> 和 <code class=\"docutils literal notranslate\">rhs</code>:</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"kn\">from</span> <span class=\"nn\">django.db.models</span> <span class=\"kn\">import</span> Transform\n\n<span class=\"k\">class</span> <span class=\"nc\">UpperCase</span><span class=\"p\">(</span>Transform<span class=\"p\">):</span>\n    lookup_name <span class=\"o\">=</span> <span class=\"s1\">&#39;upper&#39;</span>\n    function <span class=\"o\">=</span> <span class=\"s1\">&#39;UPPER&#39;</span>\n    bilateral <span class=\"o\">=</span> <span class=\"kc\">True</span>\n</code></pre></div>\n<p>下一步, 让我们注册它:</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"kn\">from</span> <span class=\"nn\">django.db.models</span> <span class=\"kn\">import</span> CharField<span class=\"p\">,</span> TextField\nCharField<span class=\"o\">.</span>register_lookup<span class=\"p\">(</span>UpperCase<span class=\"p\">)</span>\nTextField<span class=\"o\">.</span>register_lookup<span class=\"p\">(</span>UpperCase<span class=\"p\">)</span>\n</code></pre></div>\n<p>现在， <code class=\"docutils literal notranslate\">Author.objects.filter(name__upper=&quot;doe&quot;)</code> 将会产生一个不区分大小写的查询如下：</p>\n<div class=\"code-block\" data-language=\"sql\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">SQL</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"SQL code\"><code><span class=\"k\">SELECT</span> <span class=\"p\">...</span> <span class=\"k\">WHERE</span> <span class=\"k\">UPPER</span><span class=\"p\">(</span><span class=\"ss\">&quot;author&quot;</span><span class=\"p\">.</span><span class=\"ss\">&quot;name&quot;</span><span class=\"p\">)</span> <span class=\"o\">=</span> <span class=\"k\">UPPER</span><span class=\"p\">(</span><span class=\"s1\">&#39;doe&#39;</span><span class=\"p\">)</span>\n</code></pre></div>\n</section>\n<section id=\"writing-alternative-implementations-for-existing-lookups\">\n<h2>为现有的查找编写代替实现<a class=\"heading-anchor\" href=\"#writing-alternative-implementations-for-existing-lookups\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>有时候，不同的数据库提供商对相同的操作要求不同的 SQL 语句。针对此例子，我们会为 MySQL 重写 NotEqual 操作符。使用 <code class=\"docutils literal notranslate\">!=</code> 操作符替代 <code class=\"docutils literal notranslate\">&lt;&gt;</code>。（注意，实际上几乎所有的数据两者都支持，包括 Django 支持的所有正式数据库）。</p>\n<p>我们可以通过使用 <code class=\"docutils literal notranslate\">as_mysql</code> 方法创建 <code class=\"docutils literal notranslate\">NotEqual</code> 的子类来更改特定后端的行为:</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"k\">class</span> <span class=\"nc\">MySQLNotEqual</span><span class=\"p\">(</span>NotEqual<span class=\"p\">):</span>\n    <span class=\"k\">def</span> <span class=\"nf\">as_mysql</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> compiler<span class=\"p\">,</span> connection<span class=\"p\">,</span> <span class=\"o\">**</span>extra_context<span class=\"p\">):</span>\n        lhs<span class=\"p\">,</span> lhs_params <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span>process_lhs<span class=\"p\">(</span>compiler<span class=\"p\">,</span> connection<span class=\"p\">)</span>\n        rhs<span class=\"p\">,</span> rhs_params <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span>process_rhs<span class=\"p\">(</span>compiler<span class=\"p\">,</span> connection<span class=\"p\">)</span>\n        params <span class=\"o\">=</span> lhs_params <span class=\"o\">+</span> rhs_params\n        <span class=\"k\">return</span> <span class=\"s1\">&#39;</span><span class=\"si\">%s</span><span class=\"s1\"> != </span><span class=\"si\">%s</span><span class=\"s1\">&#39;</span> <span class=\"o\">%</span> <span class=\"p\">(</span>lhs<span class=\"p\">,</span> rhs<span class=\"p\">),</span> params\n\nField<span class=\"o\">.</span>register_lookup<span class=\"p\">(</span>MySQLNotEqual<span class=\"p\">)</span>\n</code></pre></div>\n<p>接着，我们可以里利用 <code class=\"docutils literal notranslate\">Field</code> 注册它。它会替换之前的 <code class=\"docutils literal notranslate\">NotEqual</code> 类，因为拥有相同的 <code class=\"docutils literal notranslate\">lookup_name</code>。</p>\n<p>编译查询指令是，Django 先查找 <code class=\"docutils literal notranslate\">as_%s % connection.vendor</code> 方法，其次 <code class=\"docutils literal notranslate\">as_sql</code>。内置后端的提供商名为 <code class=\"docutils literal notranslate\">sqlite</code>，<code class=\"docutils literal notranslate\">postgresql</code>，<code class=\"docutils literal notranslate\">oracle</code> 和 <code class=\"docutils literal notranslate\">mysql</code>。</p>\n</section>\n<section id=\"how-django-determines-the-lookups-and-transforms-which-are-used\">\n<h2>Django 是如何取舍查询器和转换器的<a class=\"heading-anchor\" href=\"#how-django-determines-the-lookups-and-transforms-which-are-used\"><span class=\"visually-hidden\">Link to this heading</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>某些场景下，你可能期望基于传入的名字动态地返回 <code class=\"docutils literal notranslate\">Transform</code> 或 <code class=\"docutils literal notranslate\">Lookup</code>，而不是指定。例如，有一个字段，存储了一些坐标或尺寸，期望使用以下语法 <code class=\"docutils literal notranslate\">.filter(coords__x7=4)</code> 返回第七个值为 4 的坐标。为此，你需要用以下内容重写 <code class=\"docutils literal notranslate\">get_lookup</code>:</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"k\">class</span> <span class=\"nc\">CoordinatesField</span><span class=\"p\">(</span>Field<span class=\"p\">):</span>\n    <span class=\"k\">def</span> <span class=\"nf\">get_lookup</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> lookup_name<span class=\"p\">):</span>\n        <span class=\"k\">if</span> lookup_name<span class=\"o\">.</span>startswith<span class=\"p\">(</span><span class=\"s1\">&#39;x&#39;</span><span class=\"p\">):</span>\n            <span class=\"k\">try</span><span class=\"p\">:</span>\n                dimension <span class=\"o\">=</span> <span class=\"nb\">int</span><span class=\"p\">(</span>lookup_name<span class=\"p\">[</span><span class=\"mi\">1</span><span class=\"p\">:])</span>\n            <span class=\"k\">except</span> <span class=\"ne\">ValueError</span><span class=\"p\">:</span>\n                <span class=\"k\">pass</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"k\">return</span> get_coordinate_lookup<span class=\"p\">(</span>dimension<span class=\"p\">)</span>\n        <span class=\"k\">return</span> <span class=\"nb\">super</span><span class=\"p\">()</span><span class=\"o\">.</span>get_lookup<span class=\"p\">(</span>lookup_name<span class=\"p\">)</span>\n</code></pre></div>\n<p>随后你需要定义 <code class=\"docutils literal notranslate\">get_coordinate_lookup</code> 正确地返回一个 <code class=\"docutils literal notranslate\">Lookup</code> 子类，用于处理 <code class=\"docutils literal notranslate\">dimension</code> 的相关值。</p>\n<p>有个类似的名字叫做 <code class=\"docutils literal notranslate\">get_transform()</code>。<code class=\"docutils literal notranslate\">get_lookup()</code> 总是要返回 <code class=\"docutils literal notranslate\">Lookup</code> 子类，而 <code class=\"docutils literal notranslate\">get_transform</code> 要返回 <code class=\"docutils literal notranslate\">Transform</code> 子类。千万牢记，<code class=\"docutils literal notranslate\">Transform</code> 对象能被进一步过滤，而 <code class=\"docutils literal notranslate\">Lookup</code> 对象不能。</p>\n<p>过滤时，若只能找到一个名字，我们会查找 <code class=\"docutils literal notranslate\">Lookup</code>。如果有多个名字，将会寻找 <code class=\"docutils literal notranslate\">Transform</code>。在某种情况下，仅有一个名字，且未找到 <code class=\"docutils literal notranslate\">Lookup</code>，我们将查找 <code class=\"docutils literal notranslate\">Transform</code>，并附加 <code class=\"docutils literal notranslate\">exact</code> 查询器。所以的系列调用都以一个 <code class=\"docutils literal notranslate\">Lookup</code> 结束。简单说明：</p>\n<ul class=\"simple\">\n<li><p><code class=\"docutils literal notranslate\">.filter(myfield__mylookup)</code> 将会调用 <code class=\"docutils literal notranslate\">myfield.get_lookup('mylookup')</code>。</p></li>\n<li><p><code class=\"docutils literal notranslate\">.filter(myfield__mytransform__mylookup)</code> 将会调用 <code class=\"docutils literal notranslate\">myfield.get_transform('mytransform')</code>, 接着调用 <code class=\"docutils literal notranslate\">mytransform.get_lookup('mylookup')</code>。</p></li>\n<li><p><code class=\"docutils literal notranslate\">.filter(myfield__mytransform)</code> 会先调用 <code class=\"docutils literal notranslate\">myfield.get_lookup('mytransform')</code>，失败，然后回滚调用 <code class=\"docutils literal notranslate\">myfield.get_transform('mytransform')</code>，随后返回 <code class=\"docutils literal notranslate\">mytransform.get_lookup('exact')</code>。</p></li>\n</ul>\n</section>","rootId":"how-to-write-custom-lookups","toc":[{"title":"一个查询器示例。","anchor":"a-lookup-example","children":[]},{"title":"一个转换器示例。","anchor":"a-transformer-example","children":[]},{"title":"编写一个高效的 abs__lt 查找","anchor":"writing-an-efficient-abs-lt-lookup","children":[]},{"title":"一个双向转换器示例","anchor":"a-bilateral-transformer-example","children":[]},{"title":"为现有的查找编写代替实现","anchor":"writing-alternative-implementations-for-existing-lookups","children":[]},{"title":"Django 是如何取舍查询器和转换器的","anchor":"how-django-determines-the-lookups-and-transforms-which-are-used","children":[]}],"breadcrumbs":[{"docname":"howto/index","title":"操作指南","url":"/zh-hans/4.1/howto/"}],"prev":{"docname":"howto/custom-model-fields","title":"编写自定义模型字段","url":"/zh-hans/4.1/howto/custom-model-fields/"},"next":{"docname":"howto/custom-template-backend","title":"如何编写一个自定义的模板后端","url":"/zh-hans/4.1/howto/custom-template-backend/"},"formats":{"html":"/zh-hans/4.1/howto/custom-lookups/","markdown":"/zh-hans/4.1/howto/custom-lookups.md","json":"/zh-hans/4.1/howto/custom-lookups.json"},"source":"https://github.com/django/django/blob/stable/4.1.x/docs/howto/custom-lookups.txt","official":"https://docs.djangoproject.com/zh-hans/4.1/howto/custom-lookups/","inVersions":["6.1","6.0","5.2","5.1","5.0","4.2","4.1","4.0","3.2","3.1","3.0","2.2","2.1","2.0"],"inLocales":["en","zh-hans","fr","ja","id","it","pt-br","ko","es","el","pl"]}