V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
37Y37
V2EX  ›  Python

Django model 转字典的几种方法

  •  
  •   37Y37 · Oct 13, 2018 · 4402 views
    This topic created in 2759 days ago, the information mentioned may be changed or developed.

    平常的开发过程中不免遇到需要把 model 转成字典的需求,尤其是现在流行前后端分离架构,Json 格式几乎成了前后端之间数据交换的标准,这种 model 转 dict 的需求就更多了,本文介绍几种日常使用的方法以供参考,所有例子均基于 Django 2.0 环境演示

    背景介绍

    model 内容如下:

    class Group(models.Model):
        name = models.CharField(max_length=255, unique=True, verbose_name='组名称')
    
        def __str__(self):
            return self.name
    
    class User(models.Model):
        create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
        update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
        username = models.EmailField(max_length=255, unique=True, verbose_name='用户名')
        fullname = models.CharField(max_length=64, null=True, verbose_name='中文名')
        is_active = models.BooleanField(default=True, verbose_name='激活状态')
        leader = models.ForeignKey('self', null=True, on_delete=models.CASCADE, verbose_name='上级')
        group = models.ManyToManyField(Group, null=True, verbose_name='所属组')
    
        def __str__(self):
            return self.username
    

    需求很简单就是分别把 Group 和 User 表中的数据转换成字典格式返回

    方法一:直接构建字典

    示例代码:

    >>> _t = Group.objects.get(id=1)
    >>> 
    >>> dict = {
    ...   'id': _t.id,
    ...   'name': _t.name
    ... }
    >>> 
    >>> print(dict)
    {'name': 'GroupA', 'id': 1}
    

    这种方法的好处是方便控制最终返回字典 value 的格式,例如对于 User 表,我想返回最终的数据是 id、创建时间、中文名、上级中文名、所属组名列表的话可以用下边的代码实现

    >>> _t = User.objects.get(id=2)
    >>> 
    >>> dict = {
    ...   'id': _t.id,
    ...   'create_time': _t.create_time.strftime('%Y-%m-%d %H:%M:%S'),
    ...   'fullname': _t.fullname if _t.fullname else None,
    ...   'leader': _t.leader.fullname if _t.leader else None,
    ...   'group': [ i.name for i in _t.group.all() ],
    ... }
    >>> 
    >>> print(dict)
    {'fullname': '运维咖啡吧', 'group': ['GroupA', 'GroupC', 'GroupE'], 'create_time': '2018-10-12 21:20:19', 'id': 2, 'leader': '公众号'}
    >>> 
    

    缺点也很明显,就是如果一个 model 字段很多且不需要转换 value 格式的时候需要写大量冗余的代码,这种问题怎么解决呢?且看下边的方法介绍

    方法二:__dict__

    示例代码:

    >>> Group.objects.get(id=1).__dict__
    {'id': 1, 'name': 'GroupA', '_state': <django.db.models.base.ModelState object at 0x7f68612daef0>}
    >>> 
    >>> User.objects.get(id=1).__dict__
    {'is_active': True, '_state': <django.db.models.base.ModelState object at 0x7f68612fa0b8>, 'id': 1, 'username': '[email protected]', 'leader_id': None, 'fullname': '公众号', 'update_time': datetime.datetime(2018, 10, 12, 17, 49, 35, 504141), 'create_time': datetime.datetime(2018, 10, 12, 16, 9, 7, 813660)}
    

    这种方法优点就是写法简单,容易理解,代码量还少

    但会发现多了个没用的_state字段,同时 Foreignkey 字段名多了_id,也没有 ManyToManyField 字段的数据,且不能按需显示输出,当我只需要其中几个字段时会有大量冗余数据

    方法三:model_to_dict

    示例代码:

    >>> model_to_dict(Group.objects.get(id=1))
    {'name': 'GroupA', 'id': 1}
    >>> 
    >>> model_to_dict(User.objects.get(id=2))
    {'leader': 1, 'is_active': True, 'username': '[email protected]', 'fullname': '运维咖啡吧', 'group': [<Group: GroupA>, <Group: GroupC>, <Group: GroupE>], 'id': 2}
    

    这种方法能满足大部分的需求,且输出也较为合理,同时还有两个参数fieldsexclude来配置输出的字段,例如:

    >>> model_to_dict(User.objects.get(id=2), fields=['fullname','is_active'])
    {'is_active': True, 'fullname': '运维咖啡吧'}
    >>> 
    >>> model_to_dict(User.objects.get(id=2), exclude=['group','leader','id'])
    {'fullname': '运维咖啡吧', 'is_active': True, 'username': '[email protected]'}
    

    但是会跳过有editable=False属性字段的展示,对于有auto_now_add=Trueauto_now=True属性的 datetime 字段会默认添加editable=False隐藏属性,这也是上边两个 time 相关字段create_timeupdate_time转换成 dict 后不显示的原因,官方相关源码如下:

    for f in chain(opts.concrete_fields, opts.private_fields, opts.many_to_many):
        if not getattr(f, 'editable', False):
            continue
    

    方法四:自定义 to_dict

    示例代码:

    from django.db.models.fields import DateTimeField
    from django.db.models.fields.related import ManyToManyField
    
    class User(models.Model):
        ...
    
        def to_dict(self, fields=None, exclude=None):
            data = {}
            for f in self._meta.concrete_fields + self._meta.many_to_many:
                value = f.value_from_object(self)
    
                if fields and f.name not in fields:
                    continue
    
                if exclude and f.name in exclude:
                    continue
    
                if isinstance(f, ManyToManyField):
                    value = [ i.id for i in value ] if self.pk else None
    
                if isinstance(f, DateTimeField):
                    value = value.strftime('%Y-%m-%d %H:%M:%S') if value else None
    
                data[f.name] = value
    
            return data
    

    执行结果:

    >>> User.objects.get(id=2).to_dict()
    {'is_active': True, 'update_time': '2018-10-12 21:21:39', 'username': '[email protected]', 'id': 2, 'leader': 1, 'group': [1, 3, 5], 'create_time': '2018-10-12 21:20:19', 'fullname': '运维咖啡吧'}
    >>> 
    >>> User.objects.get(id=2).to_dict(fields=['fullname','is_active','create_time'])
    {'is_active': True, 'fullname': '运维咖啡吧', 'create_time': '2018-10-12 21:20:19'}
    >>> 
    >>> User.objects.get(id=2).to_dict(exclude=['group','leader','id','create_time'])
    {'is_active': True, 'update_time': '2018-10-12 21:21:39', 'username': '[email protected]', 'fullname': '运维咖啡吧'}
    

    拥有model_to_dict一样的便利性,同时也解决了不能输出 time 时间字段( editable=False )的问题,还能对 value 按照自己需要的格式输出,一举多得 当然拥有便利性的同时需要自己实现to_dict的代码,增加了复杂度


    长按关注公众号查看更多原创文章

    如果你觉得文章对你有帮助,请转发分享给更多的人。如果你觉得读的不尽兴,推荐阅读以下文章:

    12 replies    2018-10-15 18:21:08 +08:00
    Zzdex
        1
    Zzdex  
       Oct 13, 2018 via iPhone   ❤️ 1
    为啥不用 drf 呢
    WilliamYang
        2
    WilliamYang  
       Oct 13, 2018
    哎, 这水平
    37Y37
        3
    37Y37  
    OP
       Oct 13, 2018
    @Zzdex 有用 drf,如果项目就是前后端分离的 drf 确实是个不错的选择,某些项目比较轻没用到 drf 可以考虑这些方法
    37Y37
        4
    37Y37  
    OP
       Oct 13, 2018
    @WilliamYang 哪里有问题欢迎指出,非专业程序员,多跟大佬学习
    wellCh4n
        5
    wellCh4n  
       Oct 13, 2018
    我不知道这水平咋了,我觉得分析的不错啊…
    ffffish
        6
    ffffish  
       Oct 13, 2018
    这不是序列化问题吗,另外可以考虑 protobuf 之类的东西
    JasperYanky
        7
    JasperYanky  
       Oct 13, 2018
    不能直接用 drf 的 serializers 么? 定义一个 ModelSerializer 直接就转成 dict 了额~
    37Y37
        8
    37Y37  
    OP
       Oct 13, 2018
    @wellCh4n 感谢
    @ffffish 就是序列化的问题,我去看下 protobuf
    @JasperYanky 如果有用 drf,那 serializers 就简单多了,我上边回答里提了有些项目很小没有用 drf
    8e47e42
        9
    8e47e42  
       Oct 14, 2018 via iPhone
    我以为是提问,结果发现居然是教程
    sglyqfh
        10
    sglyqfh  
       Oct 14, 2018
    挺好的
    silhouette
        11
    silhouette  
       Oct 14, 2018 via Android
    drf 多好,serializer 一把梭
    metamask
        12
    metamask  
       Oct 15, 2018
    1.
    https://docs.djangoproject.com/en/2.0/topics/serialization/

    queryset
    from django.core import serializers
    data = serializers.serialize("json", SomeModel.objects.all())

    2.
    drf
    from . import serializers
    serializers.Serializer(obj).data

    小项目没用 drf,直接装一个,写个 model serializer 然后用上,比手动处理快捷很多
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   2363 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 60ms · UTC 10:32 · PVG 18:32 · LAX 03:32 · JFK 06:32
    ♥ Do have faith in what you're doing.