iT邦幫忙

2024 iThome 鐵人賽

DAY 5
0
Software Development

Django 2024: 從入門到SaaS實戰系列 第 6

Django in 2024: 徹底玩轉Model,多資料庫開發與動態添加表格功能

  • 分享至 

  • xImage
  •  

身為一個需要跟資料庫打交道的框架,怎麼可以在Model這邊草草結束呢!今天繼續來深入Model

我們今天會提到以下重點:

  • 多資料庫的開發
  • 動態建立模型

多資料庫的開發

多資料庫可能會有幾種狀況:

  1. 所有資料庫都是使用同名的table,相當於將資料進行分表
  2. 不同資料庫放置不同的table,而同一個app下的table可能在不同資料庫
  3. 不同資料庫放置不同的table,不同app下的table在不同資料庫

以Django的角度來說,在技術上都是可行的

通常資料量非常大需要分表或是多租戶可能會用到第一點,需要維持查表效率或是隔離數據。第二種可以使app的操作更有彈性,但是配置上需要更加小心。使用第三種的話較為單純,這邊也示範第三種方法

  1. 我們再來建立一個app
python3 manage.py startapp chat

# settings.py
INSTALLED_APPS = [
    ...
    # my apps
    "article",
    "chat",
]
  1. 然後在該app下建立models
class Chat(models.Model):
    chat_id = models.AutoField(primary_key=True)
    message = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    user = models.IntegerField()

    class Meta:
        app_label = "chat"
  1. 定義出你所有需要的資料庫,如果不想用default的話,可以設置空字典,但是不可以不寫
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": DB_NAME,
        "USER": DB_USER,
        "PASSWORD": DB_PWD,
        "HOST": DB_HOST,
        "PORT": "5432",
    },
    "second_db": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": SECOND_DB_NAME,
        "USER": SECOND_DB_USER,
        "PASSWORD": SECOND_DB_PWD,
        "HOST": SECOND_DB_HOST,
        "PORT": "5432",
    },
}

接下來還不能馬上進行遷移,因為makemigrations只會確認default下的DB,因此我們需要配置DB的路由

  1. 在項目根目錄下建立dbRouter.py
from django.conf import settings

class DatabaseRouter:
    def db_for_read(self, model, **hints):
        if model._meta.app_label == "article":
            return "default"
        elif model._meta.app_label == "chat":
            return "second_db"
        else:
            return "default"

    def db_for_write(self, model, **hints):
        if model._meta.app_label == "article":
            return "default"
        elif model._meta.app_label == "chat":
            return "second_db"
        else:
            return "default"

    def allow_relation(self, obj1, obj2, **hints):
        """
        決定是否允許兩個對象之間建立關係。
        對於跨數據庫的關係,我們需要更精確的控制。
        """
        # 如果兩個對象在同一個數據庫,允許關係
        if obj1._state.db == obj2._state.db:
            return True

        # 其他跨數據庫的關係默認不允許
        return False

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """
        表示是否允許模型在指定資料庫上進行遷移操作
        """
        if app_label == "article":
            return db == "default"
        elif app_label == "chat":
            return db == "second_db"
        elif app_label == "auth":
            return db == "default"
        return None
  1. settings.py中配置
DATABASE_ROUTERS = ["django_project.dbRouter.DatabaseRouter"]
  1. 進行遷移,就能在新建立資料庫中看到chat_chat表格
python manage.py makemigrations chat
python manage.py migrate chat --database=second_db
  1. 因為有設定路由,所以在ORM語法上就可以不需要做調整
# shell 模式
>>> from chat.models import Chat
>>> chat = Chat(message="", user=1)
>>> chat.save()

# 如果都沒有設定路由,也可以使用如下方式
>>> Author.objects.using("default")
>>> Chat.objects.using("second_db")

動態建立模型

一般狀況下,增加model並且遷移到實際的DB中需要我們寫程式碼並且執行遷移指令。但是有時候我們可能會想要動態的添加表格,可能操作人員需要後台有這樣的功能,又或是有一些排程下的資料需要不斷建立表格來輸入資料,此時能動態添加表格就是很重要的功能了。當然在執行操作之前,一定要確保相關的安全措施與用戶的權限設置等等

  1. 建立出自定義的流程,會需要定義模型對象,遷移到資料庫與實際建立表格到資料庫

這邊加上type hint是更方便理解整個流程的運作

from django.db import models
from typing import Dict, Any, Optional, Type, Callable

def create_model(
    name: str,
    fields: Optional[Dict[str, Any]] = None,
    app_label: str = "",
    options: Optional[Dict[str, Any]] = None,
) -> Type[models.Model]:
    """
    創建模型對象
    :param name: 模型名稱
    :param fields: 模型欄位
    :param app_label: 應用名稱
    :param options: 模型選項
    :return: 模型對象
    """

    class Meta:
        pass

    setattr(Meta, "app_label", app_label)

    # 設置模型的meta選項
    if options is not None:
        for key, value in options.items():
            setattr(Meta, key, value)

    # 設置模型的欄位
    attrs: Dict[str, Any] = {"__module__": f"{app_label}.models", "Meta": Meta}
    if fields:
        attrs.update(fields)

    # 創建模型
    model: Type[models.Model] = type(name, (models.Model,), attrs)
    return model

def create_db(model: Type[models.Model]) -> None:
    """
    創建資料表
    :param model: 模型對象
    """
    from django.db import connection
    from django.db.backends.base.schema import BaseDatabaseSchemaEditor

    try:
        with BaseDatabaseSchemaEditor(connection) as schema_editor:
            schema_editor.create_model(model=model)
    except Exception as e:
        pass

def create_table(model_name: str) -> Type[models.Model]:
    """
    創建資料表
    :param model_name: 模型名稱
    :return: 模型對象
    """
    fields: Dict[str, Any] = {
        "id": models.AutoField(primary_key=True),
        "views": models.IntegerField(default=0),
        "date": models.DateField(auto_now_add=True),
        "article": models.ForeignKey("Article", on_delete=models.CASCADE),
        "__str__": lambda self: f"{self.article.title}_{self.date}_views",
    }

    options: Dict[str, str] = {
        "verbose_name": model_name,
        "verbose_name_plural": model_name,
    }

    model: Type[models.Model] = create_model(
        name=model_name, fields=fields, app_label="article", options=options
    )
    create_db(model)
    return model

create_model中,我們根據傳入的參數來建造模型對象,並且使 python內置的type()來建立

type(name, bases, dict)

這個函數有三個參數:

  1. name: 這是要創建的類的名稱
  2. bases: 這是一個元組,包含新類要繼承的基類。(models.Model,),表示新創建的類將繼承 models.Model
  3. dict: 這是一個字典,包含類的屬性和方法。 attrs 字典中包含了 __module__Meta 以及所有的模型欄位

create_db則是讓我們能夠在資料庫中建立我們設置好的模型類,其中BaseDatabaseSchemaEditor是Django 用於執行數據庫架構更改的基礎類別

最後create_table則是將整個流程定下來,調用create_model後把model當作參數丟入create_db中,最後返回model對象提供後續的操作

實際操作一次

def create_table_view(request):
    today = time.localtime(time.time())
    article = Article.objects.get(article_id=2)
    model_name = f"{article.article_id}_{time.strftime('%Y%m%d', today)}_view"

    new_model = create_table(model_name=model_name)
    new_model.objects.create(
        views=0,
        date=today,
        article=article,
    )
    return JsonResponse({"status": "success"})

可以看到確實新增了表格以及載入了數據

https://ithelp.ithome.com.tw/upload/images/20240917/201618662n3YOToueL.png

https://ithelp.ithome.com.tw/upload/images/20240917/20161866w9phwqY6H0.png

今日總結

我們介紹了兩樣比較進階的資料庫操作方式

  • 多資料庫下的開發模式
  • 如何動態添加表格

這兩者都能讓我們在開發中,面臨到動態的需求變更等都能更靈活的進行調整,滿足各式各樣的需求

目前為止,有稍微感受到Django對於資料庫使用ORM操作的魅力了嗎?除了程式碼簡潔且可讀性高之外,透過自定義的路由設置,讓我們在多變的系統下不用去修改原先的ORM語法,可以說是相當便利~

到此有關Model的部分先告一個段落,下一次再來討論資料庫的結構就會是之後的多租戶架構了

到目前為止,我們添加數據都是透過shell模式下進行操作,明天開始我們會透過表單來進行數據的添加。有了Django表單,除了在數據驗證的部分更加方便,在視圖中的程式碼也會更加的簡潔與易讀,可以期待一下

參考資料

多資料庫使用:https://docs.djangoproject.com/en/5.1/topics/db/multi-db/
SchemaEditor:https://docs.djangoproject.com/en/5.1/ref/schema-editor/


上一篇
Django in 2024: 淺嚐Model, Url與Template
下一篇
Django in 2024: 解鎖Django form的潛力
系列文
Django 2024: 從入門到SaaS實戰16
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言