在 Django 模型中使用 Python 数据类
Using Python Dataclass in Django Models
我的目标是在 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 中不可见:
我做错了什么?有没有比数据类更好的方法?
如果您的目标只是使用语法:
specs.coordinate.x
在您的数据表中,您可以简单地使用 OneToOne
关系。
这也允许特殊情况,即必须设置所有坐标值或 none。
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。
定义数据类字段
此解决方案使用 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)
我会使用 django 的内置 PointField。它存储点对象。
所以你会使用
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
我的目标是在 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 中不可见:
我做错了什么?有没有比数据类更好的方法?
如果您的目标只是使用语法:
specs.coordinate.x
在您的数据表中,您可以简单地使用 OneToOne
关系。
这也允许特殊情况,即必须设置所有坐标值或 none。
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。
定义数据类字段
此解决方案使用 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)
我会使用 django 的内置 PointField。它存储点对象。
所以你会使用
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