欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

利用Python的Django框架中的ORM建立查詢API

 更新時(shí)間:2015年04月20日 11:04:47   投稿:goldensun  
這篇文章主要介紹了利用Python的Django框架中的ORM建立查詢API,對(duì)Managers和QuerySets進(jìn)行了著重介紹,需要的朋友可以參考下

 摘要

在這篇文章里,我將以反模式的角度來(lái)直接討論Django的低級(jí)ORM查詢方法的使用。作為一種替代方式,我們需要在包含業(yè)務(wù)邏輯的模型層建立與特定領(lǐng)域相關(guān)的查詢API,這些在Django中做起來(lái)不是非常容易,但通過(guò)深入地了解ORM的內(nèi)容原理,我將告訴你一些簡(jiǎn)捷的方式來(lái)達(dá)到這個(gè)目的。

概覽

當(dāng)編寫(xiě)Django應(yīng)用程序時(shí),我們已經(jīng)習(xí)慣通過(guò)添加方法到模型里以此達(dá)到封裝業(yè)務(wù)邏輯并隱藏實(shí)現(xiàn)細(xì)節(jié)。這種方法看起來(lái)是非常的自然,而且實(shí)際上它也用在Django的內(nèi)建應(yīng)用中。
 

>>> from django.contrib.auth.models import User
>>> user = User.objects.get(pk=5)
>>> user.set_password('super-sekrit')
>>> user.save()

這里的set_password就是一個(gè)定義在django.contrib.auth.models.User模型中的方法,它隱藏了對(duì)密碼進(jìn)行哈希操作的具體實(shí)現(xiàn)。相應(yīng)的代碼看起來(lái)應(yīng)該是這樣:
 

from django.contrib.auth.hashers import make_password
 
class User(models.Model):
 
  # fields go here..
 
  def set_password(self, raw_password):
    self.password = make_password(raw_password)


我們正在使用Django,建立一個(gè)特定領(lǐng)域的頂部通用接口,低等級(jí)的ORM工具。在此基礎(chǔ)上,增加抽象等級(jí),減少交互代碼。這樣做的好處是使代碼更具可讀性、重用性和健壯性。

我們已經(jīng)在單獨(dú)的例子中這樣做了,下面將會(huì)把它用在獲取數(shù)據(jù)庫(kù)信息的例子中。

為了描述這個(gè)方法,我們使用了一個(gè)簡(jiǎn)單的app(todo list)來(lái)說(shuō)明。

注意:這是一個(gè)例子。因?yàn)楹茈y用少量的代碼展示一個(gè)真實(shí)的例子。不要過(guò)多的關(guān)心todo list繼承他自己,而要把重點(diǎn)放在如何讓這個(gè)方法運(yùn)行。
下面就是models.py文件:
 

from django.db import models
 
PRIORITY_CHOICES = [(1, 'High'), (2, 'Low')]
 
class Todo(models.Model):
  content = models.CharField(max_length=100)
  is_done = models.BooleanField(default=False)
  owner = models.ForeignKey('auth.User')
  priority = models.IntegerField(choices=PRIORITY_CHOICES, default=1


想像一下,我們將要傳遞這些數(shù)據(jù),建立一個(gè)view,來(lái)為當(dāng)前用戶展示不完整的,高優(yōu)先級(jí)的 Todos。這里是代碼: 
 

def dashboard(request):
 
  todos = Todo.objects.filter(
    owner=request.user
  ).filter(
    is_done=False
  ).filter(
    priority=1
  )
 
  return render(request, 'todos/list.html', {
    'todos': todos,
  })

注意:這里可以寫(xiě)成request.user.todo_set.filter(is_done=False, priority=1)。但是這里只是一個(gè)實(shí)驗(yàn)。

為什么這樣寫(xiě)不好呢?

首先,代碼冗長(zhǎng)。七行代碼才能完成,正式的項(xiàng)目中,將會(huì)更加復(fù)雜。

其次,泄露實(shí)現(xiàn)細(xì)節(jié)。比如代碼中的is_done是BooleanField,如果改變了他的類(lèi)型,代碼就不能用了。

然后就是,意圖不清晰,很難理解。

最后,使用中會(huì)有重復(fù)。例:你需要寫(xiě)一行命令,通過(guò)cron,每周發(fā)送給所有用戶一個(gè)todo list,這時(shí)候你就需要復(fù)制-粘貼著七行代碼。這不符合DRY(do not repeat yourself)


讓我們大膽的猜測(cè)一下:直接使用低等級(jí)的ORM代碼是反模式的。
如何改進(jìn)呢?

使用 Managers 和 QuerySets
首先,讓我們先了解一下概念。

Django 有兩個(gè)關(guān)系密切的與表級(jí)別操作相關(guān)的構(gòu)圖:managers 和 querysets

manager(django.db.models.manager.Manager的一個(gè)實(shí)例)被描述成 “通過(guò)查詢數(shù)據(jù)庫(kù)提供給Django的插件”。Manager是表級(jí)別功能的通往ORM大門(mén)。每一個(gè)model都有一個(gè)默認(rèn)的manager,叫做objects。
Quesyset (django.db.models.query.QuerySet) 是“數(shù)據(jù)庫(kù)中objects的集合”。本質(zhì)上是一個(gè)SELECT查詢,也可以使用過(guò)濾,排序等(filtered,ordered),來(lái)限制或者修改查詢到的數(shù)據(jù)。用來(lái) 創(chuàng)建或操縱 django.db.models.sql.query.Query實(shí)例,然后通過(guò)數(shù)據(jù)庫(kù)后端在真正的SQL中查詢。

啊?你還不明白?

隨著你慢慢深入的了解ORM,你就會(huì)明白Manager和QuerySet之間的區(qū)別了。


人們會(huì)被所熟知的Manager接口搞糊涂,因?yàn)樗⒉皇强瓷先ツ菢印?/p>

Manager接口就是個(gè)謊言。

QuerySet方法是可鏈接的。每一次調(diào)用QuerySet的方法(如:filter)都會(huì)返回一個(gè)復(fù)制的queryset等待下一次的調(diào)用。這也是Django ORM 流暢之美的一部分。

但是當(dāng)Model.objects 是一個(gè) Manager時(shí),就出現(xiàn)問(wèn)題了。我們需要調(diào)用objects作為開(kāi)始,然后鏈接到結(jié)果的QuerySet上去。

那么Django又是如何解決呢?

接口的謊言由此暴露,所有的QuerySet 方法基于Manager。在這個(gè)方法中,通過(guò)self.get_query_set()的代理,重新創(chuàng)建一個(gè)

QuerySet。
 
class Manager(object):
 
  # SNIP some housekeeping stuff..
 
  def get_query_set(self):
    return QuerySet(self.model, using=self._db)
 
  def all(self):
    return self.get_query_set()
 
  def count(self):
    return self.get_query_set().count()
 
  def filter(self, *args, **kwargs):
    return self.get_query_set().filter(*args, **kwargs)
 
  # and so on for 100+ lines...

更多代碼,請(qǐng)參照Manager的資源文件。

讓我們立刻回到todo list ,解決query接口的問(wèn)題。Django推薦的方法是自定義Manager子類(lèi),并加在models中。

你也可以在model中增加多個(gè)managers,或者重新定義objects,也可以維持單個(gè)的manager,增加自定義方法。

下面讓我們實(shí)驗(yàn)一下這幾種方法:

方法1:多managers

 

class IncompleteTodoManager(models.Manager):
  def get_query_set(self):
    return super(TodoManager, self).get_query_set().filter(is_done=False)
 
class HighPriorityTodoManager(models.Manager):
  def get_query_set(self):
    return super(TodoManager, self).get_query_set().filter(priority=1)
 
class Todo(models.Model):
  content = models.CharField(max_length=100)
  # other fields go here..
 
  objects = models.Manager() # the default manager
 
  # attach our custom managers:
  incomplete = models.IncompleteTodoManager()
  high_priority = models.HighPriorityTodoManager()

這個(gè)接口將以這樣的方式展現(xiàn):
 

>>> Todo.incomplete.all()
>>> Todo.high_priority.all()

這個(gè)方法有幾個(gè)問(wèn)題。


第一,這種實(shí)現(xiàn)方式比較啰嗦。你要為每一個(gè)query自定義功能定義一個(gè)class。

第二,這將會(huì)弄亂你的命名空間。Django開(kāi)發(fā)者吧Model.objects看做表的入口。這樣做會(huì)破壞命名規(guī)則。

第三,不可鏈接的。這樣做不能將managers組合在一起,獲得不完整,高優(yōu)先級(jí)的todos,還是回到低等級(jí)的ORM代碼:Todo.incomplete.filter(priority=1) 或Todo.high_priority.filter(is_done=False)
綜上,使用多managers的方法,不是最優(yōu)選擇。


方法2: Manager 方法

現(xiàn)在,我們?cè)囅缕渌鸇jango允許的方法:在單個(gè)自定義Manager中的多個(gè)方法
 

class TodoManager(models.Manager):
  def incomplete(self):
    return self.filter(is_done=False)
 
  def high_priority(self):
    return self.filter(priority=1)
 
class Todo(models.Model):
  content = models.CharField(max_length=100)
  # other fields go here..
 
  objects = TodoManager()

我們的API 現(xiàn)在看起來(lái)是這樣:
 

>>> Todo.objects.incomplete()
>>> Todo.objects.high_priority()

這個(gè)方法顯然更好。它沒(méi)有太多累贅(只有一個(gè)Manager類(lèi))并且這種查詢方法很好地在對(duì)象后預(yù)留命名空間。(譯注:可以很形象、方便地添加更多的方法)
不過(guò)這還不夠全面。 Todo.objects.incomplete() 返回一個(gè)普通查詢,但我們無(wú)法使用 Todo.objects.incomplete().high_priority() 。我們卡在 Todo.objects.incomplete().filter(is_done=False),沒(méi)有使用。


方法3:自定義QuerySet

現(xiàn)在我們已進(jìn)入Django尚未開(kāi)放的領(lǐng)域,Django文檔中找不到這些內(nèi)容。。。
 

class TodoQuerySet(models.query.QuerySet):
  def incomplete(self):
    return self.filter(is_done=False)
 
  def high_priority(self):
    return self.filter(priority=1)
 
class TodoManager(models.Manager):
  def get_query_set(self):
    return TodoQuerySet(self.model, using=self._db)
 
class Todo(models.Model):
  content = models.CharField(max_length=100)
  # other fields go here..
 
  objects = TodoManager()

我們從以下調(diào)用的視圖代碼中可以看出端倪:

 

>>> Todo.objects.get_query_set().incomplete()
>>> Todo.objects.get_query_set().high_priority()
>>> # (or)
>>> Todo.objects.all().incomplete()
>>> Todo.objects.all().high_priority()

差不多完成了!這并沒(méi)有比第2個(gè)方法多多少累贅,卻得到方法2同樣的好處,和額外的效果(來(lái)點(diǎn)鼓聲吧...),它終于可鏈?zhǔn)讲樵兞耍?br />  

>>> Todo.objects.all().incomplete().high_priority()

然而它還不夠完美。這個(gè)自定義的Manager僅僅是一個(gè)樣板而已,而且 all() 還有瑕疵,在使用時(shí)不好把握,而更重要的是不兼容,它讓我們的代碼看起來(lái)有點(diǎn)怪異。


方法3a:復(fù)制Django,代理做所有事

現(xiàn)在我們讓以上”假冒Manager API“討論變得有用:我們知道如何解決這個(gè)問(wèn)題。我們簡(jiǎn)單地在Manager中重新定義所有QuerySet方法,然后代理它們返回我們自定義QuerySet:
 

class TodoQuerySet(models.query.QuerySet):
  def incomplete(self):
    return self.filter(is_done=False)
 
  def high_priority(self):
    return self.filter(priority=1)
 
class TodoManager(models.Manager):
  def get_query_set(self):
    return TodoQuerySet(self.model, using=self._db)
 
  def incomplete(self):
    return self.get_query_set().incomplete()
 
  def high_priority(self):
    return self.get_query_set().high_priority()

這個(gè)能更好地提供我們想要的API:
 

>>> Todo.objects.incomplete().high_priority() # yay!

除上面那些輸入部分、且非常不DRY,每次你新增一個(gè)文件到QuerySet,或是更改現(xiàn)有的方法標(biāo)記,你必須記住在你的Manager中做相同的更改,否則它可能不會(huì)正常工作。這是配置的問(wèn)題
方法3b: django-model-utils

Python 是一種動(dòng)態(tài)語(yǔ)言。 我們就一定能避免所有模塊?一個(gè)名叫Django-model-utils的第三方應(yīng)用帶來(lái)的一點(diǎn)小忙,就會(huì)有點(diǎn)不受控制了。先運(yùn)行 pip install django-model-utils ,然后……
 

from model_utils.managers import PassThroughManager
 
class TodoQuerySet(models.query.QuerySet):
  def incomplete(self):
    return self.filter(is_done=False)
 
  def high_priority(self):
    return self.filter(priority=1)
 
class Todo(models.Model):
  content = models.CharField(max_length=100)
  # other fields go here..
 
  objects = PassThroughManager.for_queryset_class(TodoQuerySet)()

這要好多了。我們只是象之前一樣 簡(jiǎn)單地定義了自定義QuerySet子類(lèi),然后通過(guò)django-model-utils提供的PassThroughManager類(lèi)附加這些QuerySet到我們的model中。

PassThroughManager 是由__getattr__ 實(shí)現(xiàn)的,它能阻止訪問(wèn)到django定義的“不存在的方法”,并且自動(dòng)代理它們到QuerySet。這里需要小心一點(diǎn),檢查確認(rèn)我們沒(méi)有在一些特性中沒(méi)有無(wú)限遞歸(這是我為什么推薦使用django-model-utils所提供的用不斷嘗試測(cè)試的方法,而不是自己手工重復(fù)寫(xiě))。

做這些有什么幫助?

記得上面早些定義的視圖代碼么?
 

def dashboard(request):
 
  todos = Todo.objects.filter(
    owner=request.user
  ).filter(
    is_done=False
  ).filter(
    priority=1
  )
 
  return render(request, 'todos/list.html', {
    'todos': todos,
  })

加點(diǎn)小改動(dòng),我們讓它看起來(lái)象這樣:
 

def dashboard(request):
 
  todos = Todo.objects.for_user(
    request.user
  ).incomplete().high_priority()
 
  return render(request, 'todos/list.html', {
    'todos': todos,
  })

希望你也能同意第二個(gè)版本比第一個(gè)更簡(jiǎn)便,清晰并且更有可讀性。
Django能幫忙么?


讓這整個(gè)事情更容易的方法,已經(jīng)在django開(kāi)發(fā)郵件列表中討論過(guò),并且得到一個(gè)相關(guān)票據(jù)(譯注:associated ticket叫啥名更好?)。Zachary Voase則建議如下:
 

class TodoManager(models.Manager):
 
  @models.querymethod
  def incomplete(query):
    return query.filter(is_done=False)

通過(guò)這個(gè)簡(jiǎn)單的裝飾方法的定義,讓Manager和QuerySet都能使不可用的方法神奇地變?yōu)榭捎谩?/p>

我個(gè)人并不完全贊同使用基于裝飾方法。它略過(guò)了詳細(xì)的信息,感覺(jué)有點(diǎn)“嘻哈”。我感覺(jué)好的方法,增加一個(gè)QuerSet子類(lèi)(而不是Manager子類(lèi))是更好,更簡(jiǎn)單的途徑。
或者我們更進(jìn)一步思考。退回到在爭(zhēng)議中重新審視Django的API設(shè)計(jì)決定時(shí),也許我們能得到真實(shí)更深的改進(jìn)。能不再爭(zhēng)吵Managers和QuerySet的區(qū)別嗎(至少澄清一下)?


我很確信,不管以前是否曾經(jīng)有過(guò)這么大的重構(gòu)工作,這個(gè)功能必然要在Django 2.0 甚至更后的版本中。

因此,簡(jiǎn)單概括一下:

在視圖和其他高級(jí)應(yīng)用中使用源生的ORM查詢代碼不是很好的主意。而是用django-model-utils中的PassThroughManager將我們新加的自定義QuerySet API加進(jìn)你的模型中,這能給你以下好處:

  •     啰嗦代碼少,并且更健壯。
  •     增加DRY,增強(qiáng)抽象級(jí)別。
  •    將所屬的業(yè)務(wù)邏輯推送至對(duì)應(yīng)的域模型層。

感謝閱讀!

相關(guān)文章

  • YOLOv5中SPP/SPPF結(jié)構(gòu)源碼詳析(內(nèi)含注釋分析)

    YOLOv5中SPP/SPPF結(jié)構(gòu)源碼詳析(內(nèi)含注釋分析)

    其實(shí)關(guān)于YOLOv5的網(wǎng)絡(luò)結(jié)構(gòu)其實(shí)網(wǎng)上相關(guān)的講解已經(jīng)有很多了,但是覺(jué)著還是有必要再給大家介紹下,下面這篇文章主要給大家介紹了關(guān)于YOLOv5中SPP/SPPF結(jié)構(gòu)源碼的相關(guān)資料,需要的朋友可以參考下
    2022-05-05
  • Python中列表和字符串常用的數(shù)據(jù)去重方法總結(jié)

    Python中列表和字符串常用的數(shù)據(jù)去重方法總結(jié)

    關(guān)于數(shù)據(jù)去重,咱們這里簡(jiǎn)單理解下,就是刪除掉重復(fù)的數(shù)據(jù),應(yīng)用的場(chǎng)景比如某些產(chǎn)品產(chǎn)生的大數(shù)據(jù),有很多重復(fù)的數(shù)據(jù),為了不影響分析結(jié)果,我們可能需要對(duì)這些數(shù)據(jù)進(jìn)行去重,所以本文給大家總結(jié)了Python中列表和字符串常用的數(shù)據(jù)去重方法,需要的朋友可以參考下
    2023-11-11
  • 正確的使用Python臨時(shí)文件

    正確的使用Python臨時(shí)文件

    這篇文章主要介紹了正確的使用Python臨時(shí)文件,幫助大家更好的理解和學(xué)習(xí)使用python,感興趣的朋友可以了解下
    2021-03-03
  • python將logging模塊封裝成單獨(dú)模塊并實(shí)現(xiàn)動(dòng)態(tài)切換Level方式

    python將logging模塊封裝成單獨(dú)模塊并實(shí)現(xiàn)動(dòng)態(tài)切換Level方式

    這篇文章主要介紹了python將logging模塊封裝成單獨(dú)模塊并實(shí)現(xiàn)動(dòng)態(tài)切換Level方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-05-05
  • 使用pandas對(duì)矢量化數(shù)據(jù)進(jìn)行替換處理的方法

    使用pandas對(duì)矢量化數(shù)據(jù)進(jìn)行替換處理的方法

    下面小編就為大家分享一篇使用pandas對(duì)矢量化數(shù)據(jù)進(jìn)行替換處理的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-04-04
  • python利用拉鏈法實(shí)現(xiàn)字典方法示例

    python利用拉鏈法實(shí)現(xiàn)字典方法示例

    這篇文章主要介紹了python利用拉鏈法實(shí)現(xiàn)字典的方法,文中給出了詳細(xì)的示例代碼,相信對(duì)大家具有一定的參考價(jià)值,需要的朋友可以們下面來(lái)一起看看吧。
    2017-03-03
  • python仿evething的文件搜索器實(shí)例代碼

    python仿evething的文件搜索器實(shí)例代碼

    這篇文章主要介紹了python仿evething的文件搜索器,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05
  • python數(shù)字圖像處理像素的訪問(wèn)與裁剪示例

    python數(shù)字圖像處理像素的訪問(wèn)與裁剪示例

    這篇文章主要為大家介紹了python數(shù)字圖像處理像素的訪問(wèn)與裁剪示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • Python中__str__()的妙用

    Python中__str__()的妙用

    本文主要介紹了Python中__str__()的妙用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • 詳解matplotlib中pyplot和面向?qū)ο髢煞N繪圖模式之間的關(guān)系

    詳解matplotlib中pyplot和面向?qū)ο髢煞N繪圖模式之間的關(guān)系

    這篇文章主要介紹了詳解matplotlib中pyplot和面向?qū)ο髢煞N繪圖模式之間的關(guān)系,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01

最新評(píng)論