首页 > 其他分享 >django 1.8 官方文档翻译: 2-5-7 自定义查找

django 1.8 官方文档翻译: 2-5-7 自定义查找

时间:2023-04-13 14:35:04浏览次数:52  
标签:__ 自定义 rhs 1.8 django 查找 lhs params lookup


自定义查找

New in Django 1.7.

Django为过滤提供了大量的内建的查找(例如,exacticontains)。这篇文档阐述了如何编写自定义查找,以及如何修改现存查找的功能。关于查找的API参考,详见查找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

我们只需要在我们想让查找应用的字段上调用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文件中,或者在AppConfigready()方法中注册查找。

现在让我们深入观察这个实现,首先需要的属性是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_lhsprocess_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 Transform

class 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, Transform

class AbsoluteValue(Transform):
    lookup_name = 'abs'

    def as_sql(self, compiler, connection):
        lhs, params = compiler.compile(self.lhs)
        return "ABS(%s)" % lhs, params

    @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。这就是说,我们想要得到27 而不是ABS(27)。直接引用self.lhs.lhs是安全的,因为 AbsoluteValueLessThan只能够通过AbsoluteValue查找来访问,这就是说 lhs始终是AbsoluteValue的实例。

也要注意,就像两边都要在查询中使用多次一样,参数也需要多次包含lhs_paramsrhs_params

最终的实现直接在数据库中执行了反转 (27变为 -27) 。这样做的原因是如果self.rhs不是一个普通的整数值(比如是一个F()引用),我们在Python中不能执行这一转换。

注意

实际上,大多数带有__abs的查找都实现为这种范围查询,并且在大多数数据库后端中它更可能执行成这样,就像你可以利用索引一样。然而在PostgreSQL中,你可能想要向abs(change) 中添加索引,这会使查询更高效。

一个双向转换器的示例

我们之前讨论的,AbsoluteValue的例子是一个只应用在查找左侧的转换。可能有一些情况,你想要把转换同时应用在左侧和右侧。比如,你想过滤一个基于左右侧相等比较操作的查询集,在执行一些SQL函数之后它们是大小写不敏感的。

让我们测试一下这一大小写不敏感的转换的简单示例。这个转换在实践中并不是十分有用,因为Django已经自带了一些自建的大小写不敏感的查找,但是它是一个很好的,数据库无关的双向转换示例。

我们定义使用SQL 函数UPPER()UpperCase转换器,来在比较前转换这些值。我们定义了bilateral = True来表明转换同时作用在lhsrhs上面:

from django.db.models import Transform

class UpperCase(Transform):
    lookup_name = 'upper'
    bilateral = True

    def 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_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哪个会返回,而不是固定它。比如,你拥有可以储存搭配( 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:
                pass
            finally:
                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。

标签:__,自定义,rhs,1.8,django,查找,lhs,params,lookup
From: https://blog.51cto.com/wizardforcel/6187990

相关文章

  • django 1.8 官方文档翻译: 2-5-10 数据库函数
    数据库函数NewinDjango1.8.下面记述的类为用户提供了一些方法,来在Django中使用底层数据库提供的函数用于注解、聚合或者过滤器等操作。函数也是表达式,所以可以像聚合函数一样混合使用它们。我们会在每个函数的实例中使用下面的模型:classAuthor(models.Model):name=model......
  • django 1.8 官方文档翻译: 3-3-4 管理文件
    管理文件这篇文档描述了Django为那些用户上传文件准备的文件访问API。底层的API足够通用,你可以使用为其它目的来使用它们。如果你想要处理静态文件(JS,CSS,以及其他),参见管理静态文件(CSS和图像)。通常,Django使用MEDIA_ROOT和MEDIA_URL设置在本地储存文件。下面的例子假设你使用这些默认......
  • Cesium的搜索框如何自定义功能
    geocoder提供了地理编码功能用来搜索位置,但是在离线环境下无法使用。为了将这个搜索框用起来,添加根据坐标搜索的功能,可以这么写:viewer.geocoder._form.children[0].placeholder="请输入:经度,纬度";viewer.geocoder.autoComplete=false;functionmyGeocoder(){......
  • WPF 自定义控件 二次渲染 问题记录
    问题将多个自定义控件加载到到一个页面的Grid上显示。然后突然将一个控件从Grid里面清除,控件依然在后台处理数据。过段时间再加入Grid。然后一些已经改变的页面属性就消失了。原因经过查找是一旦控件再次加载,页面属性就会重置。这个有利也有弊端。1是可以利用这点重置页面2......
  • linux系统自定义登录提示信息
    配置说明这里对于centos7.x系统,可以直接编辑/etc/motd文件,增加定制的提示信息,并可以通过不同颜色展示。vim/etc/motd^[[32m设备IP地址:10.10.10.22^[[0m^[[32m使用人员:zhangshan^[[0m^[[31m注意:其它人员使用请和使用人确认^[[0m#注意,如果需要增加展示信息,可以......
  • Django 对实体的增删改查样例
    classUserInfo(models.Model):"""人员信息"""user_id=models.CharField(max_length=20,primary_key=True,blank=False,verbose_name='人员ID')user_name=models.CharField(max_length=200,blank=Fal......
  • django 配置admin 数据管理,增加数据批量上传下载功能
    在使用django-admin带来直接管理数据库带来的便利的同时,我们希望数据能批量上传,为了达到此目的,我们需要django-admin-export 模块一、安装模块pip3installdjango-import-export-ihttps://mirrors.aliyun.com/pypi/simple/二、settings.py注册模块INSTALLED_APPS=......
  • 界面组件Telerik ASP.NET MVC使用指南 - 如何自定义网格过滤
    TelerikUIforASP.NETMVC拥有使用JavaScript和HTML5构建网站和移动应用所需的70+UI部件,来满足开发者的各种需求,提供无语伦比的开发性能和用户体验。它主要是针对专业级的ASP.NET开发,通过该产品的强大功能,开发者可以开发出功能丰富、适应标准广泛的响应式应用程序。在上文中(......
  • [nginx]日志中记录自定义请求头
    前言假设在请求中自定义了一个请求头,key为"version",参数值为“1.2.3”,需要在日志中捕获这个请求头。nginx日志配置只需要用变量http_version就能捕获到自定义的version请求头。示例:log_formatmain'{"@timestamp":"$time_iso8601",' '"connection":"$connection&q......
  • ansible中过滤器的介绍以及如何自定义过滤器
    一、过滤器介绍二、常用过滤器介绍2.1类型转换2.2数学运算2.3字典转换为列表2.4将字典中的所有key生成一个list2.5总结三、自定义过滤器四、总结之前介绍了关于如何通过shell,python,golang等语言实现自定义模块,可以参考这篇文章:今天主要是介绍下如何实现自定......