首页 > 后端开发 > 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
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板