本文章的代码已上传至github上(github包含了更多功能,相关文章后续更新)
AGL1994/django-buildinggithub.com![b08c9eb265bc7d6bf0bc360d18bbe9b3.png](https://img-blog.csdnimg.cn/img_convert/b08c9eb265bc7d6bf0bc360d18bbe9b3.png)
前言
目前Django比较知名的序列化框架有DRF,还有Django自带的model_to_dict函数。DRF序列化需要
为model写一个serializers类,同时需要配置相关的序列化字段,如果有外键关联等,也需要作出相
关的配置。如果一个项目从0开始的话,还是建议使用DRF。对于Django自带的model_to_dict函数,
还是有一些缺点,不能序列化Foreign的值,或者某些时间格式会报错等。下面我们就进入正题。本例
子会使用反射相关的功能,也需要对Django框架有一个初步的了解。不清楚的童鞋可以先自行了解一下,
网上相关的帖子很多,这里就不多做讲解。
model
class User(AbstractUser):
phone = models.CharField(max_length=11, blank=True, null=True, verbose_name="电话号码")
name = models.CharField(max_length=45, blank=True, null=True, verbose_name="姓名")
sex = models.IntegerField(blank=True, null=True, choices=((1, "男"), (2, '女')), default=1)
birthday = models.DateField(blank=True, null=True, verbose_name="出生日期")
avatar = models.CharField(max_length=255, default=None, blank=True, null=True, verbose_name="用户头像")
status = models.SmallIntegerField(default=1, choices=USER_STATUS, verbose_name="状态")
created = models.DateTimeField(verbose_name=u"创建时间", editable=False, auto_now_add=True)
updated = models.DateTimeField(verbose_name=u"修改时间", editable=False, auto_now=True)
deleted = models.SmallIntegerField(default=0)
class Meta:
db_table = 'tb_user'
verbose_name = "用户信息"
verbose_name_plural = verbose_name
class UserWx(models.Model):
user = models.ForeignKey(User, on_delete=models.DO_NOTHING, null=True, blank=True)
openid = models.CharField(max_length=50, verbose_name='openid')
nickname = models.CharField(max_length=255, verbose_name='用户昵称')
sex = models.IntegerField(choices=USER_SEX, verbose_name='性别')
province = models.CharField(max_length=255, verbose_name='省份')
city = models.CharField(max_length=255, verbose_name='城市')
country = models.CharField(max_length=255, verbose_name='国家')
head_img_url = models.CharField(max_length=500, verbose_name='用户头像url')
privilege = models.CharField(max_length=1000, verbose_name='用户特权信息')
created = models.DateTimeField(verbose_name=u"创建时间", editable=False, auto_now_add=True)
updated = models.DateTimeField(verbose_name=u"修改时间", editable=False, auto_now=True)
deleted = models.SmallIntegerField(default=0)
class Meta:
verbose_name = '用户微信信息'
db_table = 'tb_user_wx'
思路及步骤
1. 查出数据,得到需要序列化的数据,本例直接使用QueryDict对象进行序列化。
2. 得到被序列化model的每一个字段。
3. 根据每一个字段不同的类型作出不同的处理。
4. 返回结果字典。
5. 使用json模块,将字典转成json。
代码
# 获取查询结果集
user_wx = UserWx.objects.select_related('user').all() # 获取查询的结果集
result_dict = model_serializer(user_wx)
result = json.dumps(result_dict) # 将结果集转成json即可
# 调用序列化方法,注:以下可选参数为拓展功能,可先忽略,对功能实现没有多大影响
def model_serializer(cls, choices=True, contains=(), excepts=('updated', 'deleted')):
"""
model转json
:param choices: 是否自动转换choices
:param cls: 需要转的model
:param contains: 包含的字段 (若有此参数,则只返回次参数包含字段)
:param excepts: 排除字段,默认排除更新时间与deleted (若有此参数,则排除此参数内的字段)
:return:
实现models转dict
支持了时间格式转化, foreignKey, choice类型转义
暂不支持 ManyToMany ManyToOne 等
"""
try:
# 查询是否有外键的数据,为保证性能,只会序列化使用了select_related()的外键数据。
foreign_dict = cls.query.select_related
except Exception:
foreign_dict = {}
return _model_serializer(cls, choices, contains, excepts, foreign_dict)
def _model_serializer(cls, choices, contains, excepts, foreign_dict={}):
# 此方法主要用于判断传入的序列化对象是QueryDict或者是一个model。如果是QueryDict则返回list(dict),如果是model,则返回dict
model_dict = []
single = False
if not isinstance(cls, Iterable):
cls = (cls,)
single = True
for instance in cls: # 如果是QueryDict,则遍历每一条数据
model_dict.append(_model_to_dict(instance, choices, contains, excepts, foreign_dict))
if single:
model_dict = model_dict[0]
return model_dict
def _model_to_dict(instance, choices, contains, excepts, foreign_dict):
"""
model 转 dict
:param foreign_dict:
:param contains:
:param excepts:
:param instance:
:return: dict
"""
field_dict = {}
foreign_list = foreign_dict.keys() if isinstance(foreign_dict, dict) else () # 待序列化的外键列表
fields = instance._meta.fields # 获取当前期model的所有字段
for field in fields:
# 获取字段名。Django有两个字段属性,name和attname。name是model里面写的属性名,attname则是 name_id(针对Foreign等)
name = field.name
# 排除不需要的参数(为拓展功能内容)
if contains and (name not in contains):
continue
if name in excepts:
continue
try:
# 首先判断是否为ForeignKey类型,ForeignKey需要用到name与attname,所以先单独处理
if isinstance(field, models.ForeignKey):
if name in foreign_list: # 如果ForeignKey字段包含在select_related(),在需要对ForeignKey的model做序列化,否则直接返回ForeignKey的值
value = getattr(instance, name)
foreign_list_child = foreign_dict.get(name)
# 如果有数据,则递归
value = _model_serializer(value, choices=choices, contains=contains,
excepts=excepts, foreign_dict=foreign_list_child)
else:
value = getattr(instance, field.attname)
value = value if value else ''
field_dict[name] = value
continue
else:
value = getattr(instance, name)
value = value if value else ''
except Exception as e:
field_dict[name] = ''
continue
# 上面处理了model.ForeignKey类型,下面处理其他类型。(类型不完全,可根据自己的需求添加)
if isinstance(field, models.DateTimeField): # datetime
field_dict[name] = value.strftime('%Y-%m-%d %H:%M:%S') if value else ''
elif isinstance(field, models.DateField): # date
field_dict[name] = value.strftime('%Y-%m-%d') if value else ''
else: # 其他
# 判断是否有choices。(拓展功能,自动将有choices的值转化为相应的描述)
if choices and hasattr(field, 'choices') and field.choices:
f = getattr(instance, 'get_{}_display'.format(name))
field_dict[name] = f()
else:
field_dict[name] = value
return field_dict
总结
以上第关键的代码做了一些解释说明,相信小伙伴们仔细思考应该能动。代码的功能实现总体来说还是很简单的,只需要得到别序列化对象的
字段,然后根据字段的类型作出相应的操作。但是上述代码还有很多待优化的地方,下面给出一些建议(这个建议不保证能实现,只是一些方向)。
1.减少循环次数。2.由于遍历的结果集大部分情况数据都是相同的,且相同的model的数据顺序也是相同的,我们没必要对每一天数据都做类型判
断,可以在第一次循环后,缓存相关数据类型与操作。3.更多的拓展功能,比如只是OneToOne,ManyToMany等。
最后
帖子有什么不当的地方,欢迎大佬指出。