美文网首页Python/Go
Django model 序列化为json

Django model 序列化为json

作者: Van96 | 来源:发表于2018-04-18 21:08 被阅读1711次

    本文环境

    Python 3.6.5
    Django 2.0.4


    fix(2018.5.19):最近得知Django 的model基类需要声明为abstract,故在原来的代码加入abstract声明,以免误导


    在Django中,关于如何将model类序列化为json,一般的话有两a器

    • 将model类转为字典,再使用json库的dumps方法转为json

    第一种方法就不多讲了,直接去看官方文档就好啦
    一般来说,官方提供的方法应该都是比较好用和稳定的,然而,使用官方的序列化器却问题不少:

    1. 格式丑陋,格式如下,一言难尽:
    [
        {
            "pk": "4b678b301dfd8a4e0dad910de3ae245b",
            "model": "sessions.session",
            "fields": {
                "expire_date": "2013-01-16T08:16:59.844Z",
                ...
            }
        }
    ]
    

    是的,其中pk指的是默认主键,model指的是该object的model类型,然后fields才是obj的各种字段...真的是不知如何评价了

    1. 不能很好地支持list
    2. 对于一些外键(包括ManyToManyField等)不是很友好
    3. 甚至对于自身的DateField也没有很好的支持

    数了一通官方序列化器的缺点,当然了,上面的几个点肯定是有解决方案的,但是啊,我确实不想折腾了嘤嘤嘤。


    于是扔出我的解决方案:

    1. 新建一个类BaseModel,此类继承于官方的model类django.db.models.Model
    2. 在着个BaseModel中,声明一个方法,此方法用于生成关于这个object的字典
    3. 使用这个object的字典生成json

    关于生成object的字典的策略是这样的:

    1. 通过反射获取这个object的所有字段名
    2. 根据字段名获得某个字段field
    3. 如果filed的类型的是int、float、str的话,直接将以 "字段名":字段值 的形式放入字典中
    4. 若field的类型是datetime或者date的话,使用date的方式处理,然后放入字典
    5. 若field的类型是BaseModel的话,那么就调用该field的getDict方法递归获得该field对应的字典,然后放入字典中
    6. 若field的类型是ManyToMany类型,在具体草种中我们使用这个field的all方法来这个field的所有object,然后也是通过getDict方法将其放入到字典中

    源码及使用方法

    from django.db import models
    import json
    
    
    class BaseModel(models.Model):
        class Meta:
            abstract = True
    
        # 返回self._meta.fields中没有的,但是又是需要的字段名的列表
        # 形如['name','type']
        def getMtMField(self):
            pass
    
        # 返回需要在json中忽略的字段名的列表
        # 形如['password']
        def getIgnoreList(self):
            pass
    
        def isAttrInstance(self, attr, clazz):
            return isinstance(getattr(self, attr), clazz)
    
        def getDict(self):
            fields = []
            for field in self._meta.fields:
                fields.append(field.name)
    
            d = {}
            import datetime
            for attr in fields:
                if isinstance(getattr(self, attr), datetime.datetime):
                    d[attr] = getattr(self, attr).strftime('%Y-%m-%d %H:%M:%S')
                elif isinstance(getattr(self, attr), datetime.date):
                    d[attr] = getattr(self, attr).strftime('%Y-%m-%d')
                # 特殊处理datetime的数据
                elif isinstance(getattr(self, attr), BaseModel):
                    d[attr] = getattr(self, attr).getDict()
                # 递归生成BaseModel类的dict
                elif self.isAttrInstance(attr, int) or self.isAttrInstance(attr, float) \
                        or self.isAttrInstance(attr, str):
                    d[attr] = getattr(self, attr)
                # else:
                #     d[attr] = getattr(self, attr)
    
            mAttr = self.getMtMField()
            if mAttr is not None:
                for m in mAttr:
                    if hasattr(self, m):
                        attlist = getattr(self, m).all()
                        l = []
                        for attr in attlist:
                            if isinstance(attr, BaseModel):
                                l.append(attr.getDict())
                            else:
                                dic = attr.__dict__
                                if '_state' in dic:
                                    dic.pop('_state')
                                l.append(dic)
                        d[m] = l
            # 由于ManyToMany类不能存在于_meat.fields,因而子类需要在getMtMFiled中返回这些字段
            if 'basemodel_ptr' in d:
                d.pop('basemodel_ptr')
    
            ignoreList = self.getIgnoreList()
            if ignoreList is not None:
                for m in ignoreList:
                    if d.get(m) is not None:
                        d.pop(m)
            # 移除不需要的字段
            return d
    
        def toJSON(self):
            import json
            return json.dumps(self.getDict(), ensure_ascii=False).encode('utf-8').decode()
    

    使用方法:
    models的所有类都继承BaseModel类,然后调用此类的toJSON()方法即可
    注意,不知为何,self._meta.fields中没有包含ManyToManyField字段,因而需要重写getMtMField方法。例子如下:

    class Book(BaseModel):
        name = models.CharField(max_length=50)
        authors = models.ManyToManyField(Author)
        publish = models.ForeignKey(Publisher, on_delete=models.SET_NULL, blank=True, null=True)
        page = models.IntegerField(default=0)  # 页数
        introduction = models.CharField(max_length=500)
        bookType = models.ManyToManyField(BookType, null=True, blank=True)
        bookTag = models.ManyToManyField(BookTag, null=True, blank=True)
        evaluation = models.FloatField()
        coverUrl = models.CharField(max_length=100, null=True, blank=True)
    
        def getMtMField(self):
            return ['bookType', 'bookTag']
    

    结果:

    {
        "id":4,
        "name":"Django从入门到放弃",
        "page":123,
        "introduction":"introduction",
        "evaluation":1,
        "bookType":[
            {
                "id":1,
                "name":"类型"
            }
        ],
        "bookTag":[
            {
                "id":2,
                "name":"tag"
            }
        ]
    }
    

    后记

    1. 源码有引用,即getDict方法中的第一个for循环,但懒得找原链接了,望见谅,特此声明;
    2. 本人python新手,代码多有不规范之处,望见谅;
    3. 代码不精,但是也希望能帮到你_
    4. 原文链接:https://www.jianshu.com/p/c3db3cc2c80a

    相关文章

      网友评论

        本文标题:Django model 序列化为json

        本文链接:https://www.haomeiwen.com/subject/tavbkftx.html