abersheeran
V2EX  ›  Django

一种序列化 Django Model 的新思路

  •  
  •   abersheeran · Apr 16, 2021 · 3558 views
    This topic created in 1863 days ago, the information mentioned may be changed or developed.
    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 replies    2021-04-17 11:55:16 +08:00
    maocat
        1
    maocat  
       Apr 16, 2021
    很好的东西,再来个装饰器封装一下直接返回结果了

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

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

    这些就稍微有点不妥?
    ericls
        7
    ericls  
       Apr 16, 2021 via iPhone
    @23333333333 Python library 就得这么写才爽
    nine
        8
    nine  
       Apr 16, 2021   ❤️ 1
    Rails 欢迎你
    abersheeran
        9
    abersheeran  
    OP
       Apr 16, 2021
    @ericls 目前来说,这已经是最佳的解决方法了——不显式的自己写预查询,这个序列化功能就不会给序列化外键数据。至于你说的 check,你可以试试给 Django 提 PR,反正我不抱有任何乐观看法。
    abersheeran
        10
    abersheeran  
    OP
       Apr 16, 2021
    @23333333333 还行,Django 官方不提供接口,只能自己找方法了。说实话,这玩意让 Django 自己来做更好,奈何那群人不知道天天在想什么。你看这么多年都不支持 PUT 的请求体解析、TestClient 不支持 PUT 提交多段表单格式的数据。反正我现在不用 Django 。如果不是朋友找我帮忙,这个序列化方法我可能会让它一直停留在脑海里。
    abersheeran
        11
    abersheeran  
    OP
       Apr 16, 2021
    @maocat 这个是小问题。合并两个字典列表,一行代码就够了。
    ericls
        12
    ericls  
       Apr 16, 2021 via iPhone
    @abersheeran 你都用了这么多私有方法了 离 monkey patch 还远吗 况且只需要开发个测试环境中
    ericls
        13
    ericls  
       Apr 16, 2021 via iPhone
    @abersheeran Django 也是标准 wsgi/asgi 任何不支持的东西裸写 wsgi/asgi 即可…… 甚至可以和别的框架混用 我经常这么干
    abersheeran
        14
    abersheeran  
    OP
       Apr 16, 2021
    @ericls 你先自己试试再说吧。Talk is cheap
    ericls
        15
    ericls  
       Apr 17, 2021
    @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
       Apr 17, 2021 via Android
    @ericls 行。等你搞完,我去给你 star
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   2668 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 38ms · UTC 10:56 · PVG 18:56 · LAX 03:56 · JFK 06:56
    ♥ Do have faith in what you're doing.