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-04-16 11:26:41 +08:00
很好的东西,再来个装饰器封装一下直接返回结果了
可惜的是很多人不遵从,他们热爱用 property 添加各式各样的属性,一点都不关心这种是不是需要优化 |
2
ericls 2021-04-16 11:35:54 +08:00 via iPhone 1
要解决 N+1 问题 不能从单个 model 入手……
一定要从一个 request 的全局入手 |
3
ericls 2021-04-16 11:42:20 +08:00 via iPhone 1
先记录一个 request 中一共需要哪些东西 才有可能知道怎么去优化查询
另外你这个治标不治本 只是把原来要查询的地方变成 None. Restful 的查询本来就是固定的 所以这种地方只在 dev 环境中和 CI 里面报错 上线环境就不会出错。 另外这个问题也是 restful 自己的问题 如果一个请求拿不到我要的数据 我自然会发一个新的请求…… |
4
abersheeran OP @ericls 我说的 N+1 是 Django ORM 导致的 N+1 。业务上的 N+1 问题是另一回事。
|
5
ericls 2021-04-16 13:08:50 +08:00 via iPhone 1
@abersheeran 你只是把 n+1 变成了 没有兑现的承诺而已 restful 返回格式很固定 与其破坏承诺 不如在 dev 和自己 test 让 n+1 报错 生产环境就不会 n*1 了…… 类似 type checking 的思路
|
6
23333333333 2021-04-16 13:13:20 +08:00
我感觉用了一些字符串和一些内部接口 比如._state
这些就稍微有点不妥? |
7
ericls 2021-04-16 13:23:10 +08:00 via iPhone
@23333333333 Python library 就得这么写才爽
|
8
nine 2021-04-16 13:27:34 +08:00 1
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-04-16 21:18:54 +08:00 via iPhone
@abersheeran 你都用了这么多私有方法了 离 monkey patch 还远吗 况且只需要开发个测试环境中
|
13
ericls 2021-04-16 21:21:02 +08:00 via iPhone
@abersheeran Django 也是标准 wsgi/asgi 任何不支持的东西裸写 wsgi/asgi 即可…… 甚至可以和别的框架混用 我经常这么干
|
14
abersheeran OP @ericls 你先自己试试再说吧。Talk is cheap
|
15
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` 里面的方法 这个文件前面有详细的说明. |
16
abersheeran OP @ericls 行。等你搞完,我去给你 star
|