V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
abersheeran
V2EX  ›  Django

一种序列化 Django Model 的新思路

  •  
  •   abersheeran · 232 天前 · 1346 次点击
    这是一个创建于 232 天前的主题,其中的信息可能已经有所发展或是发生改变。
    from typing import Any, Dict, List
    
    from django.core.exceptions import FieldDoesNotExist
    from django.db import models
    
    
    def serialize_model(model: models.Model) -> Dict[str, Any]:
        result = {
            name: serialize_model(foreign_key)
            for name, foreign_key in model.__dict__["_state"].__dict__.get("fields_cache", {}).items()
        }
        for name, value in model.__dict__.items():
            try:
                model._meta.get_field(name)
            except FieldDoesNotExist:
                continue
            else:
                result[name] = value
        for name, queryset in model.__dict__.get("_prefetched_objects_cache", {}).items():
            result[name] = serialize_queryset(queryset)
        return result
    
    
    def serialize_queryset(queryset: models.QuerySet) -> List[Dict[str, Any]]:
        return [serialize_model(model) for model in queryset]
    

    发 V2EX 上给各位大佬看就不写那么多前后文的废话了。直接根据 Django Model 存储设计进行序列化,不需要定义额外的模型,不需要担心 N+1 查询。在我博客《一种序列化 Django model 的新思路》可以看看前因后果。

    16 条回复    2021-04-17 11:55:16 +08:00
    maocat
        1
    maocat  
       232 天前
    很好的东西,再来个装饰器封装一下直接返回结果了

    可惜的是很多人不遵从,他们热爱用 property 添加各式各样的属性,一点都不关心这种是不是需要优化
    ericls
        2
    ericls  
       232 天前 via iPhone   ❤️ 1
    要解决 N+1 问题 不能从单个 model 入手……
    一定要从一个 request 的全局入手
    ericls
        3
    ericls  
       232 天前 via iPhone   ❤️ 1
    先记录一个 request 中一共需要哪些东西 才有可能知道怎么去优化查询

    另外你这个治标不治本 只是把原来要查询的地方变成 None. Restful 的查询本来就是固定的 所以这种地方只在 dev 环境中和 CI 里面报错 上线环境就不会出错。
    另外这个问题也是 restful 自己的问题 如果一个请求拿不到我要的数据 我自然会发一个新的请求……
    abersheeran
        4
    abersheeran  
    OP
       232 天前
    @ericls 我说的 N+1 是 Django ORM 导致的 N+1 。业务上的 N+1 问题是另一回事。
    ericls
        5
    ericls  
       232 天前 via iPhone   ❤️ 1
    @abersheeran 你只是把 n+1 变成了 没有兑现的承诺而已 restful 返回格式很固定 与其破坏承诺 不如在 dev 和自己 test 让 n+1 报错 生产环境就不会 n*1 了…… 类似 type checking 的思路
    23333333333
        6
    23333333333  
       232 天前
    我感觉用了一些字符串和一些内部接口 比如._state

    这些就稍微有点不妥?
    ericls
        7
    ericls  
       232 天前 via iPhone
    @23333333333 Python library 就得这么写才爽
    nine
        8
    nine  
       232 天前   ❤️ 1
    Rails 欢迎你
    abersheeran
        9
    abersheeran  
    OP
       232 天前
    @ericls 目前来说,这已经是最佳的解决方法了——不显式的自己写预查询,这个序列化功能就不会给序列化外键数据。至于你说的 check,你可以试试给 Django 提 PR,反正我不抱有任何乐观看法。
    abersheeran
        10
    abersheeran  
    OP
       232 天前
    @23333333333 还行,Django 官方不提供接口,只能自己找方法了。说实话,这玩意让 Django 自己来做更好,奈何那群人不知道天天在想什么。你看这么多年都不支持 PUT 的请求体解析、TestClient 不支持 PUT 提交多段表单格式的数据。反正我现在不用 Django 。如果不是朋友找我帮忙,这个序列化方法我可能会让它一直停留在脑海里。
    abersheeran
        11
    abersheeran  
    OP
       232 天前
    @maocat 这个是小问题。合并两个字典列表,一行代码就够了。
    ericls
        12
    ericls  
       232 天前 via iPhone
    @abersheeran 你都用了这么多私有方法了 离 monkey patch 还远吗 况且只需要开发个测试环境中
    ericls
        13
    ericls  
       232 天前 via iPhone
    @abersheeran Django 也是标准 wsgi/asgi 任何不支持的东西裸写 wsgi/asgi 即可…… 甚至可以和别的框架混用 我经常这么干
    abersheeran
        14
    abersheeran  
    OP
       232 天前
    @ericls 你先自己试试再说吧。Talk is cheap
    ericls
        15
    ericls  
       232 天前
    @abersheeran 试了一下 没有想象中 hacky

    继承一下 ForwardManyToOneDescriptor 把 get_object 改成直接报错,然后继承 ForeignKey 把 class attribute `forward_related_accessor_class` 改成刚刚创建的 class 就搞定了。`related_accessor_class` 同理。当然,这个需要把用到原生 ForeignKey 的地方都替换了,所以比较推荐 monkey patch `django.db.models.fields.related_descriptors` 里面的方法 这个文件前面有详细的说明.
    abersheeran
        16
    abersheeran  
    OP
       231 天前 via Android
    @ericls 行。等你搞完,我去给你 star
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1630 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 33ms · UTC 17:04 · PVG 01:04 · LAX 09:04 · JFK 12:04
    ♥ Do have faith in what you're doing.