Django model 序列化為json

本文環(huán)境

Python 3.6.5
Django 2.0.4


fix(2018.5.19):最近得知Django 的model基類需要聲明為abstract,故在原來的代碼加入abstract聲明,以免誤導(dǎo)


在Django中塑煎,關(guān)于如何將model類序列化為json畴蹭,一般的話有兩種方法

  • 將model類轉(zhuǎn)為字典鼎天,再使用json庫的dumps方法轉(zhuǎn)為json

第一種方法就不多講了晦鞋,直接去看官方文檔就好啦
一般來說罢防,官方提供的方法應(yīng)該都是比較好用和穩(wěn)定的艘虎,然而,使用官方的序列化器卻問題不少:

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

是的,其中pk指的是默認(rèn)主鍵恬叹,model指的是該object的model類型候生,然后fields才是obj的各種字段...真的是不知如何評價(jià)了

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

數(shù)了一通官方序列化器的缺點(diǎn),當(dāng)然了绽昼,上面的幾個(gè)點(diǎn)肯定是有解決方案的唯鸭,但是啊,我確實(shí)不想折騰了嚶嚶嚶硅确。


于是扔出我的解決方案:

  1. 新建一個(gè)類BaseModel目溉,此類繼承于官方的model類django.db.models.Model
  2. 在著個(gè)BaseModel中,聲明一個(gè)方法菱农,此方法用于生成關(guān)于這個(gè)object的字典
  3. 使用這個(gè)object的字典生成json

關(guān)于生成object的字典的策略是這樣的:

  1. 通過反射獲取這個(gè)object的所有字段名
  2. 根據(jù)字段名獲得某個(gè)字段field
  3. 如果filed的類型的是int停做、float、str的話大莫,直接將以 "字段名":字段值 的形式放入字典中
  4. 若field的類型是datetime或者date的話蛉腌,使用date的方式處理,然后放入字典
  5. 若field的類型是BaseModel的話只厘,那么就調(diào)用該field的getDict方法遞歸獲得該field對應(yīng)的字典烙丛,然后放入字典中
  6. 若field的類型是ManyToMany類型,在具體草種中我們使用這個(gè)field的all方法來這個(gè)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的數(shù)據(jù)
            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類赋元,然后調(diào)用此類的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)  # 頁數(shù)
    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']

結(jié)果:

{
    "id":4,
    "name":"Django從入門到放棄",
    "page":123,
    "introduction":"introduction",
    "evaluation":1,
    "bookType":[
        {
            "id":1,
            "name":"類型"
        }
    ],
    "bookTag":[
        {
            "id":2,
            "name":"tag"
        }
    ]
}

后記

  1. 源碼有引用,即getDict方法中的第一個(gè)for循環(huán)护糖,但懶得找原鏈接了褥芒,望見諒,特此聲明嫡良;
  2. 本人python新手锰扶,代碼多有不規(guī)范之處献酗,望見諒;
  3. 代碼不精坷牛,但是也希望能幫到你_罕偎;
  4. 原文鏈接:http://www.reibang.com/p/c3db3cc2c80a
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市京闰,隨后出現(xiàn)的幾起案子锨亏,更是在濱河造成了極大的恐慌,老刑警劉巖忙干,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異浪藻,居然都是意外死亡捐迫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門爱葵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來施戴,“玉大人,你說我怎么就攤上這事萌丈≡藁” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵辆雾,是天一觀的道長肪笋。 經(jīng)常有香客問我,道長度迂,這世上最難降的妖魔是什么藤乙? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮惭墓,結(jié)果婚禮上坛梁,老公的妹妹穿的比我還像新娘。我一直安慰自己腊凶,他們只是感情好划咐,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钧萍,像睡著了一般褐缠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上风瘦,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天送丰,我揣著相機(jī)與錄音,去河邊找鬼弛秋。 笑死器躏,一個(gè)胖子當(dāng)著我的面吹牛俐载,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播登失,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼遏佣,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了揽浙?” 一聲冷哼從身側(cè)響起状婶,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎馅巷,沒想到半個(gè)月后膛虫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钓猬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年稍刀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敞曹。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡账月,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出澳迫,到底是詐尸還是另有隱情局齿,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布橄登,位于F島的核電站抓歼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拢锹。R本人自食惡果不足惜锭部,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望面褐。 院中可真熱鬧拌禾,春花似錦、人聲如沸展哭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匪傍。三九已至您市,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間役衡,已是汗流浹背茵休。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人榕莺。 一個(gè)月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓俐芯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钉鸯。 傳聞我的和親對象是個(gè)殘疾皇子吧史,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360