Django v3.1的主要更新之一便是完善了对JSON数据存储的支持,新增models.JSONField和forms.JSONField,可在所有受支持的数据库后端上使用。
通过models.JSONField可指定此字段为存储类型为JSON格式。null=True表示此字段可以为空。
from django.db import models
class Hello(models.Model):
name = models.CharField(max_length=200)
data = models.JSONField(null=True)
def __str__(self):
return self.name
如果您想实现JSONField的自动转换,可以使用Django REST framework的JSONField,或者自定义一个字段类并覆盖from_db_value()和get_prep_value()方法来实现这个功能。
DRF的JSONField更简单,但使用上相对复杂一些。自定义字段类的方法更轻量,但需要我们自己完成一定的编码工作。
这里推荐使用自定义字段类的方法!
要使用DRF的JSONField,主要是在Serializer中导入并应用于需要自动转换JSON的字段,然后在视图进行序列化和反序列化,JSONField会自动完成与之相关的所有转换工作。
@models.register_field()是一个模型注册装饰器。使用它可以注册自定义字段,使其可以像内置字段一样在模型中使用。
例如,使用了这个装饰器的JSONField可以在模型中像此使用:
class Product(models.Model):
info = models.JSONField()
而不用导入字段类:
from .fields import JSONField
class Product(models.Model):
info = JSONField()
总结:这样的话,就不用单独导入字段类了(我们只需要在django启动入口的位置,如apps.py中 导入JSONField。目的是为了使用@models.register_field装饰器注册这个字段,使其在Django知道并可以在任何模型中像内置字段一样使用)。如果不使用这个装饰器,我们必须导入字段类后才能在模型中使用它。
另外,即使使用了@models.register_field装饰器,我们也可以直接导入JSONField字段类并在模型中使用。
举例:apps.py导入了,在具体的模型类中又单独引入了这个JSONField,用的是哪个?
在这种情况下,Django会使用您在模型中直接导入的JSONField字段类。
也就是说,apps.py中的导入会被忽略,模型中导入的字段类会生效并在模型中实际使用。
这是因为:
使用@models.register_field()带来的好处是:
另外,需要注意的一点是,您使用了@models.register_field装饰器,将JSONField注册为了一个可以像内建字段一样使用的模型字段。
但是,Django在首次运行时需要导入这个字段类,才知道JSONField代表什么字段。
所以,您需要在首次使用JSONField的模型对应的apps.py中导入这个字段类。
经过测试Django-4.2.1,里没有register_field装饰器。
因此总结起来,直接定义自定义模型字段类,使用时单独引入即可,这样也不污染环境(不用在不用入口apps.py导一次,IDE还报灰色),即用即可,感觉更清晰。
直接使用JSONField不会自动转换,是因为:
覆盖模型字段的from_db_value()和get_prep_value()方法可以实现「自动转换」的效果。
import json
from django.core import serializers
from django.core.exceptions import ValidationError
from django.db import models
class JSONField(models.TextField):
description = 'JSON Encoded Data'
def from_json(self, value):
try:
return json.loads(value)
except ValueError:
raise ValidationError('Invalid JSON')
def to_json(self, value):
return json.dumps(value, cls=serializers.json.DjangoJSONEncoder)
def from_db_value(self, value, *args):
if value == '':
return None
return self.from_json(value)
def get_db_prep_save(self, value, *args, **kwargs):
if isinstance(value, str):
return value
if value == "":
return None
return self.to_json(value)
def save(self, *args, **kwargs):
if self.value == '':
self.value = None
if self.value is None:
self.value = json.dumps(None)
return super().save(*args, **kwargs)