AberSheeran
Aber Sheeran

一种序列化 Django model 的新思路

起笔自
所属文集: Django-Simple-Api
共计 1556 个字符
落笔于

在我编写一种新的 ORM 时,我更深入的研究了 Python ORM 的设计,这让我有了足够的知识来支撑起两年前的一个序列化 Django Model 的想法。

一般来说序列化 Django Model 都会使用 django-rest-framework 的序列化功能。使用它,你首先需要根据需求定义一个 Serializer 模型,然后通过这个模型去序列化 Django Model,它通常意味着你需要同时维护两套模型。而且在做复杂的跨表序列化操作时,这很容易导致 N+1 查询。

使用 Django ORM 的人都知道,Django 是有 prefetch_relatedselect_related 两个预查询功能来处理跨表查询中的 N+1 查询问题。那么预查询出来的模型缓存在哪儿了呢?

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]

在我研究之后发现 prefetch_related 预查询出来的结果会作为 QuerySet 对象存放在 __dict__["_prefetched_objects_cache"] 这个字典中字段名对应的键下;select_related 预查询出来的结果会存放在 __dict__["_state"].__dict__["fields_cache"] 中字段名对应的键下。

于是这就有了上面的代码。它不会自做主张的去查询数据库,只用你查询出来的结果,成功避免了 N+1 查询问题。通过查询时的 deferonly 操作可以控制哪些模型字段被查询,通过预查询语句可以控制哪些外键字段被查询。它不需要你维护另一套模型,你只需要专注于数据库模型正确即可。

如果你觉得本文值得,不妨赏杯茶
Django 解决跨域请求
MVT 模式在 API 中的应用