【问题标题】:Using Python Dataclass in Django Models在 Django 模型中使用 Python 数据类
【发布时间】:2020-10-12 13:47:38
【问题描述】:

我的目标是在 Django PostgreSQL 中创建一个类似结构的对象,例如:

specs.coordinate.x
specs.coordinate.y
specs.coordinate.z

x,y,z 因此应该是坐标的子类。它们也应该是可变的,因此不能使用命名元组。

我已经尝试使用新的数据类:

from django.db import models
from dataclasses import dataclass

class Specs(models.Model):
    name = models.CharField(max_length=80)
    age = models.IntegerField()

    @dataclass
    class coordinate:
        x: float
        y: float
        z: float

然而 坐标 x,y,z 项目在 pgAdmin 中是不可见的:

我做错了什么?有没有比使用数据类更好的方法?

【问题讨论】:

  • Django 不适用于dataclasses。您可以定义一个custom model field。但这可能需要一些开发工作。
  • @WillemVanOnsem 非常感谢您的快速帮助,我会检查一下!
  • 如果您只想将 django 模型映射到数据类,请检查 this 帖子:

标签: python django postgresql django-models python-dataclasses


【解决方案1】:

定义数据类字段

此解决方案使用 dacite 库来实现对嵌套数据类的支持。

from dacite import from_dict
from django.db import models
from dataclasses import dataclass, asdict
import json

"""Field that maps dataclass to django model fields."""
class DataClassField(models.CharField):
    description = "Map python dataclasses to model."

    def __init__(self, dataClass, *args, **kwargs):
        self.dataClass = dataClass
        if not kwargs['max_length'] 
            kwargs['max_length'] = 1024
        super().__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        kwargs['dataClass'] = self.dataClass
        return name, path, args, kwargs

    def from_db_value(self, value, expression, connection):
        if value is None:
            return value
        obj = json.loads(value)
        return from_dict(data_class=self.dataClass, data=obj)

    def to_python(self, value):
        if isinstance(value, self.dataClass):
            return value
        if value is None:
            return value
        obj = json.loads(value)
        return from_dict(data_class=self.dataClass, data=obj)

    def get_prep_value(self, value):
        if value is None:
            return value
        return json.dumps(asdict(value)

示例用法

@dataclass
class MyDataclass1:
    foo: str

@dataclass
class MyDataclass2:
    bar: MyDataclass1

class MyModel(models.Model)
    dc1 = DataClassField(dataClass=MyDataclass1, null=True)
    dc2 = DataClassField(dataClass=MyDataclass2, null=True)

instance = MyModel(dc1=MyDataclass1(foo="foo"))
instance.save()
instance.dc1.foo # Should return "foo"
instance.dc1 # Should return <MyDataclass1 .... at ....>

更新

要添加对自定义数据类型(例如日期时间)的支持,您需要为这些类型指定 JSON 解码和编码,如下所示:

def decode_datetime(dt: datetime) -> str:
    return dt.strftime('%Y-%m-%dT%H:%M:%S%z')

def encode_datetime(dt: str) -> datetime:
    return datetime.strptime(dt, '%Y-%m-%dT%H:%M:%S%z')
    
def default(o):
    if isinstance(o, (datetime.datetime)):
        return decode_datetime(o)
    else:
        print(o)

class DataclassField(models.CharField):
    ...
    def from_db_value(self, value, expression, connection):
        ...
        obj = json.loads(value)
        return from_dict(data_class=self.data_class, data=obj,
                         config=Config(type_hooks={datetime.datetime: encode_datetime}))

    def to_python(self, value):
        ...
        return from_dict(data_class=self.data_class, data=obj,
                         config=Config(type_hooks={datetime.datetime: encode_datetime}))

    def get_prep_value(self, value):
        ...
        return json.dumps(asdict(value), sort_keys=True, indent=1, default=default)

【讨论】:

    【解决方案2】:

    我会使用 django 的内置 PointField。它存储 Point 对象。

    所以你会使用

    from django.contrib.gis.db import models
    
    
    class Specs(models.Model):
        coordinate = models.PointField
    
        
    

    在 django 中,您可以像使用普通 django 模型字段一样访问它:

    spec.coordinate
    

    既然是Point 对象,你也可以这样做:

    spec.coordinate.x
    spec.coordinate.y
    spec.coordinate.z
    

    【讨论】:

      【解决方案3】:

      如果您的目标只是使用语法: specs.coordinate.x 在您的数据表中,您可以简单地使用 OneToOne 关系。

      这也允许特殊情况,即必须设置所有坐标值或不设置。

      from django.db import models
      
      class Coordinate(models.Model):
          x = models.FloatField()
          y = models.FloatField()
          z = models.FloatField()
      
      
      class Specs(models.Model):
          name = models.CharField(max_length=80)
          age = models.IntegerField()
          coordinate = models.OneToOneField(
              Coordinate, 
              null=True, 
              on_delete=models.SET_NULL
          )
      
      

      现在您可以像使用 Dataclass 一样使用 Specs。

      【讨论】:

      • 好点,是复制粘贴的,忘记删除了
      猜你喜欢
      • 2014-06-04
      • 2016-04-04
      • 1970-01-01
      • 1970-01-01
      • 2020-02-22
      • 2023-04-05
      • 2021-06-18
      • 2021-08-07
      • 1970-01-01
      相关资源
      最近更新 更多