<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                ------------------------- ## 簡介 ###所有譯文同時以 GitHub Issue 的形式發布,[點此閱讀](https://github.com/cundi/Django-Design-Patterns-and-Best-Practices/issues?state=open)。 ## 版權協議 除注明外,所有文章均采用 [Creative Commons BY-NC-ND 3.0(自由轉載-保持署名-非商用-非衍生)](http://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh) 協議發布。 這意味著你可以在非商業的前提下免費轉載,但同時你必須: * 保持文章原文,不作修改。 * 明確署名,即至少注明 `作者:cundi` 字樣以及文章的原始鏈接。 如需商業合作,[請直接聯系作者](https://github.com/cundi/Web.Development.with.Django.Cookbook/issues/3)。 如果你認為譯文對你所有幫助,而且希望看到更多,可以考慮[小額捐助](https://github.com/cundi/Web.Development.with.Django.Cookbook/issues/3)。 # Django-Design-Patterns-and-Best-Practices 中文名:《Django 設計模式與最佳實踐》 英文原版:https://www.packtpub.com/web-development/django-design-patterns-and-best-practices 作者:Arun Ravindran 出版日期:March 2015 特色:Easily build maintainable websites with powerful and relevant Django design patterns 級別:Mastering 頁數:Paperback 222 pages 第三章預覽 *********** 第三章 模型 ----------- 本章,我們會討論以下話題: * 模型的重要性 * 類圖表 * 模型的結構模式 * 模型的行為模式 * 遷移 ## M大于V與C 在Django中,模型就是類,該類提供了一種處理數據庫的面向對象方法。通常,每個類都引用一個數據庫表,每個屬性都引用一個數據庫列。你可以使用一個自動生成的API來查詢這些表。 模型是很多其他組件的基礎。只要你有一個模型,你可以很快地得到模型admin,模型表單,以及所有類型的通用視圖。每種情況下,都需要你編寫一兩行代碼,這樣可以讓它看上去沒有太多魔法。 模型也被用在更多的超出你期望的地方。這是因為Django可以以多種方式運行。Django的一些切入點如下: * 常見的web請求-響應流程 * Django的交互式命令行 * 管理命令 * 測試腳本 * 異步任務隊列,比如Celery 幾乎所有的情況中,模型模塊都需要導入(作為django.setup()的一部分)。因此,最好保證模型遠離任何不必要的依賴,或者導入任何的其他Django組件,比如視圖。 簡而言之,恰當地設計模型是件十分重要的事情。現在,讓我們從SuperBook模型設計開始。 >#### 注釋 **自帶午餐便當** *作者注釋:SuperBook項目的進度會以這樣的一個盒子來表現。你可以跳過這個盒子,但是在web應用項目中的情況下,你缺少的是領悟,經驗 。 >史蒂夫和客戶的第一周——超級英雄情報監控(簡稱為S.H.I.M)。簡單來說,這是一個大雜燴。辦公室是非常未來化的,但是不論做什么事情都需要上百個審核和簽字。 >作為Django開發者的領隊,史蒂夫已經配置好了中型的運行超過兩天的4臺虛擬機。第二天的一個早晨,機器自己不翼而飛了。一個附近的清潔機器人說,機器被法務部們給帶走了,他們要對未經審核的軟件安裝做出處理。 >然而,CTO哈特給予史蒂夫了極大的幫助。他要求機器在一個小時之內完好無損地給還回去。他還對SuperBook項目做出了提前審核以避免將來可能出現的任何阻礙。 >那個下午的稍晚些時候,史蒂夫給他帶了一個午餐便當。身著一件米色外套和淺藍色牛仔褲的哈特如約而至。盡管高出周圍人許多,有著清爽面龐的他依舊那么帥氣,那么平易近人。他問史蒂夫如果他之前是否嘗試過構建一個60年代的超級英雄數據庫。 >”嗯,對的,是哨兵項目么?“,史蒂夫說道。”是我設計的。數據庫看上去被設計成了一個條目-屬性-值形式的模式,有些地方我考慮用反模式。可能,這些天他們有一些超級英雄屬性的小想法。哈特幾乎等不到聽完最后一句,他壓低嗓門道:“沒錯。是我的錯。另外,他們只給了我兩天來設計整個架構。他們這是在要我老命啊!” >聽了這些,史蒂夫的嘴巴張的大大的,三明治也卡在了嘴里。哈特微笑著道:“當然了,我還沒有盡全力來做這件事。只要它成長為100萬美元的單子,我們就可以多花點時間在這該死的數據庫上了。SuperBook用它就能分分鐘完事的,小史你說呢?” >史蒂夫微微點頭稱是。他從來沒有想過在這么樣的地方將會有上百萬的超級英雄出現。 ## 模型搜尋 這是我們頭一次見識到SuperBook中模型。我們只表示了基本模型,以及類圖表中的表單的基本關系,這也是早期嘗試中所特有的情況: ![2015-05-28 14 53 47](https://cloud.githubusercontent.com/assets/10941075/7854171/6549c63c-0549-11e5-8dc9-3550ff9edcdb.png) 讓我們暫且忘掉模型,來談談我們正在構建的對象的術語。每個用戶都有一個賬戶。用戶可以寫多個回復或者多篇文章。**Like**同時關聯到了一個獨立用戶/文章組合。 建議你為自己的模型畫一個這樣類圖表。這一步的某些屬性缺失了,不過你可以在之后對它們進行詳細補充。只要整個項目用圖表表現出來,便可以輕松地分離app了。 下面是創建這個表現的一些提示: * 盒子表示條目,它將成為模型。 * 名詞通常作為條目的終止。 * 箭頭是雙向的,它代表了Django中的三種關系類型其中的一種:一對一,一對多(通過外鍵實現),和多對多。 * 字段表明在模型中根據**條目-關系模型(ER-modle)**定義了一對多關系。換句來說,星號就是聲明外鍵的地方。 類圖表可以映射到下面的Django代碼中(分布于多個應用之中): ```python class Profile(models.Model): user = models.OnToOneField(User) class Post(models.Model): posted_by = models.ForeignKey(User) class Comment(models.Model): commented_by = models.ForeignKey(User) for_post = models.ForeignKey(Post) class Like(models.Model): liked_by = models.ForeignKey(User) post = models.ForeignKey(Post) ``` 后面,我們不會直接地引用**User**,而是使用更常見的**settings.AUTH_USER_MODEL**來。 ### 把model.py分到多個文件中去 就像多數的Django組件那樣,一個大的model.py文件可以在一個包內分割為多個文件。**package**通過一個目錄來實現,它包含多個文件,目錄中的一個文件必須是一個稱為`__init__.py`特殊文件。 所有可以在包級別中暴露的定義都必須在`__init__.py`里使用全局變量域定義。例如,如果我們分割model.py到獨立的類,models子文件夾中的對應文件,比如,postable.py,post.py和comment.py, 之后`__init__.py`包會像這樣: ```python from postable import Postable from post import Post from commnet import Comment ``` 現在你可以像之前那樣導入models.Post了。 在`__init__.py`包中的任何其他代碼都會在包運行時被導入。因此,它是一個任意級別包初始化代碼的理想之地。 ## 結構模式 本節包含多個幫助你設計和構建模型的設計模式。 ### 模式-規范化模型 **問題**:通過設計,模型實例的重復數據引起數據不一致。 **解決方法**:通過規范化,分解模型到更小的模型。使用這些模型之間的邏輯關系來連接他們。 ### 問題細節 想象一下,如果某人用下面的方法設計Post表(省略部分列): | 超級英雄的名字 | 消息 | 發布時間 | | ------------- |:-------------:| -----:| | Captain Temper | 消息已經發布過了? | 2012/07/07/07:15 | | Professor English | 應該用“Is”而不是“Has" | 2012/07/07/07:17 | | Captain Temper | 消息已經發布過了? | 2012/07/07/07:18 | | Capt. Temper | 消息已經發布過了? | 2012/07/07/07:19 | 我希望你注意到了在最后一行的超級英雄名字和之前不一致(船長一如既往的缺乏耐心)。 如果我們看看第一列,我們也不確定哪一個拼寫是正確的——`Captain Temper或者Capt.Temper`。這就是我們要通過規范化消除的一種數據冗余。 ### 詳解 在我們看下完整的規范方案,讓我們用Django模型的上下文來個關于數據庫規范化的簡要說明。 ### 規范化的三個步驟 規范化有助于你更有效地的存儲數據庫。只要模型完全地的規范化處理,他們就不會有冗余的數據,每個模型應該只包含邏輯上關聯到自身的數據。 這里給出一個簡單的例子,如果我們規范化了Post表,我們就可以不模棱兩可地引用發布消息的超級英雄,然后我們需要用一個獨立的表來隔離用戶細節。默認,Django已經創建了用戶表。因此,你只需要在第一列中引用發布消息的用戶的ID,一如下表所示: |用戶ID|消息 |發布時間 | |-----|:------:|---------:| | 12 | 消息已經發布過了? | 2012/07/07/07:15 | | 8 | 應該用“Is”而不是“Has" | 2012/07/07/07:17 | | 12 | 消息已經發布過了? | 2012/07/07/07:18 | | 12 | 消息已經發布過了? | 2012/07/07/07:19 | 現在,不僅僅相同用戶發布三條消息的清楚在列,而且我們可以通過查詢用戶表找到用戶的正確的名字。 通常來說,你會按照模型的完全規規范化表來設計模型,也會因為性能原因而有選擇性地非規范化設計。在數據庫中,**Normal Forms**是一組可以被應用于表,確保表被規范化的指南。一般我們建立第一,第二,第三規范表,盡管他們可以遞增至第五規范表。 這接下來的例子中,我們規范化一個表,創建對應的Django模型。想象下有個名字稱做“Sightings”的表格,它列出了某人第一次發現超級英雄使用能力或者特異功能。每個條目都提到了已知的原始身份,超能力,和第一次發現的地點,包括維度和經度。 | 名字 | 原始信息 | 能力 |第一次使用記錄(維度,經度,國家,時間) | | ----------| :-----------:|:----:|----------------------------------:| |Blitz | Alien |Freeze Flight|+40.75, -73.99; USA; 2014/07/03 23:12 |Hexa |Scientist |Telekinesis Flight|+35.68, +139.73; Japan; 2010/02/17 20:15 |Traveller |Billonaire |Time travel |+43.62, +1.45, France; 2010/11/10 08:20 上面的地理數據提取自http://www.golombek.com/locations.html. ### 第一范式表(1NF) 確認一下的第一張范式表格,這張表必須含有: 多個沒有屬性(cell)的值 一個主鍵作為單獨一列或者一組列(合成鍵) 讓我們試著把表格轉換為一個數據庫表。明顯地,我們的`Power`列破壞了第一個規則。 更新過的表滿足第一范式表。主鍵(用一個`*`標記)是`Name`和`Power`的合并,對于每一排它都應該是唯一的。 |Name*|Origin|Power*|Latitude |Longtitude |Country|Time | |-----|:----:|:----:|:---------:|:---------:|:-----:|:---------:| |Blitz |Alien |Freeze|+40.75170 |-73.99420|USA|2014/07/03 23:12| |Blitz|Alien|Flight|+40.75170|-73.99420|USA|2013/03/12 11:30| |Hexa|Scientist|Telekinesis|+35.68330|+139.73330|Japan|2010/02/17 20:15| |Hexa|Scientist|Filght|+35.68330|+139.73330|Japan|2010/02/19 20:30| |Traveller|Billionaire|Time tavel|+43.61670|+1.45000|France|2010/11/10 08:20| ### 第二范式表 第二范式表必須滿足所有第一范式表的條件。此外,它必須滿足所有非主鍵列都必須依賴于整個主鍵的條件。 我們注意到在前面的表中`Origin`只依賴于超級英雄,即,`Name`。不論我們談論的是哪一個`Power`。因此,`Origin`不是完全地依賴于合成組件-`Name`和`Power`。 這里,讓我們只取出原始信息到一個獨立的,稱做`Origins`的表: |Name*|Origin| |-----|-----:| |Blitz|Alien| |Hexa|Scientist| |Traveller|Billionaire| 現在`Sightings`表更新為兼容第二范式表,它大概是這個樣子: |Name*|Power*|Latitude |Longtitude |Country|Time | |-----|:----:|:---------:|:---------:|:-----:|:---------:| |Blitz |Freeze|+40.75170 |-73.99420|USA|2014/07/03 23:12| |Blitz||Flight|+40.75170|-73.99420|USA|2013/03/12 11:30| |Hexa|Telekinesis|+35.68330|+139.73330|Japan|2010/02/17 20:15| |Hexa|Filght|+35.68330|+139.73330|Japan|2010/02/19 20:30| |Traveller|Time tavel|+43.61670|+1.45000|France|2010/11/10 08:20| ### 第三范式表 在第三范式表中,比表格必須滿足第二范式表,而且應該額外滿足所有的非主鍵列都直接依賴整個主鍵,而且這些非主鍵列都是互相獨立的這個條件。 考慮下`Country`類。給出`維度`和`經度`,你可以輕松地得出`Country`列。即使觀測到超級英雄的地方依賴于`Name-Power`合成鍵,但只是間接地依賴他們。 因此,我們把詳細地址分離到一個獨立的國家表格中: |Location ID|Latitude*|Longtitude*|Country| |-----------|:-------:|:---------:|------:| 1|+40.75170|-73.99420|USA| 2|+35.68330|+139.73330|Japan| 3|+43.61670|+1.45000|France| 現在`Sightings`表格的第三范式表大抵如此: |User ID*|Power*|Location ID|Time| ---------|:----:|:---------:|---:| 2|Freeze|1|2014/0703 23:12 2|Flight|1|2013/03/12 11:30 4|Telekinesis|2|2010/02/17 20:15 4|Flight|2|2010/02/19 20:30 7|Time tavel|3|2010/11/10 08:20 如之前所做的那樣,我們用對應的`User ID`替換了超級英雄的名字,這個用戶ID用來引用用戶表格。 ### Django模型 現在我們可以看看這些規范化的表格可以用來表現Django模型。Django中并不直接支持合成鍵。這里用到的解決方案是應用代理鍵,以及在`Meta`類中指定`unique_together`屬性: ```python class Origin(models.Model): superhero = models.ForeignKey(settings.AUTH_USER_MODEL) origin = models.CharField(max_length=100) class Location(models.Model): latitude = models.FloatField() longtitude = models.FloatField() country = models.CharField(max_length=100) class Meta: unique_together = ("latitude", "longtitude") class Sighting(models.Model): superhero = models.ForeignKey(settings.AUTH_USER_MODEL) power = models.CharField(max_length=100) location = models.ForeignKey(Location) sighted_on = models.DateTimeField() class Meta: unique_together = ("superhero", "power") ``` ### 性能和非規范化 規范化可能對性能有不利的影響。隨著模型的增長,需要應答查詢的連接數也隨之增加。例如,要在美國發現具有冷凍能力的超級英雄的數量,你需要連接四個表格。先前的內容規范后,任何信息都可以通過查詢一個單獨的表格被找到。 你應該設計模式以保持數據規范化。這可以維持數據的完整。然而,如果你面臨擴展性問題,你可以有選擇性地從這些模型取得數據以生成非規范化的數據。 >##### 提示 **最佳實踐** *因設計而規范,又因優化而非規范* >例如,在一個確定的國家中計算觀測次數是非常普通的,然后將觀測次數作為一個附加的字段到`Location`模型。現在,你可以使用Django ORM 繼承其他的查詢,而不是一個緩存的值。 >然而,你需要在每次添加或者移除觀測時更新這個計數。你需要添加該計算到*Singhting*的`save`方法,添加一個信號處理器,甚至使用一個異步任務去計算。 >如果你有一個跨越多個表的負責查詢,比如國家的超能力計算,你需要創建一個獨立的非規范表格。就像前面那樣,我們需要在每一次規范化模型中的數據改變時更新這個非規范的表格。 >令人驚訝的是非規范化在大型的網站中是非常普遍的,因為它是數度和存儲空間兩者之間的折衷。今天的存儲空間已經比較便宜了,然而速度也是用戶體驗中至關重要的一環。因此,如果你的查詢耗時過于久的話,那么就需要考慮非規范化了。 ## 我們應該一直使用規范化嗎? 過多的規范化是是件不必要的事。有時候,它可以引入一個非必需的能夠重復更新和查詢的表格。 例如,你的`User`模型或許有好多個家庭地址的字段,你可以規范這些字段到一個`Address`模型中。可是,多數情況下,把一個額外的表引進數據庫是沒有必要的。 與其針對大多數的非規范化設計,不如在代碼重構之前仔細地衡量每個非規范化的機會,對性能和速度上做出一個折衷的選擇。 ## 模式-模型mixins **問題**:明顯地模型含有重復的相同字段/或者方法,違反了DRY原則。 **方案**:提取公共字段和方法到各種不同的可重復使用的模型mixins中。 ## 問題細節 設計模型之時,你或許某些個公共屬性或者行為跨類共享。烈日,`Post`和`Comment`模型需要一直跟蹤自己的`created`日期和`modified`日期。手動地復制-粘貼字段和它們所關聯的方法并不符合DRY原則。 由于Django的模型是類,像合成以及繼承這樣的面向對象方法都是可以選擇的解決方案。然而,合成(具有包含一個共享類實例的屬性)需要一個額外的間接地訪問字段的標準。 繼承是有技巧的。我們使用一個`Post`和`Comment`的公共基類。然而,在Django中有三種類型的繼承:**concrete(具體)**, **abstract(抽象)**, 和**proxy(代理)**。 具體繼承的運行是源于基類,就像你在Python類中通常用到的那樣。不過,在Django中,這個基類將被映射到一個獨立的表中。每次你訪問基本字段時,都需要一個準確的連接。這會帶來非常糟糕的性能問題。 代理繼承只能添加新的行為到父類。你不能夠添加新字段。因此,這種情況下它也不大好用。 最后,我們只有托付于抽象繼承了。 ## 詳解 抽象基類是用于模型之間共享數據和行為的游戲簡潔方案。當你定義一個基類時,它在數據中沒有創建任何與之對象的表。反而,這些字段是在派生的非基類中創建的。 訪問抽象基類字段不要`JOIN`語句。有支配的字段結構表也是不解自明的。對于這些優勢,大多數的Django項目都使用抽象基類實現公共字段或者方法。 使用抽象模型是局限的: 它們不能夠擁有外鍵或者來自其他模型的多対多字段。 它們不能夠被實例化或者保存。 它們不能夠直接用在查詢中,因為它沒有管理器。 下面是post和comment類如何使用一個抽象基類初始設計的: ```python class Postable(models.Model): created = models.DateTimeField(auto_now_add=True) modified = modified.DateTimeField(auto_now=True) message = models.TextField(max_length=500) class Meta: abstract = True class Post(Postable): ... class Comment(Postable): ... ``` 要將一個模型轉換到抽象基類,你需要在它的內部`Meta`類中寫上`abstract = True`。這里的`Postable`是一個抽象基類。可是,它不是那么的可復用。 實際上,如果有一個類含有`created`和`modified`字段,我們在后面就可以在附近的任何需要時間戳的模型中重復使用這個時間戳功能。 ### 模型mixins 模型mixins是一個可以把抽象基類當作父類來添加的模型。不像其他的語法,比如Java那樣,Python支持多種繼承。因此,你可以列出一個模型的任意數量的父類。 Mixins應該是互相垂直的而且易于組合的。把一個mixin放進基類的列表,這些mixin應該可以正常運行。這樣看來,它們在行為上更類似于合成而非繼承。 較小的mixin的會更好。不論何時一個mixin變得臃腫,而且又違反了獨立響應原則,就要考慮把它重構到一個更小的類。就讓一個mixin一次做好一件吧。 在前面的例子中,模型mixin用于更新`created`和`modified`的時間可以輕松地分解出來,一如下面代碼所示: ```python class TimeStampedModel(models.Model): created = modified.TimeStampModel(auto_now_add=True) modified = modified.DateTimeField(auto_now=True) class Meta: abstract = True class Postable(TimeStampedModel): message = models.TextField(max_length=500) ... class Meta: abstract = True class Post(Postable): ... class Comment(Postable): ... ``` 我們現在有兩個超類了。不過,功能之間都完全地獨立。mixin可以分離到自己的模塊之內,并且另外的上下文中被復用。 ## 模式-用戶賬戶 **問題**:每一個網站都存儲一組不同的用戶賬戶細節。然而,Django的內建User模型旨在針對認證細節。 **方案**:用一個一對一關系的用戶模型,創建一個用戶賬戶類。 ## 問題細節 Django提供一個開箱即用的相當不錯的User模型。你可以在創建超級用戶或者登錄amdin接口的時候用到它。它含有少量的基本字段,比如全名,用戶名,和電子郵件。 然而,大多數的現實世界項目都保留了很多關于用戶的信息,比如他們的地址,喜歡的電影,或者它們的超能力。打Django1.5開始,默認的User模型就可以被擴展或者替換掉。不過,官方文檔極力推薦只存儲認證數據,即便是在定制的用戶模型中也是如此(畢竟,用戶模型也是所屬于`auth`這個app的)。 某些項目是需要多種類型的用戶的。例如,SuperBook可以被超級英雄和非超級英雄所使用。這里或許會有一些公共字段,以及基于用戶類型的不同字段。 ## 詳解 官方推薦解決方案是創建一個用戶賬戶模型。它應該和用戶模型有一個一對一的關系。其余的全部用戶信息都存儲于該模型: ```python class Profile(models.Model): user = models.OnToOneField(settings.AUTH_USER_MODEL, primary_key=True) ``` 這里推薦你明確的將`primary_key`賦值為`True`以阻止類似PostgreSQL這樣的數據庫后端中的并發問題。剩下的模型可以包含其他的任何用戶詳情,比如生日,喜好色彩,等等。 設計賬戶模型之時,建議所有的賬戶詳情字段都必須是非空的,或者含有一個默認值。憑直覺我們就知道用戶在注冊時是不可能填寫完所有的賬戶細節的。此外,我們也要確保創建賬戶實例時,信號處理器沒有傳遞任何初始參數。 ### 信號 理論上,每一次用戶模型的創建都必須把對應的用戶賬戶實例創建好。這個操作通常利用信號來完成。例如,我們可以使用下面的信號處理器偵聽用戶模型的`post_save`信號: ```python # signals.py from django.db.models.signals import post_save from django.dispatch import receiver from django.conf import settings from . import models @receiver(post_save, sender=settings.AUTH_USER_MODEL) def create_profile_handler(sender, instance, created, **kwargs): if not created: return # Create the profile object, only if it is newly created profile = models.Profile(user=instance) profile.save() ``` 注意賬戶模型除了用戶實例之外沒有傳遞額外的參數。 之前沒有指定初始信號代碼的地方。通常,它們在`models.py`中(這是不可靠的)導入或者執行。不過,隨著Django 1.7的app載入重構,應用初始化代碼位置的問題也很好的解決了。 首先,為你的應用創建一個`__init__.py`包以引用應用的`ProfileConfig`: default_app_config = "profile.apps.ProfileConfig" 接下來是`app.py`中,子類`ProfileConfig`的方法,可于`ready`方法中配置信號: ```python # app.py from django.apps import AppConfig class ProfileConfig(AppConfig): name = "profiles" verbose_name = "User Profiles" def ready(self): from . import signals ``` 隨著信號的配置,對所有的用戶來說,訪問`user.profile`應該都返回一個`Profile`對象,即使是最新創建的用戶也是如此。 ### Admin 現在,用戶的詳情為存在admin內的兩個不同地方:普通用戶admin頁面中的認證細節,在一個獨立賬戶admin頁面中的相同用戶的補充賬戶詳情。但是這樣做非常麻煩。 為了操作方便,賬戶admin可以通過定義一個自定義的`UserAdmin`嵌入到默認的用戶admin中: ```python # admin.py from django.contrib import admin from .models import Profile from django.contrib.auth.models import User class UserProfileInline(admin.StackedInline): model = Profile class UserAdmin(admin.UserAdmin): inlines = [UserProfileInline] admin.site.unregister(User) admin.site.register(User, UserAdmin) ``` ### 多賬戶類型 假設在應用中你需要幾種類型的用戶賬戶。這里需要有一個字段去跟蹤用戶使用的是哪一種賬戶類型。賬戶數據本身需要存儲在獨立的模型中,或者存儲在一個統一的模型中。 建議使用聚合賬戶的方法,因為它讓改變賬戶類型而不丟失賬戶細節,并具有靈活性,減小復雜度。在這個房中中,賬戶模型包含一個所有賬戶類型的字段超集。 例如,SuperBook會需要一個`SuperHero`類型賬戶,和一個`Ordinary`(非超集英雄)賬戶。它可以用一個獨立的統一賬戶模型實現: ```python class BaseProfile(models.Model): USER_TYPES = ( (0, 'Ordinary'), (1, 'SuperHero'), ) user = models.OnToOneField(settings.AUTH_USER_MODEL, primary_key=True) user_type = models.IntegerField(max_length=1, null=True, choices=USER_TYPES) bio = models.CharField(max_length=200, blank=True, null=True) def __str__(self): return "{}:{:.20}".format(self.user, self.bio or "") class Meta: abstract = True class SuperHeroProfile(models.Model): origin = models.CharField(max_length=100, blank=True, null=True) class Meta: abstract = True class OrdinaryProfile(models.Model): address = models.CharField(max_length=200, blank=True, null=True) class Meta: abstract = True class Profile(SuperHeroProfile, OrdinaryProfile, BaseProfile): pass ``` 我們組織賬戶細節到多個抽象基類再到獨立的關系中。`BaseProfile`類包含所有的不關心用戶類型的公共賬戶細節。它也有一個`user_type`字段,它持續追蹤用戶的激活賬戶。 `SuperHeroProfile`類和`OrdinaryProfile`類分別包含所有到超級英雄和非超級英雄特定賬戶細節。最后,所有這些基類的`profile`類創建了一個賬戶細節的超集。 使用該方法時要主要的一些細節: 所有屬于類的字段或者它抽象基類都必須是非空的,或者有一個默認值。 這個方法或許會為了每個用戶而消耗更多的數據庫,但是卻帶來極大的靈活性。 賬戶類型的激活和非激活字段都必須在模型外部是可管理的。 說到,編輯賬戶的表必須顯示合乎目前激活用戶類型的字段。 ## 模式-服務模式 **問題**:模型會變得龐大而且不可管控。當一個模型不止實現一個功能時,測試和維護也會變得困難。 **解決方法**:重構出一組相關方法到一個專用的`Service`對象中。 ### 問題細節 富模型,瘦視圖是一個通常要告訴Django新手的格言。理論上,你的視圖不應該包含任何其他表現邏輯。 可是,隨著時間的推移,代碼段不能夠放在任意地點,除非你打算將它們放進模型中。很快,模型會變成一個代碼的垃圾場。 下面是模型可以`Service`對象的征兆: 1. 與擴展能服務交互,例如web服務中,檢查一個用戶具有資格。 2. 幫助任務不會處理數據庫,例如,生成一個短鏈接,或者針對用戶的驗證碼。 3. 牽涉到一個短命的對象時不會存在數據庫狀態記錄,烈日,創建一個AJAX調用的JSON響應。 4. 對于長時間運行的任務設計到了多實例,比如Celery任務。 Django中的模型遵循著激活記錄模式。理論上,它們同時封裝應用羅即數據庫訪問。不過,要記得保持應用邏輯最小化。 在測試時,如果我們發現沒有對數據庫建模的必要,甚至不會用到數據庫,那么我們需要考慮把分解模型類。建議這種場合下使用`Service`對象。 ### 詳解 服務對象是封裝一個`服務`或者和系統狡猾的普通而老舊的Python對象(POPOs)。它們通常保存在一個獨立的稱為`service.py`或者`utils.py`的文件。 例如,像下面這樣,檢查一個web服務是否作為一個模型方法: ```python class Profile(models.Model): ... def is_superhero(self): url = "http://api.herocheck.com/?q={0}".format( self.user.username ) return webclient.get(url) ``` 該方法可以使用一個服務對象來重構: ```python from .services import SuperHeroWebAPI def is_superhero(self): return SuperHeroWebAPI.is_superhero(self.user.username) ``` 現在服務對象可以定義在`services.py`中了: ```python API_URL = "http://api.herocheck.com/?q={0}" class SuperHeroWebAPI: ... @staticmehtod def is_hero(username): url = API_URL.format(username) return webclient.get(url) ``` 多數情況下,`Service`對象的方法是無狀態的,即,它們基于函數參數不使用任何的類屬性來獨自執行動作。因此,最好明確地把它們標記為靜態方法(就像我們對`is_hero`所做的那樣)。 可以考慮把業務邏輯和域名邏輯從模型遷移到服務對象中去。這樣,你可以在Django應用的外部很好使用它們。 想象一下,由于業務的原因要依據某些用戶的名字把這些要成為超級英雄的用戶加進黑名單。我們的服務對象稍微改動以下就可以支持這個功能: ```python class SuperHeroWebAPI: ... @staticmethod def is_hero(username): blacklist = set(["syndrome", "kcka$$", "superfake"]) ulr = API_URL.format(username) return username not in blacklist and webclient.get(url) ``` 理論上,服務對象自包含的。這使它們不用建模——即數據庫,也可以易于測試。它們也易于復用。 Django中,耗時服務以Celery這樣的異步任務隊列方式執行。通常,`Service`對象以Celery任務的方式執行操作。這樣的任務可以周期性地運行或者延遲運行。 ## 檢索模式 本節包含處理模型屬性的訪問,或者對模型執行查詢。 ### 模式-屬性字段 **問題**:模型有以方法實現的屬性。可是,這些屬性不應該保存到數據庫。 ***解決方案*:對這樣的方法使用屬性裝飾器。 ### 問題詳情 模型字段存儲每個實例的屬性,比如名,和姓,生日,等等。它們存儲于數據庫之中。可是,我們也需要訪問某些派生的屬性,比如一個完整的名字和年齡。 它們可以輕易地計算數據庫字段,因此它們不需要單獨地存儲。在某些情況下,它們可以成為一個檢查所提供的年齡,會員積分,和激活狀態是否合格的條件語句。 簡潔明了的實現這個方法是定義比如類似`get_age`這樣函數: ```python class BaseProfile(models.Model): birthdate = models.DateField() #... def get_age(self): today = datetime.date.today() return (today.year - self.birthdate.year) - int( (today.month, today.day) < (self.birthdate.month, self.birthdate.day) ) ``` 調用`profile.get_age()`會通過計算調整過的月和日期所在的那個年份的不同來返回用戶的年齡。 不過,這樣調用`profile.age`變得更加可讀(和Python范)。 ### 詳解 Python類可以使用`property`裝飾器把函數當作一個屬性來使用。這樣,Django模型也可以較好地利用它。替換前面那個例子中的函數: @property def age(self): 現在我們可以用`profile.age`來訪問用戶的年齡。注意,函數的名稱要盡可能的短。 屬性的一個重大缺陷是它對于ORM來說是不可訪問的,就像模型的方法那樣。你不能夠在一個`Queryset`對象中使用它。例如,這么做是無效的,`Profile.objects.exlude(age__lt=18)。 它也是一個定義一個屬性來隱藏類內部細節的好主意。這也正式地稱做*得墨忒耳定律*。簡單地說,定律聲明你應該只訪問自己的直屬成員或者“僅使用一個點號”。 例如,最好是定義一個`profile.birthyear`屬性,而不是訪問`profile.birthdate.year`。這樣,它有助于你隱藏`birthdate`字段的內在結構。 >#### 提示 **最佳實踐** *遵循得墨忒耳定律,并且訪問屬性時只使用點號* >該定律的一個不良反應是它導致在模型中有多個包裝器屬性被創建。這使模型膨脹并讓它們變得難以維護。利用定律來改進你的模型API,減少模型間的耦合,在任何地方都是可行的。 ### 緩存特性 每次我們調用一個屬性時,就要重新計算函數。如果計算的代價很大,我們就想到了緩存結果。因此,下次訪問屬性,我們就拿到了緩存的結果。 ```python from django.utils.function import cached_property #... @cached_property def full_name(self): # 代價高昂的操作,比如,外部服務調用 return "{0} {1}".format(self.firstname, self.lastname) ``` 緩存的值會作為Python實例的一部分而保存。只要實例一直存在,就會得到同樣的返回值。 對于一個保護性機制,你或許想要強制執行高昂代價操作以確保過期的值不會返回。如此情境之下,設置一個`cached=False`這樣的關鍵字參數能夠阻止返回緩存值。 ## 模式-定制模型管理器 **問題**:某些模型的定義的查詢被重復地訪問,這徹徹底底的違反了DRY原則。 **解決方案**:定義自定義的管理器以給常見的查詢一個更有意義的名字。 ### 問題細節 每一個Django的模型都有一個默認稱做`objects`的管理器。調用`objects.all()`會返回數據庫中的這個模型的所有條目。通常,我們只對所有條目的子集感興趣。 我們應用多種過濾器以找出所需的條目組。挑選它們的原則常常是我們的核心業務邏輯。例如,我們發現使用下面的代碼可以通過public訪問文章: ```python public = Posts.objects.filter(privacy="public") ``` 這個標準在未來或許會改變。我們或許也想要檢查文章是否標記為編輯。這個改變或許如此: ```python public = Posts.objects.filter(privacy=POST_PRIVACY.Public, draft=Flase) ``` 可是,這個改變需要使在任何地方都要用到公共文章。這讓人非常沮喪。這僅需要一個定義這樣常見地查詢而無需“自我重復”。 ### 詳解 `Querysets`是一個極其有用的抽象概念。它們僅在需要時進行惰性查詢。因此,通過鏈式方法(一個流暢的界面)構建更長的`Querysets`并不影響性能。 事實上,應該更多的過濾會使結果數據集縮減。這樣做通常可以減少結果的內存消耗。 模型管理器是一個模型獲取自身`Queryset`對象的便利接口。換句話來講,它們有助于你使用Django的ORM訪問潛在的數據庫。事實上,`QuerySet`對象上管理器以一個非常簡單的包裝器實現。 ```python >>> Post.objects.filter(posted_by__username="a") [<Post:a: Hello World>, <Post:a: This is Private!>] >>> Post.objects.get_queryset().filter(posted_yb__username="a") [<Post:a: Hello World>, <Post:a: This is Private!>] ``` 默認的管理器由Django創建,`objects`有多種方法返回`Queryset`,比如`all`,`filter`或者`exclude`。可是,它們生成一個到數據庫的低級API。 定制管理器用于創建域名指定,更高級的API。這樣不僅更加具有可讀性而且通過實現細節減輕影響。因此,你能夠在更高級的抽象概念上工作,緊密地模型化到域名。 前面的公共文章例子可以輕松地轉換到一個定制的管理器: ```python # managers.py from django.db.models.query import Queryset class PostQuerySet(QuerySet): def public_posts(self): return self.filter(privacy="public") PostManager = PostQuerySet.as_manager ``` 這是一個在Django 1.7中從`QuerySet`對象創建定制管理器的捷徑。不像前面的其他方法,這個`PostManager`對象像默認的`objects`管理器一樣是鏈式的。 如下所示,使用我們的定制管理器替換默認的`objects`管理器也是可行的: ```python from .managers import PostManager class Post(Postable): ... objects = PostManager() ``` 這樣做,訪問`public_posts`相當簡單: ```python public = Post.objects.public_posts() ``` 因此返回值是一個`QuerySet`,它們可以更進一步過濾: ```python public_apology = Post.objects.public_posts().filter( message_startwith = "Sorry" ) ``` `QuerySets`由多個有趣的屬性。在下一章,我們可以看看某些帶有合并的`QuerySet`的常見地模式。 ### Querysets的組合動作 事實上,對于它們的名字,`QuerySets`支持多組操作。為了說明,考慮包含用戶對象的兩個`QuerySets`: ```python >>> q1 = User.objects.filter(username__in["a", "b", "c"]) [<User:a>, <User:b>, <User:c>] >>> q2 = User.objects.filter(username__in["c", "d"]) [<User:c>, <User:d>] ``` 對于一些組合操作可以執行以下動作: *Union*:合并,移除重復動作。使用`q1`|`q2`獲得[`<User: a>, <User: b>, <User: c>, <User: d>`] *Intersection*:找出公共項。使用`q1`|`q2`獲得[`<User: c>] *Difference*:從第一個組中移除第二個組。該操作并不按邏輯來。改用`q1.exlude(pk__in=q2)獲得[<User: a>, <User: b>] 同樣的操作我們也可以用`Q`對象來完成: ```python from django.db.models import Q # Union >>> User.objects.filter(Q(username__in["a", "b", "c"]) | Q(username__in=["c", "d"])) [`<User: a>, <User: b>, <User: c>, <User: d>`] # Intersection >>> User.objects.filter(Q(username__in["a", "b", "c"]) & Q(username__in=["c", "d"])) [<User: c>] # Difference >>> User.objects.filter(Q(username__in=["a", "b", "c"]) & ~Q(username__in=["c", "d"])) [<User: a>, <User: b>] ``` 注意執行動作所使用`&`(和)以及`~`(否定)的不同。`Q`對象是非常強大的,它可以用來構建非常復雜的查詢。 可是,`Set`雖類似但卻不完美。`QuerySets`不像數學上的集合,那樣按照順序來。因此,在這一方面它們更接近Python的列表數據結構。 ### 鏈接多個Querysets 目前為止,我們已經合并了屬于相同基類的同類型`QuerySets`。可是,我們或許需要合并來自不同模型的`QuestSets`,并對它們執行操作。 例如,一個用戶的活動時間表包含了它們自身所有的按照反向時間順序所發布的文章和評論。之前合并的`QuerySets`方法是不會起作用的。一個較為天真的做法是把它們轉換到列表,連接并排列這個列表: ```python >>> recent = list(posts)+list(comments) >>> sorted(recent, key=lambda e: e.modified, reverse=True)[:3] [<Post: user: Post1>, <Comment: user: Comment1>, <Post: user: Post0>] ``` 不幸的是,這個操作已經對惰性的`QuerySets`對象求值了。兩個列表的內存使用算在一起可能很大內存開銷。另外,轉換一個龐大的`QuerySets`到列表是很慢很慢的。 一個更好的解決方案是使用迭代器減少內存消耗。如下,使用`itertools.chain`方法合并多個`QuerySets`: ```python >>> from itertools import chain >>> recent = chain(posts, comments) >>> sorted(recent, key=lambda e: e.modified, reverse=True)[:3] ``` 只要計算`QuerySets`,連接數據的開銷都會非常搞。因此,重要的是,盡可能長的僅有的不對`QuerySets`求值的操作時間。 >## 提示 盡量延長`QuerySets`不求值的時間。 ## 遷移 遷移讓你改變模型時更有信心。說的Django 1.7,遷移已經是開發流程中基本的易于使用的一部分了。 新的基本流程如下: 1. 第一次定義模型類的話,你需要運行: python manage.py makemigrations <app_label> 2. 這將在`app/migrations/`文件夾內創建遷移腳本。 在同樣的(開發)環境中運行以下命令: python manage.py migrate <app_label> 3. 這將對數據庫應用模型變更。有時候,遇到的問題有,處理默認值,重命名,等等。 4. 普及遷移腳本到其他的環境。通常,你的版本控制工具,例如,Git,會小心處理這事。當最新的源釋出時,新的遷移腳本也會隨之出現。 5. 在這些環境中運行下面的命令以應用模型的改變: python manage.py migarte <app_label> 不論何時要將變更應用到模型,請重復以上1-5步驟。 如果你在命令里忽略了app標簽,Django會在每一個app中發現未應用的變更并遷移它們。 ## 總結 模型設計要正確地操作很困難。它依然是Django開發的基礎。本章,我們學習了使用模型時多個常見模式。每個例子中,我們都見到了建議方案的作用,以及多種折衷方案。 這下一章,我們會用視圖和URL配置來驗證所遇到的常見設計模式。 -------------------- ? Creative Commons BY-NC-ND 3.0 | [我要訂閱](https://github.com/cundi/Django-Design-Patterns-and-Best-Practices/subscription) | [我要捐助](https://github.com/cundi/Web.Development.with.Django.Cookbook/issues/3) -------------------- ? Creative Commons BY-NC-ND 3.0 | [我要訂閱](https://github.com/cundi/Django-Design-Patterns-and-Best-Practices/subscription) | [我要捐助](https://github.com/cundi/Web.Development.with.Django.Cookbook/issues/3) 目錄預覽 *********************************** 內容目錄 ----------------- * Django設計模式與最佳實踐 * 第一章 Django與模式 1. Django是什么? 2. Django的故事 * 一個框架的誕生 * 移除魔法 * Django堅持做到更好 * Django是如何工作的? 3. 什么是模式? * 四人組模式 * Django是MVC架構嗎? * 福勒模式 * 還存在更多的模式嗎? 4. 本書的模式 * 鑒定模式 * 如何使用模式 5. 最佳實踐 * Python之禪和Django的設計哲學 6. 總結 * 第二章 應用模式 1. 如何獲取需求 2. 你會講故事嗎? 3. HTML模型 4. 設計應用 * 將一個項目分成多個App * 重新使用還是用自己的? * 我的app沙箱 * 它是由哪一個包構建的? 5. 開始項目之前 6. SuperBook—給你的任務,你應該選擇接受它 * 為什么是Pyhton3? * 開始一個項目 7. 總結 * 第三章 模型 1. M比V和C更大 2. 模型選取 * 分拆model.py到多個文件 3. 結構化模型 * 模式-規范化的模型 * 具體問題 * 答案細節 * 第一個規范表單 * 第二個規范表單 * 第三個規范表單 * Django模型 * 性能和反規范 * 我們應該一直遵守規范化嗎? * 模式-模型mixin * 具體問題 * 答案細節 * 信號 * Admin * 多賬戶類型 * 模式-服務對象 * 具體問題 * 答案細節 4. 檢索模式 * 模式-屬性字段 * 具體問題 * 答案細節 * 緩存特性 * 模式-自定義模型管理 * 具體問題 * 答案細節 * 對QuerySet的操作 * 鏈接多個QuerySet 5. 遷移 6. 總結 * 第四章 視圖和URL 1. 來自頂端的視圖 * 讓視圖更高級 2. 基于類的通用視圖 3. 混合視圖 * 混合的順序 4. 裝飾器 5. 視圖模式 * 模式-訪問控制視圖 * 具體問題 * 詳細答案 * 模式-上下文增強器 * 具體問題 * 詳細答案 * 模式-服務 * 具體問題 * 詳細答案 6. 設計URL * URL剖析 * 在urls.py中發了什么? * URL模式語法 * Mnemonic – parents question pink action-figures * 名字和命名空間 * 模式順序 * URL模式風格 * 分部門存儲URL * RESTful URL 7. 總結 * 第五章 模板 * 理解Django的模板語言特點 * 變量 * 屬性 * 過濾器 * 標簽 * 設計哲學-不要發明一種編程語言 * 規劃模板 * 支持其他的模板語言 * 使用Bootstrap * 可是,他們看上去都一樣! * 模板模式 * 模式-模板繼承樹 * 具體問題 * 詳細答案 * 模式-激活的鏈接 * 具體問題 * 詳細答案 * 僅使用模板的解決方案 * 自定義標簽 * 總結 * 第六章 Admin接口 * 使用admin接口 1. 對admin使用加強的模型 * 不是每一個人都應該稱為admin 2. 自定義Admin接口 * 改變頭部 * 改變base模板和樣式表 * 給WYSIWG編輯添加一個富文本編輯器 * admin的Bootstrap主題 * 完成變革 3. 保護Admin * 模式-特性標識 * 具體問題 * 詳細答案 4. 總結 * 第七章 表單 1. 表單是如何工作的 * Django中的表單 * 為什么數據需要清潔? 2. 顯示表單 * 時間變得很脆弱 3. 理解CSRF 4. 使用基于類的表單處理 5. 表單模式 * 模式-動態表單生成 * 具體問題 * 詳細答案 * 模式-基于用戶的表單 * 具體問題 * 詳細答案 * 模式-一個視圖的多個表單行為 * 具體問題 * 詳細答案 * 對單獨的行為使用單獨的視圖 * 單獨的行為使用相同的視圖 * 模式-CRUD視圖 * 具體問題 * 詳細答案 6. 總結 * 第八章 處理早期代碼 1. 找到Django版本 * 激活虛擬環境 2. 文件放在哪里?這可不是PHP 3. 從urls.py開始 4. 跳躍的代碼 5. 理解代碼基礎 * 繪制宏偉藍圖 6. 增量改進還是完全重寫? 7. 做出任何改變之前都要寫測試 * 按步驟寫測試 8.早期數據庫 9.總結 * 第九章 測試和調試 1. 為什么要寫測試? 2. 測試驅動的開發 3. 寫一個測試的案例 * 斷言方法 * 寫出更好的測試案例 4. 建模 5. 模式-測試裝置和工廠 * 具體問題 * 詳細答案 6. 學習更多的測試知識 8. 調試 * Django的調試頁面 * 一個更好的調試頁面 9. print函數 10. 寫日志 11. Django調試工具條 12. Python的調試器pdb 13. 其他的調試器 14. 調試Django模板 15. 總結 * 第十章 安全 1. 跨站腳本(XSS) * 為什么你的cookies如何有利用價值? * Django是如何幫助你的 * 在什么地方Django也幫不上你 * 跨站請求偽造(CSRF) * Django是如何幫助你的 * 在什么地方Django也幫不上你 * SQL注入 * Django是如何幫助你的 * 在什么地方Django也幫不上你 * 點擊劫持 * Django是如何幫助你的 * Shell注入 * Django是如何幫助你的 * 攻擊方法的列表還在增長中 2. 一張便捷的安全檢查清單 3. 總結 * 第十一章 產品預發布 1. 產品環境 * 選擇一個web棧 * 一個棧的組件 2. 托管 * 平臺即服務 * 虛擬私有服務 * 其他的托管方法 3. 部署工具 * Fabric * 典型的幾種部署步驟 * 配置的管理 4. 監測 5. 性能 * 前端性能 * 后端性能 * 模板 * 數據庫 * 緩存 * 緩存會話后端 * 緩存框架 * 緩存模式 6. 總結 * 目錄-A Python2 VS Python 3 * 不過我依舊使用Python2.7! 1. Python 3 * Python 3 for Djangonauts * 改變所有的 `__unicode__` 方法到 `__str__`方法 * 所有的類都應該繼承自object類 * 調用super()更簡單 * 必須更明確地相對導入 * HttpRequest and HttpResponse have str and bytes types * 異常語法的改變和提高 * 重組標準庫 * 新東西 * 使用Pyvenv和Pip * 其他的改變 2. 更多內容
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看