一种序列化 Django Model 的新思路

2021-04-16 10:30:09 +08:00
 abersheeran
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 的新思路》可以看看前因后果。

2353 次点击
所在节点    Django
16 条回复
maocat
2021-04-16 11:26:41 +08:00
很好的东西,再来个装饰器封装一下直接返回结果了

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

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

这些就稍微有点不妥?
ericls
2021-04-16 13:23:10 +08:00
@23333333333 Python library 就得这么写才爽
nine
2021-04-16 13:27:34 +08:00
Rails 欢迎你
abersheeran
2021-04-16 17:33:11 +08:00
@ericls 目前来说,这已经是最佳的解决方法了——不显式的自己写预查询,这个序列化功能就不会给序列化外键数据。至于你说的 check,你可以试试给 Django 提 PR,反正我不抱有任何乐观看法。
abersheeran
2021-04-16 17:37:22 +08:00
@23333333333 还行,Django 官方不提供接口,只能自己找方法了。说实话,这玩意让 Django 自己来做更好,奈何那群人不知道天天在想什么。你看这么多年都不支持 PUT 的请求体解析、TestClient 不支持 PUT 提交多段表单格式的数据。反正我现在不用 Django 。如果不是朋友找我帮忙,这个序列化方法我可能会让它一直停留在脑海里。
abersheeran
2021-04-16 17:38:32 +08:00
@maocat 这个是小问题。合并两个字典列表,一行代码就够了。
ericls
2021-04-16 21:18:54 +08:00
@abersheeran 你都用了这么多私有方法了 离 monkey patch 还远吗 况且只需要开发个测试环境中
ericls
2021-04-16 21:21:02 +08:00
@abersheeran Django 也是标准 wsgi/asgi 任何不支持的东西裸写 wsgi/asgi 即可…… 甚至可以和别的框架混用 我经常这么干
abersheeran
2021-04-16 22:34:29 +08:00
@ericls 你先自己试试再说吧。Talk is cheap
ericls
2021-04-17 01:03:31 +08:00
@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
2021-04-17 11:55:16 +08:00
@ericls 行。等你搞完,我去给你 star

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/771023

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX