用 Django 管理現有資料庫

CoXie發表於2019-02-16

在多數專案中,總有一些幾乎一成不變的 CRUD 操作,編寫這些程式碼很無聊,但又是整個系統必不可少的功能之一。我們在上一個專案中也面臨類似的問題,雖然已經實現了一個功能相對完整的管理後臺,也儘量做到了程式碼複用,但隨著專案規模的增長,需要編寫的樣本程式碼也不斷膨脹,佔用了大量開發時間。

面對這種局面,我自然想到了 Django。要知道, Django Admin 幾乎就是為這種需求量身定製的。但對於我們的專案而言,還有幾個問題要解決:

  • 我們的資料庫使用 SQL Server。Django 預設對此沒有很好的支援;
  • 資料庫結構是由另一個工具管理的,Django 並沒有直接修改資料庫結構的許可權。因*
  • 此,我們不能使用 Django migrate;

出於同樣的理由,我們無法在資料庫中建立 Django Admin 內建要求的資料表(包括 auth/session 等)。
下面我們來解決這些問題。如果你碰到類似情況的話,可以參考本文的做法。

SQL Server 支援

遺憾的是,針對 Django 開發的 SQL Server 介面卡雖然有幾種,但都比較古老了,對新版的 Django 支援存在問題。經過嘗試,我們選擇了 Django-Mssql,雖然功能是可用的,但該庫只支援到 Django 1.8,經測試,對 Django 1.11 不相容,Django 2.x 就更不行了。好在我們並不需要很新的功能,因此就用 virtualenv 鎖定版本了:

Django==1.8
django-mssql==1.8
pywin32==223

  在這裡還是要推薦下我自己建的Python開發學習群:725479218,群裡都是學Python開發的,如果你正在學習Python ,小編歡迎你加入,大家都是軟體開發黨,不定期分享乾貨(只有Python軟體開發相關的),包括我自己整理的一份2018最新的Python進階資料和高階開發教程,歡迎進階中和進想深入Python的小夥伴
django-mssql 是 Windows 版的庫,幕後使用了 ADO 為驅動,因此同時還要安裝 pywin32。

多資料庫

針對第二和第三個問題基本上有兩個思路。第一個是通過實現自定義的 Backend 來跳過 Django 內建的、基於資料庫的實現。從原理上來講是行得通的,但簡單嘗試了一下,發現要自定義的部分相當多,工作量太大。總之,這條路不是很可取。

第二個思路是利用 Django 的多資料庫支援。既然業務資料庫不可由 Django 來管理,那麼就再用一個資料庫來支援 Django 的基本功能,而 Django 對業務資料庫只作查詢和更新,不執行 migrate。當然,為了使用多個資料庫,我們需要在配置上多做一些工作。由於使用後臺的使用者基本上只有公司內部的業務人員,資料量不會大,用伺服器級的資料庫有牛刀之嫌。處於簡便考慮,這裡使用預設的 SQLite 作為內建資料庫:

DATABASES = {
    `default`: {
        `ENGINE`: `django.db.backends.sqlite3`,
        `NAME`: os.path.join(BASE_DIR, `db.sqlite3`),
    },
    `mydb`: {
        `ENGINE`: `sqlserver_ado`,
        `HOST`: `127.0.0.1`,
        `NAME`: `<DB_NAME>`,
        `USER`: `<DB_USER>`,
        `PASSWORD`: `<DB_PASSWORD>`,
        `OPTIONS`: {
            `provider`: `SQLOLEDB`,
        }
    }
}

需要說明,Django-mssql 為 provider 選項提供的預設值(按照官方文件應為 SQLCLI10)實測會導致出現“找不到提供程式” 的錯誤。由於 provider 的設定取決於 ADO 的註冊資訊,不一定在所有機器上都相同,所以你可能需要自己測試決定哪個選項可用。

現在我們配置了兩個資料來源,但還需要告訴 Django 它們和模型的對照關係。實現這一點可以在語句/實體/全域性等多種級別定義。對於我們的需求而言,對應關係是固定的,逐個模型定義並無必要,通過全域性定義是最簡單的。實現這一定義的物件在 Django 的術語中稱為資料庫路由(Database Router)。首先在 settings.py 中定義類名:

DATABASE_ROUTERS = [`project.db.MyAppRouter`]

然後完成類的實現:

class MyAppRouter:
    def db_for_read(self, model, **hints):
        if model._meta.app_label == `myapp`:
            return `myapp`
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == `myapp`:
            return `myapp`
        return None

    def allow_relation(self, obj1, obj2, **hints):
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        return False

資料路由需要按照 Django 的要求實現四個方法。其中主要是讀寫兩個方法,我們需要根據傳來的模型決定匹配到哪個資料來源。 其他兩個方法目前意義不大,按照預設的實現即可。

定義模型

配置到此完成,接下來需要建立模型。對於已經存在的資料表,可以用管理命令 inspectdb 反向生成程式碼,減少一些手工輸入的負擔。但生成的程式碼未必完全符合你的要求,所以還是應該自己檢查一下。對於 SQL Server,如果主鍵名不是預設的 id,那麼 inspectdb 似乎不會自動識別到它們,所以我們需要檢查一下主鍵欄位有無 primary_key,如果沒有的話就加上。

python manage.py inspectdb --database=myapp > myappmodels.py

為了方便除錯和辨別記錄,一般來說我們還要為模型類加上 verbose_name 並過載內建的字串方法。

class XXModel(models.Model):
    XXId = models.BigIntegerField(primary_key=True)
    ...

    class Meta:
        managed = False
        db_table = `XXModel`
        verbose_name = `模型名稱`
        verbose_name_plural = `模型名稱`

    def __str__(self):
        return self.XXField

把模型新增到 admin,對應的後臺管理資訊就完成了。

admin.site.register(XXModel, XXAdmin)

執行程式

最後,為內建資料庫生成必要的表,建立管理員賬戶,即可執行程式。以下命令就無需說明了:

$ python manage.py migrate
$ python manage.py createsuperuser
$ python manage.py runserver

總結

我們第一個版本的後臺程式是自己手工編碼完成的,用了大概兩週的時間。問題在於,每增加一個模型都要手工新增大量樣本程式碼。而改寫成 Django 只用了一天時間,包括熟悉相關資料和使用方法,增加一個模型只需花幾分鐘。這也是為什麼很多瞭解 Django 的開發者轉移到其他平臺以後,會尋找類似的專案。就我瞭解的範圍,Spring Boo 和 Django 在概念上比較類似,但 Boo 主要走的是程式碼生成的路線,複雜度更高,理論上靈活性也應該更好一些(我沒有深度研究過)。Nodejs 社群有 Keystone.js 和 Sails.js,不過前者專門針對 MongoDB,後者支援多種資料庫後端,但風聞最近有停止開發的跡象。.Net 社群以前有一個 DynamicData,現在似乎也沒了下文。發展多年的 Django 也應該算是同類產品中最成熟、生態也最為完整的產品了。

Django 潛在的問題在於不夠現代化的介面,以及深度定製較為困難。不過對於我們的後臺應用來說,這些都是可以接受的代價。

相關文章