
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 的新思路》可以看看前因后果。
1 maocat 2021 年 4 月 16 日 很好的东西,再来个装饰器封装一下直接返回结果了 可惜的是很多人不遵从,他们热爱用 property 添加各式各样的属性,一点都不关心这种是不是需要优化 |
2 ericls 2021 年 4 月 16 日 via iPhone 要解决 N+1 问题 不能从单个 model 入手…… 一定要从一个 request 的全局入手 |
3 ericls 2021 年 4 月 16 日 via iPhone 先记录一个 request 中一共需要哪些东西 才有可能知道怎么去优化查询 另外你这个治标不治本 只是把原来要查询的地方变成 None. Restful 的查询本来就是固定的 所以这种地方只在 dev 环境中和 CI 里面报错 上线环境就不会出错。 另外这个问题也是 restful 自己的问题 如果一个请求拿不到我要的数据 我自然会发一个新的请求…… |
4 abersheeran OP @ericls 我说的 N+1 是 Django ORM 导致的 N+1 。业务上的 N+1 问题是另一回事。 |
5 ericls 2021 年 4 月 16 日 via iPhone @abersheeran 你只是把 n+1 变成了 没有兑现的承诺而已 restful 返回格式很固定 与其破坏承诺 不如在 dev 和自己 test 让 n+1 报错 生产环境就不会 n*1 了…… 类似 type checking 的思路 |
6 23333333333 2021 年 4 月 16 日 我感觉用了一些字符串和一些内部接口 比如._state 这些就稍微有点不妥? |
7 ericls 2021 年 4 月 16 日 via iPhone @23333333333 Python library 就得这么写才爽 |
8 nine 2021 年 4 月 16 日 Rails 欢迎你 |
9 abersheeran OP @ericls 目前来说,这已经是最佳的解决方法了不显式的自己写预查询,这个序列化功能就不会给序列化外键数据。至于你说的 check,你可以试试给 Django 提 PR,反正我不抱有任何乐观看法。 |
10 abersheeran OP @23333333333 还行,Django 官方不提供接口,只能自己找方法了。说实话,这玩意让 Django 自己来做更好,奈何那群人不知道天天在想什么。你看这么多年都不支持 PUT 的请求体解析、TestClient 不支持 PUT 提交多段表单格式的数据。反正我现在不用 Django 。如果不是朋友找我帮忙,这个序列化方法我可能会让它一直停留在脑海里。 |
11 abersheeran OP @maocat 这个是小问题。合并两个字典列表,一行代码就够了。 |
12 ericls 2021 年 4 月 16 日 via iPhone @abersheeran 你都用了这么多私有方法了 离 monkey patch 还远吗 况且只需要开发个测试环境中 |
13 ericls 2021 年 4 月 16 日 via iPhone @abersheeran Django 也是标准 wsgi/asgi 任何不支持的东西裸写 wsgi/asgi 即可…… 甚至可以和别的框架混用 我经常这么干 |
14 abersheeran OP @ericls 你先自己试试再说吧。Talk is cheap |
15 ericls 2021 年 4 月 17 日 @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` 里面的方法 这个文件前面有详细的说明. |
16 abersheeran OP @ericls 行。等你搞完,我去给你 star |