django 不包括字段 序列化器_手写一个Django序列化功能

本文章的代码已上传至github上(github包含了更多功能,相关文章后续更新)

AGL1994/django-building​github.com
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等。

最后

帖子有什么不当的地方,欢迎大佬指出。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值