關於正在開發中的DjangoStarter v3版本

程序设计实验室發表於2024-06-07

前言

最近做的這個專案大量使用了 python 及其相關的生態,因此自然而然選擇了我的 DjangoStarter 作為後端框架

之前 v2 版本是用 RestFramework 做介面的,後面我試用了一次 django-ninja 之後就喜歡這種類似 FastApi 的寫介面方式

正所謂天下苦 drf 久矣,在新的 v3 版本框架中,我決定直接把整個 RestFramework 替換成 ninja

目前大部分功能都完成了,程式碼在主專案的 v3-alpha 分支裡,等開發完成我會合併到 master 裡 (現在基本可用了)

另外還做了很多新功能和改進,接下來會介紹一下

關於 DjangoStarter

這個開發腳手架,最開始還是叫 DjangoRails ,名字模仿的 Ruby on Rails

後面經過重構才改成 DjangoStarter

這個腳手架起源於2020年開始的專案,當時使用 Django + RestFramework 作為後端框架,為了滿足安全部門的要求,又做了很多魔改,再加上其他一些配置啥的,慢慢的就積累出了 DjangoStarter ~

這裡貼一下專案介紹吧~

DjangoStarter v3 是下一代 Django 專案快速開發模板,專為提升開發效率和效能而設計。

結合了 Django 的豐富功能和 Django-Ninja 的效能、靈活、簡潔特性,v3 版本旨在為開發者提供一個更加強大、簡潔和高速的開發體驗。

透過這個全新的框架版本,開發者能夠迅速搭建起符合現代 web 應用標準的專案基礎架構。

核心特性

  • Django Ninja 整合:採用 Django Ninja 替代傳統的 Django Rest Framework,為 API 開發帶來了效能最佳化和更簡潔的編碼體驗。利用 Python 型別提示,自動生成互動式 API 文件,不再需要 drf-yasg 那一堆繁瑣的手動配置文件,同時提升了程式碼的可讀性和維護性。
  • 增強的安全性:內建了多項安全功能,包括但不限於 Admin 登入驗證碼、IP 限制等,確保應用的安全性。
  • 程式碼自動生成:v3 版本進一步最佳化了程式碼生成器,丟掉了 DRF 這個包袱,只需要定義模型,就可以生成 schema 以及 RESTFul API,還能根據定義自動建立測試用例,大大提高開發效率。
  • 隨機種子資料生成:v3 版本內建 seed 模組,支援為已有模型自動填充假資料,方便開發測試。
  • 模組化專案結構:推出了更加模組化的專案結構設計,方便開發者根據需要新增或移除功能模組,使專案維護更為簡單。
  • 現代化前端整合:提供了對現代化前端技術的整合,以及利用 NPM 和 gulp 管理前端資源,幫助開發者打造富互動式的使用者介面。
  • 容器化支援:內建 Dockerfile 和 docker-compose.yml 配置,簡化了容器化部署的過程,支援一鍵部署到任何支援 Docker 的環境。
  • 詳盡的文件與社群生態:提供全面的文件和指南,覆蓋從專案啟動到部署的每一個步驟。同時,基於活躍的 Django 開源社群,開發者可以輕鬆獲取支援和反饋。

關於v3版本

OK,終於說回正題

這次重構v3版本最主要的原因是把 RestFramework 替換成 ninja

然後也做了一些新的功能

比如:

  • 新的自動程式碼生成功能
  • 完善了單元測試和整合測試,搭配程式碼生成,可以為每個應用自動生成 crud 的測試用例
  • 隨機種子資料,目前使用 faker 實現假資料,打算進一步實現類似 EFCore 的種子資料機制,使假資料更自然
  • 新的登入介面
  • 多種第三方登入接入(目前接了微信、小程式、企微)
  • 使用 tailwindcss 替換 bootstrap 實現前端(只是一些簡單的後臺展示,還是以 API 為主)
  • 拆分 settings 配置,像 AspNetCore 那樣支援多個環境配置
  • 更換了包管理器

目前大概就這些吧

後面有用到什麼新的再一步步加入

程式碼生成

一直沒有好好介紹一下 DjangoStarter 框架的具體實現

程式碼生成這塊其實也不復雜,包名是 django_starter.contrib.code_generator

主要就兩個部分

  • 分析器 - src/django_starter/contrib/code_generator/analyzer.py
  • 生成器 - src/django_starter/contrib/code_generator/generator.py

分析器使用 django.apps.apps 提供的 get_app_configget_models 來掃描已註冊的所有 App

然後搭配反射(或者在 Python 中應該叫自省 inspect)來獲取各個欄位的資訊,把蒐集到的資訊儲存到我定義的幾個物件中

然後在生成器部分,根據特定的規則,使用 jinja2 模板進行渲染~

注意要把欄位的 primary_key, is_relation 這些屬性拿出來,後面有用。如果是關係欄位(如外來鍵)的話,還需要把 target_field 拿出來。

大概思路就是這樣,其中有很多細節的地方,本文的篇幅受限,後續我寫篇文章來介紹吧。

API

使用 ninja 來寫 API

不同於之前的 RestFramework ,ninja 用的是裝飾器來定義路徑,這個對於不喜歡 Django 配置式路由的人來說很友好

專案結構我也做了一些調整

以 demo 應用為例

每個 model 都在 apis 下單獨建立一個 package,單獨有 apis.py 和 schemas.py 程式碼,這樣不會把所有程式碼邏輯混在一起

PS:後續如果我轉向使用 FastApi,也可以用這個思路來組織專案

 demo
 ├─ tests
 │  ├─ __init__.py
 │  ├─ test_music_album.py
 │  ├─ test_music.py
 │  ├─ test_movie.py
 │  └─ test_actor.py
 ├─ migrations
 │  ├─ __init__.py
 │  └─ 0001_initial.py
 ├─ apis
 │  ├─ music_album
 │  │  ├─ __init__.py
 │  │  ├─ schemas.py
 │  │  └─ apis.py
 │  ├─ music
 │  │  ├─ __init__.py
 │  │  ├─ schemas.py
 │  │  └─ apis.py
 │  ├─ movie
 │  │  ├─ __init__.py
 │  │  ├─ schemas.py
 │  │  └─ apis.py
 │  ├─ actor
 │  │  ├─ __init__.py
 │  │  ├─ schemas.py
 │  │  └─ apis.py
 │  └─ __init__.py
 ├─ __init__.py
 ├─ views.py
 ├─ models.py
 ├─ apps.py
 └─ admin.py

坑點

要說的話,ninja 這種比較新的庫,還是有一點點坑的地方的

  • 有些文件不夠詳細
  • URL reverse 功能不夠好用,只能在 NinjaAPI 物件配置 urls_namespace ,下面的各級 router 都不能配置 urls_namespace,我只能對下面的介面用 url_name='demo/movie/list' 這種形式的命名
  • ModelSchema 對外來鍵的支援有限,對於輸入的 schema ,不能在 Meta.fields 裡配置這個外來鍵欄位,需要自己單獨寫出來,這點對於自動生成程式碼來說有點麻煩,不過我已經解決了

種子資料/假資料

這個可以叫 seed data ,也可以叫 mock data

在開發測試中很有用,不用手動去新增各種資料

包名是 django_starter.contrib.seed

一開始我是找到了一個叫 django-seed 的庫,可以實現種子資料的生成

不過這個包已經年久失修,好幾年沒更新了

我試了一下,執行起來居然還依賴 PostgreSql 的庫?!

就離譜,不應該和資料庫無關的嗎……

算了,我自己寫得了,又不難

Python 生態就是好,Faker 庫用來生成隨機假資料很好用

主要程式碼在 src/django_starter/contrib/seed/seeder.py 檔案裡

就是根據不同的欄位型別,使用不同的假資料方法

坑點:外來鍵

其中外來鍵欄位會比較坑,需要做一些特殊處理

related_model = field.related_model
# Ensure there is at least one instance of the related model
related_instance = related_model.objects.order_by('?').first()
if not related_instance:
  related_instance = related_model.objects.create(**self.seed(related_model))
# Set the foreign key ID field
fake_data[field.attname] = related_instance.pk

settings 拆分

早就對 Django 的配置 settings.py 不爽了

專案一大,這個檔案就亂七八糟又臭又長

而且還不支援多環境切換,得自己寫一堆邏輯去判斷不同環境

之前版本中,我是把幾個主要的配置拆分成不同檔案,然後在 settings.py 裡引用

現在我用上了 django-split-settings 這個包,瞬間舒服了

來看看現在的 config 目錄

 config
 ├─ settings
 │  ├─ environments
 │  │  ├─ __init__.py
 │  │  ├─ testing.py
 │  │  ├─ production.py
 │  │  ├─ local.py.template
 │  │  └─ development.py
 │  ├─ components
 │  │  ├─ __init__.py
 │  │  ├─ simpleui.py
 │  │  ├─ rq.py
 │  │  ├─ ninja.py
 │  │  ├─ logging.py
 │  │  ├─ django_starter.py
 │  │  ├─ database.py
 │  │  ├─ csp.py
 │  │  ├─ cors.py
 │  │  ├─ common.py
 │  │  ├─ captcha.py
 │  │  └─ caches.py
 │  └─ __init__.py
 ├─ __init__.py
 ├─ wsgi.py
 ├─ urls_root.py
 ├─ urls.py
 ├─ env_init.py
 ├─ asgi.py
 └─ apis.py

可以看到現在 settings 變成了一個 package

各種配置拆分出來分散到 components 下面

然後不同的環境又放到 environments 下面,可以覆蓋前面定義的配置

很好的解決了之前的幾個痛點

更換包管理器

原本就直接使用 pip ,搭配 requirements.txt 來管理依賴

這個方式的優缺點我就不多說了

這次換成 pdm ,總算有點現代包管理器的感覺了

PS:其實我之前還用過 poetry ,不過偶爾會遇到一些奇奇怪怪的問題,棄了~

小結

大概就這些吧,後面有什麼新的想法我再來更新

還有其中幾個關鍵的更新我可能會單獨寫文章來詳細介紹~

相關文章