首頁 > 後端開發 > Python教學 > 如何使用 JSONField 和 Pydantic 在 Django 中建立靈活的資料模型

如何使用 JSONField 和 Pydantic 在 Django 中建立靈活的資料模型

Mary-Kate Olsen
發布: 2024-12-31 18:13:09
原創
405 人瀏覽過

How to Build Flexible Data Models in Django with JSONField and Pydantic

在本文中,我將引導您了解如何使用Django 的JSONField(JSON 和JSONB 包裝器)對半結構化資料進行建模,以及如何對其強制實施架構使用Pydantic 取得資料-對於Python Web 開發人員來說,這種方法應該很自然。

靈活的類型定義

讓我們考慮一個處理付款的系統,例如交易表。它看起來像這樣:

from django.db import models

class Transaction(models.Model):
    # Other relevant fields...
    payment_method = models.JSONField(default=dict, null=True, blank=True)
登入後複製
登入後複製

我們的重點是 payment_method 欄位。在現實世界中,我們將擁有處理付款的現有方法:

  • 信用卡

  • PayPal

  • 現在購買,稍後付款

  • 加密貨幣

我們的系統必須能夠適應儲存每種付款方式所需的特定數據,同時保持一致且可驗證的結構。

我們將使用 Pydantic 為不同的付款方式定義精確的模式:

from typing import Optional
from pydantic import BaseModel

class CreditCardSchema(BaseModel):
    last_four: str
    expiry_month: int
    expiry_year: int
    cvv: str


class PayPalSchema(BaseModel):
    email: EmailStr
    account_id: str


class CryptoSchema(BaseModel):
    wallet_address: str
    network: Optional[str] = None


class BillingAddressSchema(BaseModel):
    street: str
    city: str
    country: str
    postal_code: str
    state: Optional[str] = None


class PaymentMethodSchema(BaseModel):
    credit_card: Optional[CreditCardSchema] = None
    paypal: Optional[PayPalSchema] = None
    crypto: Optional[CryptoSchema] = None
    billing_address: Optional[BillingAddressSchema] = None
登入後複製
登入後複製

這個方法有幾個顯著的好處:

  1. 一次只能有一種付款方式具有非空值。

  2. 無需複雜的資料庫遷移即可輕鬆擴展或修改。

  3. 確保模型層級的資料完整性。

為了在 payment_method 欄位上強制執行架構,我們利用 Pydantic 模型來確保傳遞到該欄位的任何資料都與我們定義的架構一致。

from typing import Optional, Mapping, Type, NoReturn
from pydantic import ValidationError as PydanticValidationError
from django.core.exceptions import ValidationError

def payment_method_validator(value: Optional[dict]) -> Optional[Type[BaseModel] | NoReturn]:
    if value is None:
        return

    if not isinstance(value, Mapping):
        raise TypeError("Payment method must be a dictionary")

    try:
        PaymentMethodSchema(**value)
    except (TypeError, PydanticValidationError) as e:
        raise ValidationError(f"Invalid payment method: {str(e)}")
登入後複製
登入後複製

在這裡,我們執行一些檢查,以確保輸入驗證器的資料類型正確,以便 Pydantic 可以驗證它。我們對可為 null 的值不執行任何操作,如果傳入的值不是 Mapping 類型的子類別(例如 Dict 或 OrderedDict),則會引發類型錯誤。

當我們使用傳遞給建構函數的值來建立 Pydantic 模型的實例時。如果值的結構不符合 PaymentMethodSchema 定義的架構,Pydantic 將引發驗證錯誤。例如,如果我們在 PayPalSchema 中為電子郵件欄位傳遞無效的電子郵件值,Pydantic 將引發如下驗證錯誤:

ValidationError: 1 validation error for PaymentMethodSchema
paypal.email
  value is not a valid email address: An email address must have an @-sign. [type=value_error, input_value='Check me out on LinkedIn: https://linkedin.com/in/daniel-c-olah', input_type=str]
登入後複製

我們可以透過兩種方式強制執行此驗證:

  1. 自訂驗證方法

    在儲存過程中,我們呼叫驗證函數以確保付款方式與預期模式相符。

    from django.db import models
    
    class Transaction(models.Model):
        # ... other fields ...
        payment_method = models.JSONField(null=True, blank=True)
        def save(self, *args, **kwargs):
            # Override save method to include custom validation
            payment_method_validator(self.payment_method)
            super().save(*args, **kwargs)
    
    登入後複製

    雖然有效,但這種方法在 Django 中可能會變得麻煩且不太慣用。我們甚至可以用具有相同功能的類別方法來替換該函數,以使程式碼更簡潔。

  2. 使用欄位驗證器

    此方法利用了 Django 內建的欄位驗證機制:

    from django.db import models
    
    class Transaction(models.Model):
        # Other relevant fields...
        payment_method = models.JSONField(default=dict, null=True, blank=True)
    
    登入後複製
    登入後複製

    這種方法平衡了靈活性和對 payment_method 欄位中儲存的值的控制。它使我們能夠適應未來需求的變化,而不會損害該領域現有資料的完整性。例如,我們可以在 Paystack 架構中包含 Paystack ID 欄位。此更改將是無縫的,因為我們不必處理複雜的資料庫遷移。

我們甚至可以在未來添加 pay_later 方法,沒有任何麻煩。欄位的類型也可能會發生變化,我們不會面臨資料庫欄位遷移限制,就像從整數主鍵遷移到 UUID 主鍵時遇到的限制一樣。您可以在此處查看完整的程式碼以完全理解這個概念。

非規範化

反規範化涉及跨多個文件或集合故意複製數據,以優化效能和可擴展性。這種方法與傳統關聯式資料庫中使用的嚴格規範化形成鮮明對比,NoSQL 資料庫透過引入靈活的、以文件為導向的儲存範例,在普及非規範化方面發揮了重要作用。

考慮一個電子商務場景,其中產品和訂單有單獨的表。當客戶下訂單時,必須捕獲購物車中產品詳細資訊的快照。我們不會引用目前的產品記錄(該記錄可能會隨著時間的推移因更新或刪除而發生變化),而是直接將產品資訊儲存在訂單中。這可確保訂單保留其原始上下文和完整性,反映購買時產品的確切狀態。非規範化在實現這種一致性方面發揮著至關重要的作用。

一種可能的方法可能涉及複製訂單表中的某些產品欄位。然而,這種方法可能會帶來可擴展性挑戰並損害訂單模式的內聚性。更有效的解決方案是將相關產品欄位序列化為 JSON 結構,讓訂單能夠維護產品的獨立記錄,而不需要依賴外部查詢。下面的程式碼說明了這個技巧:

from typing import Optional
from pydantic import BaseModel

class CreditCardSchema(BaseModel):
    last_four: str
    expiry_month: int
    expiry_year: int
    cvv: str


class PayPalSchema(BaseModel):
    email: EmailStr
    account_id: str


class CryptoSchema(BaseModel):
    wallet_address: str
    network: Optional[str] = None


class BillingAddressSchema(BaseModel):
    street: str
    city: str
    country: str
    postal_code: str
    state: Optional[str] = None


class PaymentMethodSchema(BaseModel):
    credit_card: Optional[CreditCardSchema] = None
    paypal: Optional[PayPalSchema] = None
    crypto: Optional[CryptoSchema] = None
    billing_address: Optional[BillingAddressSchema] = None
登入後複製
登入後複製

由於我們已經介紹了上一節中的大部分概念,您應該開始欣賞 Pydantic 在這一切中的作用。在上面的範例中,我們使用 Pydantic 來驗證連結到訂單的產品清單。透過定義產品結構的模式,Pydantic 確保添加到訂單中的每個產品都符合預期要求。如果提供的資料不符合架構,Pydantic 會引發驗證錯誤。

在 Django 查詢 JSONField

我們可以像在 Django 欄位中執行查找一樣查詢 JSONField 鍵。以下是基於我們的用例的一些範例。

from typing import Optional, Mapping, Type, NoReturn
from pydantic import ValidationError as PydanticValidationError
from django.core.exceptions import ValidationError

def payment_method_validator(value: Optional[dict]) -> Optional[Type[BaseModel] | NoReturn]:
    if value is None:
        return

    if not isinstance(value, Mapping):
        raise TypeError("Payment method must be a dictionary")

    try:
        PaymentMethodSchema(**value)
    except (TypeError, PydanticValidationError) as e:
        raise ValidationError(f"Invalid payment method: {str(e)}")
登入後複製
登入後複製

您可以查看文件以了解有關過濾 JSON 欄位的更多資訊。

結論

在 PostgreSQL 中使用 JSON 和 JSONB 為處理關聯式資料庫中的半結構化資料提供了極大的靈活性。 Pydantic 和 Django 的 JSONField 等工具有助於強制執行資料結構規則,從而更容易保持準確性並適應變化。然而,這種靈活性需要謹慎使用。如果沒有適當的規劃,隨著數據隨著時間的推移而變化,可能會導致效能下降或不必要的複雜性。

在 Django 中,只有在明確呼叫 full_clean() 時才會觸發欄位驗證器 - 這通常發生在使用 Django Forms 或在 DRF 序列化器上呼叫 is_valid() 時。更多詳細信息,您可以參考 Django 驗證器文件。

解決此問題的更高級方法是實作一個自訂 Django 字段,該字段整合 Pydantic 以在內部處理 JSON 資料的序列化和驗證。雖然這需要一篇專門的文章,但目前,您可以探索為該問題提供現成解決方案的庫,例如:django-pydantic-jsonfield

以上是如何使用 JSONField 和 Pydantic 在 Django 中建立靈活的資料模型的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板