django 1.8 官方文档翻译: 2-5-7 自定义查找
自定义查找
New in Django 1.7.
Django为过滤提供了大量的内建的查找(例如,exact
和icontains
)。这篇文档阐述了如何编写自定义查找,以及如何修改现存查找的功能。关于查找的API参考,详见查找API参考。
一个简单的查找示例
让我们从一个简单的自定义查找开始。我们会编写一个自定义查找ne
,提供和exact
相反的功能。Author.objects.filter(name__ne='Jack')
会转换成下面的SQL:
"author"."name" <> 'Jack'
这条SQL是后端独立的,所以我们并不需要担心不同的数据库。
实现它需要两个步骤。首先我们需要实现这个查找,然后我们需要告诉Django它的信息。实现是十分简单直接的:
from django.db.models import Lookupclass 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_paramsreturn '%s <> %s' % (lhs, rhs), params
我们只需要在我们想让查找应用的字段上调用register_lookup
,来注册NotEqual
查找。这种情况下,查找在所有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):# ...
Changed in Django 1.8:新增了使用装饰器模式的能力。
我们现在可以为任何foo
字段使用 foo__ne
。你需要确保在你尝试创建使用它的任何查询集之前完成注册。你应该把实现放在models.py
文件中,或者在AppConfig
的ready()
方法中注册查找。
现在让我们深入观察这个实现,首先需要的属性是lookup_name
。这需要让ORM理解如何去解释name__ne
,以及如何使用NotEqual
来生成SQL。按照惯例,这些名字一般是只包含字母的小写字符串,但是唯一硬性的要求是不能够包含字符串__
。
然后我们需要定义as_sql
方法。这个方法需要传入一个SQLCompiler
对象,叫做 compiler
,以及活动的数据库连接。SQLCompiler
对象并没有记录,但是我们需要知道的唯一一件事就是他们拥有compile()
方法,这个方法返回一个元组,含有SQL字符串和要向字符串插入的参数。在多数情况下,你并不需要世界使用它,并且可以把它传递给process_lhs()
和 process_rhs()
。
Lookup
作用于两个值,lhs和rhs,分别是左边和右边。左边的值一般是个字段的引用,但是它可以是任何实现了查询表达式API的对象。右边的值由用户提供。在例子Author.objects.filter(name__ne='Jack')
中,左边的值是Author
模型的name
字段的引用,右边的值是'Jack'
。
我们可以调用 process_lhs
和process_rhs
来将它们转换为我们需要的SQL值,使用之前我们描述的compiler
对象。
最后我们用<>
将这些部分组合成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 Transformclass AbsoluteValue(Transform):lookup_name = 'abs'def as_sql(self, compiler, connection):lhs, params = compiler.compile(self.lhs)return "ABS(%s)" % lhs, params
接下来,为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
。
当寻找在 Transform
之后,哪个查找可以使用的时候,Django使用output_field
属性。因为它并没有修改,我们在这里并不指定,但是假设我们在一些字段上应用AbsoluteValue,这些字段代表了一个更复杂的类型(比如说与原点(origin)相关的一个点,或者一个复数(complex number))。之后我们可能想指定,转换要为进一步的查找返回FloatField
类型。这可以通过向转换添加output_field
属性来实现:
from django.db.models import FloatField, Transformclass AbsoluteValue(Transform):lookup_name = 'abs'def as_sql(self, compiler, connection):lhs, params = compiler.compile(self.lhs)return "ABS(%s)" % lhs, params@propertydef 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 Lookupclass 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_paramsreturn '%s < %s AND %s > -%s' % (lhs, rhs, lhs, rhs), paramsAbsoluteValue.register_lookup(AbsoluteValueLessThan)
有一些值得注意的事情。首先,AbsoluteValueLessThan
并不调用process_lhs()
。而是它跳过了由AbsoluteValue
完成的lhs
,并且使用原始的lhs
。这就是说,我们想要得到27
而不是ABS(27)
。直接引用self.lhs.lhs
是安全的,因为 AbsoluteValueLessThan
只能够通过AbsoluteValue
查找来访问,这就是说 lhs
始终是AbsoluteValue
的实例。
也要注意,就像两边都要在查询中使用多次一样,参数也需要多次包含lhs_params
和rhs_params
。
最终的实现直接在数据库中执行了反转 (27变为 -27) 。这样做的原因是如果self.rhs
不是一个普通的整数值(比如是一个F()
引用),我们在Python中不能执行这一转换。
注意
实际上,大多数带有__abs的查找都实现为这种范围查询,并且在大多数数据库后端中它更可能执行成这样,就像你可以利用索引一样。然而在PostgreSQL中,你可能想要向abs(change) 中添加索引,这会使查询更高效。
一个双向转换器的示例
我们之前讨论的,AbsoluteValue
的例子是一个只应用在查找左侧的转换。可能有一些情况,你想要把转换同时应用在左侧和右侧。比如,你想过滤一个基于左右侧相等比较操作的查询集,在执行一些SQL函数之后它们是大小写不敏感的。
让我们测试一下这一大小写不敏感的转换的简单示例。这个转换在实践中并不是十分有用,因为Django已经自带了一些自建的大小写不敏感的查找,但是它是一个很好的,数据库无关的双向转换示例。
我们定义使用SQL 函数UPPER()
的UpperCase
转换器,来在比较前转换这些值。我们定义了bilateral = True
来表明转换同时作用在lhs
和rhs
上面:
from django.db.models import Transformclass UpperCase(Transform):lookup_name = 'upper'bilateral = Truedef as_sql(self, compiler, connection):lhs, params = compiler.compile(self.lhs)return "UPPER(%s)" % lhs, params
接下来,让我们注册它:
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。对于这个例子,我们会为MySQL重新编写一个自定义的,NotEqual
操作的实现。我们会使用 !=
而不是 <>
操作符。(注意实际上几乎所有数据库都支持这两个,包括所有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_paramsreturn '%s != %s' % (lhs, rhs), paramsField.register_lookup(MySQLNotEqual)
我们可以在Field
中注册它。它取代了原始的NotEqual
类,由于它具有相同的lookup_name
。
当编译一个查询的时候,Django首先寻找as_%s % connection.vendor
方法,然后回退到 as_sql
。内建后端的供应商名称是 sqlite,postgresql, oracle 和mysql。
Django如何决定使用查找还是转换
有些情况下,你可能想要动态修改基于传递进来的名称, Transform
或者 Lookup
哪个会返回,而不是固定它。比如,你拥有可以储存搭配( coordinate)或者任意一个维度(dimension)的字段,并且想让类似于.filter(coords__x7=4)
的语法返回第七个搭配值为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:passfinally:return get_coordinate_lookup(dimension)return super(CoordinatesField, self).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')
。
译者:Django 文档协作翻译小组,原文:Custom lookups。
本文以 CC BY-NC-SA 3.0 协议发布,转载请保留作者署名和文章出处。
Django 文档协作翻译小组人手紧缺,有兴趣的朋友可以加入我们,完全公益性质。交流群:467338606。
django 1.8 官方文档翻译: 2-5-7 自定义查找相关推荐
- django 1.8 官方文档翻译: 3-3-5 编写自定义存储系统
编写自定义存储系统 如果你需要提供自定义文件存储 – 一个普遍的例子是在某个远程系统上储存文件 – 你可以通过定义一个自定义的储存类来实现.你需要遵循以下步骤: 1. 你的自定义储存类必须是djang ...
- django 1.8 官方文档翻译: 6-4-2 编写自定义的django-admin命令
编写自定义的django-admin命令 应用可以通过manage.py注册它们自己的动作.例如,你可能想为你正在发布的Django应用添加一个manage.py动作.在本页文档中,我们将为教程中的 ...
- django 1.8 官方文档翻译: 3-3-1 文件上传
文件上传 当Django在处理文件上传的时候,文件数据被保存在request. FILES (更多关于 request 对象的信息 请查看 请求和响应对象).这篇文档阐述了文件如何上传到内存和硬盘,以 ...
- django 1.8 官方文档翻译: 2-2-3 查找 API 参考
查找 API 参考 New in Django 1.7. 这篇文档是查找 API 的参考,Django 用这些API 构建数据库查询的WHERE 子句.若要学习如何使用 查找,参见执行查询:若要了解如 ...
- django 1.8 官方文档翻译: 2-5-6 多数据库
多数据库 这篇主题描述Django 对多个数据库的支持.大部分Django 文档假设你只和一个数据库打交道.如果你想与多个数据库打交道,你将需要一些额外的步骤. 定义你的数据库 在Django中使用多 ...
- django 1.8 官方文档翻译: 1-1-2 快速安装指南
快速安装指南 在你开始使用 Django 之前,你需要先安装它.我们有一个 完整安装指南 它涵盖了所有的安装步骤和可能遇到的问题:本指南将会给你一个最简单.简洁的安装指引. 安装 Python 作为一 ...
- django 1.8 官方文档翻译: 3-4-2 基于类的内建通用视图
基于类的内建通用视图 编写Web应用可能是单调的,因为你需要不断的重复某一种模式. Django尝试从model和 template层移除一些单调的情况,但是Web开发者依然会在view(视图)层经历 ...
- django 1.8 官方文档翻译:2-5-9 条件表达式
条件表达式 New in Django 1.8. 条件表达式允许你在过滤器.注解.聚合和更新操作中使用 if ... elif ... else的逻辑.条件表达式为表中的每一行计算一系列的条件,并且返 ...
- django 1.8 官方文档翻译:8-5 加密签名
加密签名 web应用安全的黄金法则是,永远不要相信来自不可信来源的数据.有时通过不可信的媒介来传递数据会非常方便.密码签名后的值可以通过不受信任的途径传递,这样是安全的,因为任何篡改都会检测的到. D ...
最新文章
- 深蓝学院第三章:基于卷积神经网络(CNN)的手写数字识别实践
- 命令行创建React项目
- 升级之后的BCH将推动游戏业更上一层楼
- 技术人如何搭建自己的技术博客
- python上机实验报告读取文件_Python程序设计实验报告八 : 文件
- oracle ebs form视频,ORACLE EBS FORM利用模板开发步骤
- /etc/profile、/etc/bashrc、~/.bash_profile、~/.bashrc(转载)
- python os模块详细_python之os模块详解
- mysql 表示时间_MySQL-时间(time、date、datetime、timestamp和year)
- 【JavsScript】webapp的优化整理
- android 进程(复习)
- 程序员常识--OJ系统及ACM测试题库大全
- ValueError: only one element tensors can be converted to Python scalars
- Initramfs文件系统的制作
- gohbase的使用
- CodeForces 464E The Classic Problem | 呆克斯歘 主席树维护高精度
- 致命错误: Call to undefined function mb_detect_encoding()
- alin42490怎样解除_我们应该如何思维42490
- MP4学习(五)ts-mp4源码阅读(3)ftyp box的解析
- 1年时间业务量疯长40倍,谈人人车的平台架构演进之路
热门文章
- 蓝桥杯小朋友排队java_1215. 小朋友排队
- 如何通过cmd网站服务器地址,如何用cmd进入服务器地址
- react接收后端文件_React如何从后端获取数据并渲染到前端?
- 5001 boost之bind库函数
- 傅里叶变换 c语言程序,(快速傅里叶变换)C语言程序汇编
- 第五章——微型计算机与外设的数据传输
- 3.3.4.7. 模式匹配
- uboot copy_from_nand代码详解
- apache php 整合 linux,apache集成php5.6方法分享
- Oracle包和包体