在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Python/ 自定義查找
編寫自定義存儲系統(tǒng)
高級
編寫你的第一個Django應(yīng)用,第5部分
視圖層
Django管理文檔生成器
編寫你的第一個 Django 程序 第3部分
編寫你的第一個Django應(yīng)用,第6部分
模型層
中間件
測試
數(shù)據(jù)庫訪問優(yōu)化
文件上傳
中間件
驗(yàn)證器
基于類的內(nèi)建通用視圖
部署靜態(tài)文件
使用Django認(rèn)證系統(tǒng)
高級教程:如何編寫可重用的應(yīng)用
Model 類參考
Django 初探
使用Django輸出PDF
模型的實(shí)例
模型
文件上傳
進(jìn)行原始的sql查詢
面向程序員
中間件
編寫數(shù)據(jù)庫遷移
TemplateResponse 和 SimpleTemplateResponse
異常
Django中的測試
基礎(chǔ)
管理器
File對象
URL調(diào)度器
加密簽名
國際化和本地化
日志
查詢集
django-admin 和 manage.py
使用基于類的視圖處理表單
聚合
內(nèi)建基于類的視圖的API
如何使用會話
部署 Django
其它
其它核心功能
高級
Django中的密碼管理
模型元選項(xiàng)
按需內(nèi)容處理
查找 API 參考
高級
Django 的快捷函數(shù)
Django 的設(shè)置
Admin
開發(fā)過程
新手入門
基于類的視圖
模型實(shí)例參考
信號
表單素材 ( <code>Media</code> 類)
自定義查找
常見的網(wǎng)站應(yīng)用工具
模型
django.contrib.humanize
Django模版語言
點(diǎn)擊劫持保護(hù)
管理操作
編寫你的第一個 Django 程序 第2部分
Django安全
模式編輯器
多數(shù)據(jù)庫
部署
基于類的視圖
內(nèi)建的視圖
視圖裝飾器
面向設(shè)計師
編寫視圖
應(yīng)用程序
如何使用WSGI 部署
參考
表單 API
文件儲存API
認(rèn)證
國際化和本地化
錯誤報告
基礎(chǔ)
基礎(chǔ)
將遺留數(shù)據(jù)庫整合到Django
教程
Django異常
編寫你的第一個 Django 程序 第4部分
遷移
分頁
重定向應(yīng)用
表單
從零開始
為模型提供初始數(shù)據(jù)
設(shè)置
使用Django輸出CSV
關(guān)聯(lián)對象參考
使用表單
Django 中的用戶認(rèn)證
快速安裝指南
安全問題歸檔
數(shù)據(jù)庫函數(shù)
編寫自定義的django-admin命令
高級教程
管理文件
格式本地化
基于類的通用視圖 —— 索引
安全
系統(tǒng)檢查框架
為Django編寫首個補(bǔ)丁
模板層
Widgets
編寫你的第一個 Django 程序 第1部分
執(zhí)行查詢

自定義查找

New in Django 1.7.

Django為過濾提供了大量的內(nèi)建的查找(例如,exacticontains)。這篇文檔闡述了如何編寫自定義查找,以及如何修改現(xiàn)存查找的功能。關(guān)于查找的API參考,詳見查找API參考。

一個簡單的查找示例

讓我們從一個簡單的自定義查找開始。我們會編寫一個自定義查找ne,提供和exact相反的功能。Author.objects.filter(name__ne='Jack')會轉(zhuǎn)換成下面的SQL:

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

這條SQL是后端獨(dú)立的,所以我們并不需要擔(dān)心不同的數(shù)據(jù)庫。

實(shí)現(xiàn)它需要兩個步驟。首先我們需要實(shí)現(xiàn)這個查找,然后我們需要告訴Django它的信息。實(shí)現(xiàn)是十分簡單直接的:

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

我們只需要在我們想讓查找應(yīng)用的字段上調(diào)用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:

新增了使用裝飾器模式的能力。

我們現(xiàn)在可以為任何foo字段使用 foo__ne。你需要確保在你嘗試創(chuàng)建使用它的任何查詢集之前完成注冊。你應(yīng)該把實(shí)現(xiàn)放在models.py文件中,或者在AppConfigready()方法中注冊查找。

現(xiàn)在讓我們深入觀察這個實(shí)現(xiàn),首先需要的屬性是lookup_name。這需要讓ORM理解如何去解釋name__ne,以及如何使用NotEqual來生成SQL。按照慣例,這些名字一般是只包含字母的小寫字符串,但是唯一硬性的要求是不能夠包含字符串__

然后我們需要定義as_sql方法。這個方法需要傳入一個SQLCompiler對象,叫做 compiler,以及活動的數(shù)據(jù)庫連接。SQLCompiler對象并沒有記錄,但是我們需要知道的唯一一件事就是他們擁有compile()方法,這個方法返回一個元組,含有SQL字符串和要向字符串插入的參數(shù)。在多數(shù)情況下,你并不需要世界使用它,并且可以把它傳遞給process_lhs()process_rhs()。

Lookup作用于兩個值,lhs和rhs,分別是左邊和右邊。左邊的值一般是個字段的引用,但是它可以是任何實(shí)現(xiàn)了查詢表達(dá)式API的對象。右邊的值由用戶提供。在例子Author.objects.filter(name__ne='Jack')中,左邊的值是Author模型的name 字段的引用,右邊的值是'Jack'。

我們可以調(diào)用 process_lhsprocess_rhs 來將它們轉(zhuǎn)換為我們需要的SQL值,使用之前我們描述的compiler 對象。

最后我們用<>將這些部分組合成SQL表達(dá)式,然后將所有參數(shù)用在查詢中。然后我們返回一個元組,包含生成的SQL字符串以及參數(shù)。

一個簡單的轉(zhuǎn)換器示例

上面的自定義轉(zhuǎn)換器是極好的,但是一些情況下你可能想要把查找放在一起。例如,假設(shè)我們構(gòu)建一個應(yīng)用,想要利用abs() 操作符。我們有用一個Experiment模型,它記錄了起始值,終止值,以及變化量(起始值 - 終止值)。我們想要尋找所有變化量等于一個特定值的實(shí)驗(yàn)(Experiment.objects.filter(change__abs=27)),或者沒有達(dá)到指定值的實(shí)驗(yàn)(Experiment.objects.filter(change__abs__lt=27))。

注意

這個例子一定程度上很不自然,但是很好地展示了數(shù)據(jù)庫后端獨(dú)立的功能范圍,并且沒有重復(fù)實(shí)現(xiàn)Django中已有的功能。

我們從編寫AbsoluteValue轉(zhuǎn)換器來開始。這會用到SQL函數(shù)ABS(),來在比較之前轉(zhuǎn)換值。

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)

我們現(xiàn)在可以執(zhí)行之前的查詢。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。

當(dāng)尋找在 Transform之后,哪個查找可以使用的時候,Django使用output_field屬性。因?yàn)樗]有修改,我們在這里并不指定,但是假設(shè)我們在一些字段上應(yīng)用AbsoluteValue,這些字段代表了一個更復(fù)雜的類型(比如說與原點(diǎn)(origin)相關(guān)的一個點(diǎn),或者一個復(fù)數(shù)(complex number))。之后我們可能想指定,轉(zhuǎn)換要為進(jìn)一步的查找返回FloatField類型。這可以通過向轉(zhuǎn)換添加output_field 屬性來實(shí)現(xiàn):

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

這確保了更進(jìn)一步的查找,像abs__lte的行為和對FloatField表現(xiàn)的一樣。

編寫高效的 abs__lt 查找

當(dāng)我們使用上面編寫的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

它的實(shí)現(xiàn)為:

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并不調(diào)用process_lhs()。而是它跳過了由AbsoluteValue完成的lhs,并且使用原始的lhs。這就是說,我們想要得到27 而不是ABS(27)。直接引用self.lhs.lhs是安全的,因?yàn)?AbsoluteValueLessThan只能夠通過AbsoluteValue查找來訪問,這就是說 lhs始終是AbsoluteValue的實(shí)例。

也要注意,就像兩邊都要在查詢中使用多次一樣,參數(shù)也需要多次包含lhs_paramsrhs_params。

最終的實(shí)現(xiàn)直接在數(shù)據(jù)庫中執(zhí)行了反轉(zhuǎn) (27變?yōu)?-27) 。這樣做的原因是如果self.rhs不是一個普通的整數(shù)值(比如是一個F()引用),我們在Python中不能執(zhí)行這一轉(zhuǎn)換。

注意

實(shí)際上,大多數(shù)帶有__abs的查找都實(shí)現(xiàn)為這種范圍查詢,并且在大多數(shù)數(shù)據(jù)庫后端中它更可能執(zhí)行成這樣,就像你可以利用索引一樣。然而在PostgreSQL中,你可能想要向abs(change) 中添加索引,這會使查詢更高效。

一個雙向轉(zhuǎn)換器的示例

我們之前討論的,AbsoluteValue的例子是一個只應(yīng)用在查找左側(cè)的轉(zhuǎn)換??赡苡幸恍┣闆r,你想要把轉(zhuǎn)換同時應(yīng)用在左側(cè)和右側(cè)。比如,你想過濾一個基于左右側(cè)相等比較操作的查詢集,在執(zhí)行一些SQL函數(shù)之后它們是大小寫不敏感的。

讓我們測試一下這一大小寫不敏感的轉(zhuǎn)換的簡單示例。這個轉(zhuǎn)換在實(shí)踐中并不是十分有用,因?yàn)镈jango已經(jīng)自帶了一些自建的大小寫不敏感的查找,但是它是一個很好的,數(shù)據(jù)庫無關(guān)的雙向轉(zhuǎn)換示例。

我們定義使用SQL 函數(shù)UPPER()UpperCase轉(zhuǎn)換器,來在比較前轉(zhuǎn)換這些值。我們定義了bilateral = True來表明轉(zhuǎn)換同時作用在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)

現(xiàn)在,查詢集Author.objects.filter(name__upper="doe")會生成像這樣的大小寫不敏感查詢:

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

為現(xiàn)存查找編寫自動的實(shí)現(xiàn)

有時不同的數(shù)據(jù)庫供應(yīng)商對于相同的操作需要不同的SQL。對于這個例子,我們會為MySQL重新編寫一個自定義的,NotEqual操作的實(shí)現(xiàn)。我們會使用 != 而不是 <>操作符。(注意實(shí)際上幾乎所有數(shù)據(jù)庫都支持這兩個,包括所有Django支持的官方數(shù)據(jù)庫)。

我們可以通過創(chuàng)建帶有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。

當(dāng)編譯一個查詢的時候,Django首先尋找as_%s % connection.vendor方法,然后回退到 as_sql。內(nèi)建后端的供應(yīng)商名稱是 sqlite,postgresql, oracle 和mysql。

Django如何決定使用查找還是轉(zhuǎn)換

有些情況下,你可能想要動態(tài)修改基于傳遞進(jìn)來的名稱, 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)

之后你應(yīng)該合理定義get_coordinate_lookup。來返回一個 Lookup的子類,它處理dimension的相關(guān)值。

有一個名稱相似的方法叫做get_transform()get_lookup()應(yīng)該始終返回 Lookup 的子類,而get_transform() 返回Transform 的子類。記住Transform 對象可以進(jìn)一步過濾,而 Lookup 對象不可以,這非常重要。

過濾的時候,如果還剩下只有一個查找名稱要處理,它會尋找Lookup。如果有多個名稱,它會尋找Transform。在只有一個名稱并且 Lookup找不到的情況下,會尋找Transform,之后尋找在Transform上面的exact查找。所有調(diào)用的語句都以一個Lookup結(jié)尾。解釋一下:

  • .filter(myfield__mylookup)會調(diào)用 myfield.get_lookup('mylookup')。
  • .filter(myfield__mytransform__mylookup) 會調(diào)用 myfield.get_transform('mytransform'),然后調(diào)用mytransform.get_lookup('mylookup')。
  • .filter(myfield__mytransform) 會首先調(diào)用 myfield.get_lookup('mytransform'),這樣會失敗,所以它會回退來調(diào)用 myfield.get_transform('mytransform') ,之后是 mytransform.get_lookup('exact')

譯者:Django 文檔協(xié)作翻譯小組,原文:Custom lookups。

本文以 CC BY-NC-SA 3.0 協(xié)議發(fā)布,轉(zhuǎn)載請保留作者署名和文章出處。

Django 文檔協(xié)作翻譯小組人手緊缺,有興趣的朋友可以加入我們,完全公益性質(zhì)。交流群:467338606。

上一篇:使用Django輸出PDF下一篇:使用表單