北郵在線分享:Django 全棧項目經驗

北郵在線分享:Django 全棧項目經驗

北郵在線分享:Django 全棧項目經驗

比較完整的 Django 項目經驗分享,值得學習。

來源:https://kinegratii.github.io/2017/07/31/django-full-stack-note/

概述

BWS項目是自己一年來在開發的項目。本文就項目中一些技術選型、功能實現、項目流程做一些簡單的總結。

python3

雖然Python3發佈已經10年之久,但是2和3之爭直到今天依舊存在,對於如何學習,每個人都有自己的理解和學習策略。我個人看法:

  • 果斷學習面向未來的3;
  • 3和2的差別對於學習過程沒有太多的影響,學好了3自然也能夠很快上手2了;
  • 常見的第三方庫大多數(80%以上)是支持Python3的;
  • 不過在系統安裝的時候總是23共存的,可以自由切換;

今年4月份重新開發的時候做了一個比較激進的做法:完全拋棄對Python2的支持。當時的考量,把這個項目作為Django持續學習的一個示範項目,畢竟 Django的下一個大版本2.0(預計2017年12月發佈),也已經要求最低版本為3.5了。

其實在項目中使用幾個Python2不支持的語法和沒有的標準庫,就可以達到以上目的,具體來說,在代碼中使用了以下幾個語法:

  • 字典合併創建語法(PEP448)
  • 強制關鍵字傳參(PEP3102)

根據 PEP448,在Python3.5中可以使用更加簡潔明瞭的代碼實現合併多個字典。

  1. # 3.5+
  2. combination = {**first_dictionary, "x": 1, "y": 2}
  3. # 3.5以下
  4. combination = first_dictionary.copy()
  5. combination.update({"x": 1, "y": 2})

另外Python3在文本和二進制方面作了比較大的改變,這對文件導入導出功能開發提供了便利,不用再糾結2的編碼問題,可以集中解決業務層面的問題。

Django

我也算是Django的忠實用戶了,從1.4到1.11都有用到,不斷看到Django的成長和壯大。1.4/1.8/1.11是LTS版本,項目使用的是1.10。這些年來Django比較大的變更有:

  • 自定義用戶類型:這個是1.5就有的功能了,之前只能使用內置的用戶類,連使用郵箱作為用戶名也不能直接支持;
  • 數據庫遷移:1.7借鑑 South 實現的,這個是開發的利器,修改用戶模型時候可以使用命令一鍵將修改同步到數據庫,而忽略具體的數據庫類型;
  • 自定義過濾查詢:1.7,這個主要用於封裝一些業務數據庫查詢。
  • 多模板支持:1.8引入的,Django自己的模板引擎效率歷來為人們所詬病,現在可以在Django中使用Jinja2這樣的模板了。
  • 表單控件支持模板渲染:Django表單其實是著重於後端驗證,前端相對薄弱,導致定製起來沒有那麼順手。最新的1.11引入的可以通過模板文件定製控件樣式等等。

CBV

強烈建議使用 Class-Based-View 組織視圖處理函數。

Class-Based-View 是相對於Function-Based-View而言,主要支持封裝,減少重複的代碼編寫工作,邏輯流程清晰,經過測試過的。在具體編寫代碼還是一定要查看源代碼,才能理解其中的功能實現。

CBV的核心是Mixin模式。

Mixin是一種將多個類中的功能單元的進行組合的利用的方式,這聽起來就像是有類的繼承機制就可以實現,然而這與傳統的類繼承有所不同。通常mixin並不作為任何類的基類,也不關心與什麼類一起使用,而是在運行時動態的同其他零散的類一起組合使用。

使用mixin機制有如下好處:可以在不修改任何源代碼的情況下,對已有類進行擴展;可以保證組件的劃分;可以根據需要,使用已有的功能進行組合,來實現"新"類;很好的避免了類繼承的侷限性,因為新的業務需要可能就需要創建新的子類。

現在也基本上不寫視圖函數了,項目上能見到的也就是 django.contrib.auth.login 等幾個函數了,不過現在也要改成視圖類形式了。

是否啟用admin

雖然admin是Django的主要優勢所在,但是它的使用場景有限,主要由於整合許多功能,比如分頁、過濾、搜索、增刪改查和批量操作等等,相互之間具有非常高的耦合度。在沒有提供公開的API下去實現一些定製往往是"牽一髮而動全身",最後基本上也改的是不成樣子。

由於項目中沒有使用內置的admin組件,增刪改查的頁面就需要多花一點時間自己去適配。

日誌模型也要自己去設計,項目中我自己添加了ip這個字段,這個是原來所沒有的。

後端數據API - DRF

後端數據採用的是 Django Rest Framework 這個框架,覆蓋了大多數需求,包括:

  • 搜索/分頁
  • 訪問權限
  • 請求限制(頻率、IP)
  • 表單驗證

前端 Amaze UI

前端UI用的是 Amaze UI這個框架。不過從後來的發展形勢來看,這是最為錯誤的決定了,主要原因在於無法和後端比較平穩地整合。

Django表單中有一個比較大的問題,如何需要定製控件樣式,需要在Python代碼中修改,而且需要應用的每一處都需要更改,靈活度不夠。目前主要有兩種解決方式:

  • 使用Django1.11版本的模板功能,這個功能剛剛推出,文檔也只有一頁的內容,不太建議使用。
  • 使用 django-crispy-forms 第三方庫,這個庫的思路也是使用模板html文件渲染控件,已經有一定的使用規模,但是支持 Bootstrap這樣常見的UI框架,不支持 Amaze UI。

導入導出

實現導入導出功能主要使用的是 tablib 和 django-import-export 這兩個庫,其中後者依賴前者。

導出

編寫 Resource, 幾點值得注意的地方:

  • 需要設置表頭,不僅需要指定字段 Meta.fields,同時也要顯示指定 Meta.export_order的值,通常和 Meta.fields一樣即可。
  • Meta.fields 裡的元素必須是模型的數據庫字段,不能是自定義的 property,這一點和 ModelAdmin.list_display 不一樣。
  • 可以使用 dehydrate_FOO 函數重寫導出內容
  • class BillResource(resources.ModelResource):
  1. def get_export_headers(self):
  2. return ['流水號','月份', '類型','單價','用量', '金額']
  3. def dehydrate_price(self, obj):
  4. return obj.get_price_display
  5. class Meta:
  6. model = models.Bill
  7. fields = ('id', 'month', 'resource_type','price', 'amout', 'total')
  8. export_order = fields

導入

django-import-export也提供了幾個Mixin,但問題這些和admin組件耦合很高,不利於一些自定義操作,所以直接使用tablib庫比較好。根據官方文檔,可以使用以下代碼實現文件導入

  1. imported_data = Dataset().load(open('data.csv').read())

但其實load還有幾個比較重要的參數:

  • format:文件格式,如果不寫,使用自動識別,但有出錯的機率,之前試驗過一個xlsx文件在不同環境下識別為json文件的,因此建議這個參數也留給用戶輸入
  • headers:表示第一行是否是表頭,這個參數在文檔中沒有表明,需要自行查看源代碼獲取相關信息。

所以最後就寫成下面這個樣子

  1. class BillUploadForm(form.Form):
  2. import_file = forms.FileField()
  3. format = forms.ChoiceField(choices=(('xlsx', 'xlsx'), ('xls', 'xls')))
  4. # 導入
  5. tablib.Dataset().load(form.cleaned_data['import_file'].read(), format=form.cleaned_data['format'], headers=False)

數據庫查詢優化

select_related函數

selectrelated是 django.db.models.QuerySet 類的一個方法,它解決了 ORM中常見的N+1查詢效率問題,關於這一部分可以參考我之前寫過的一篇文章《selectrelated函數性能基本測試》。

更新記錄

在更新記錄時可以使用 update_fields 參數指定只需更新的字段列表。這個參數在只更新一兩個字段的時候特別有用。

  1. product.name = 'Name changed again'
  2. product.save(update_fields=['name'])

如果不指定參數的值,將更新所有字段。

測試和部署

分離配置文件

Django使用 settings 模塊配置相關參數,這使得其很好的區分開發/測試/生產。

  1. - BillWorkingSystem
  2. - BillWorkingSystem
  3. - __init__.py
  4. - settings.py
  5. - test_settings.py
  6. - urls.py
  7. - wsgi.py

一個簡單的testsettings.py如下,可以配置一些僅用於測試的項目,如數據目錄 FIXTUREDIRS 。

  1. from BillWorkingSystem.settings import *
  2. class DisableMigrations(object):
  3. def __contains__(self, item):
  4. return True
  5. def __getitem__(self, item):
  6. return "notmigrations"
  7. MIGRATION_MODULES = DisableMigrations()
  8. FIXTURE_DIRS = (
  9. os.path.join(BASE_DIR, 'fixtures').replace('', '/'),
  10. )
  11. UPLOAD_TEST_DATA_DIR = os.path.join(BASE_DIR, 'fixtures', 'test_data').replace('', '/')

MIGRATION_MODULES 設置表示是否運行數據遷移腳本。上述例子設置為空,表示測試無需運行這些遷移腳本。

單元測試

單元測試主要測試那些返回為實際數據(如json/yaml)的視圖。

測試採用標準的 django.test.TestCase,按照文檔所描述的步驟,一步一步的編寫。

  1. class BillCreateTestCase(TestCaseBase):
  2. url = '/api/bill/create/'
  3. def test_success(self):
  4. data = {
  5. 'enterprise': 1,
  6. 'year': 2017,
  7. 'month': 1,
  8. 'amount': 4000,
  9. 'create_name': 'Test'
  10. }
  11. rsp = self.client.post(self.url, data)
  12. self.assertEqual(201, rsp.status_code)
  13. # 其他assert語句
  14. def test_with_error_enterprise(self):
  15. data = { } # 參數
  16. rsp = self.client.post(self.url, data)
  17. self.assertEqual(400, rsp.status_code)
  18. # 其他assert語句

一個基本模式,

  • 一個TestCase表示一個主要功能,如一個POST請求
  • 每個 test_FOO 函數表示一種情況,包括正確和無效參數
  • 依次對響應對象的狀態碼和內容、數據變更進行斷言(Assert)測試

docker部署

之前採用的是daocloud這個平臺的工具。具體可參考《使用DaoCloud部署Django項目》這篇文章。由於對docker這方面沒有一個完整的學習,加上daocloud.io更新到3版本,作了一些比較大的改變,後來就決定搬遷到阿里雲服務器上,這樣相對比較容易把握。

後記

輪子

什麼是輪子 wheel,寫多了代碼就會發現一些代碼具有共同之處,將其抽象並提取,慢慢地就形成了一個庫,可以和別人分享。本質上來說,Django也是一個輪子。

持續開發

由於是個人項目,因此一些版本升級方面就比較隨意,基本上新版本出來就完全廢棄舊有版本。

第三方庫

穩定才是真,不要為追求標新立異而盲目升級。


北郵在線9月免費訓練營報名中,可免費試聽課程,學不學你說了算!


分享到:


相關文章: