AberSheeran
Aber Sheeran

一种序列化 Django model 的新思路

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

在我编写一种新的 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]:
    serialized = set()

    def _serialize_model(model: models.Model) -> Dict[str, Any]:
        if model in serialized:
            return model.pk
        else:
            serialized.add(model)

        result: Dict[str, Any] = {
            name: _serialize_model(foreign_key)
            for name, foreign_key in model.__dict__["_state"]
            .__dict__.get("fields_cache", {})
            .items()
            if foreign_key is not None
        }

        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

    return _serialize_model(model)


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 中的应用