Django一分钟:在Django中怎么存储树形结构的数据,DRF校验递归嵌套模型的替代方案

引言

在开发过程中我们可能需要这样的树形结构:

python 复制代码
[
    {
        "data": {"name": "牛奶"},
        "children": [
            {"data": {"name": "蒙牛"}, },
            {"data": {"name": "伊利"}, }
        ]
    },
    {
        "data": {"name": "面包"},
        "children": [
            {"data": {"name": "黑面包"}, },
            {"data": {"name": "白面包"}, }
        ]
    }
]

这种需求很常见,比如:分层的用户组、产品组件表等等,你都会用到树形结构,为这样的需求在django中自己从头创建模型并实现一系列的API是一个相当大的工作,好在我们有很多现成的库可以使用。

如果你在想:Django能不能做到某件事情;来这个网站看看是个好习惯。

一、创建和使用模型

本文中我们用到的库是django-treebeard

shell 复制代码
pip install django-treebeard

django-treebeard提供了三种实现方式:邻接表、嵌套集、路径树(MPTT)。下面我们使用路径树来演示,创建模型如下:

python 复制代码
class Category(MP_Node):
    name = models.CharField(max_length=30)
    node_order_by = ['name']

    def __str__(self):
        return 'Category: {}'.format(self.name)

路径树会为你额外创建三个字段来辅助定义结构分别是pathdepthnumchild,它的插入和更新等维护比较繁重,但查询速度相当快。本文中我们不具体讨论实现原理,只是做简单使用演示,并为你可能遇到的问题提供思路,想详细了解其所有API阅读其文档

定义好模型之后,模型中的一些方法可以辅助我们完成序列化和反序列化,快速的在数据库中保存和拉取数据:

python 复制代码
>>> data =[
    {
        # tree1
        "data": {"name": "牛奶"},
        "children": [
            {"data": {"name": "蒙牛"}, },
            {"data": {"name": "伊利"}, }
        ]
    },
    {
        # tree2
        "data": {"name": "面包"},
        "children": [
            {"data": {"name": "黑面包"}, },
            {"data": {"name": "白面包"}, }
        ]
    }
]
>>> Category.load_bulk(data)
>>> Category.dump_bulk()
# [{'data': {'name': '牛奶'}, 'id': 1, 'children': [{'data': {'name': '伊利'}, 'id': 3}, {'data': {'name': '蒙牛'}, 'id': 2}]}, {'data': {'name': '面包'}, 'id': 4, 'children': [{'data': {'name': '白面包'}, 'id': 6}, {'data': {'name': '黑面包'}, 'id': 5}]}]

二、数据校验

假如你使用django rest framework为这样的递归嵌套的模型创建序列化器非常的困难,我推荐的解决方法是使用pydantic,如果你学习过FastAPI框架你应该对它不陌生。pydantic无法从django模型中自动生成模型,你需要自己定义:

python 复制代码
from pydantic import BaseModel

class CategoryNodelModel(BaseModel):
    name: str

class CategoryTreeModel(BaseModel):
    data: CategoryNodelModel
    children: list['CategoryTreeModel'] | None = None

使用方法:

python 复制代码
# 假设用户输入
>>> data = [
    {
        # tree1
        "data": {"name": "牛奶"},
        "children": [
            {"data": {"name": "蒙牛"}, },
            {"data": {"name": "伊利"}, }
        ]
    },
    {
        # tree2
        "data": {"name": "面包"},
        "children": [
            {"data": {"name": "黑面包"}, },
            {"data": {"name": "白面包"}, }
        ]
    }
]
# 测试用户输入的结构是否正确
>>> [CategoryTreeModel(**tree) for tree in data]
# 保存到数据库
>>> Category.load_bulk(data)

下面是测试代码,包含对一些api的简单演示:

python 复制代码
from rest_framework.test import APITestCase
from api.models import Category
from api.views import CategoryS, CategoryNodelModel, CategoryTreeModel


class TestCategory(APITestCase):

    def setUp(self):
        # 用户输入
        data = [
            {
                # tree1
                "data": {"name": "牛奶"},
                "children": [
                    {"data": {"name": "蒙牛"}, },
                    {"data": {"name": "伊利"}, }
                ]
            },
            {
                # tree2
                "data": {"name": "面包"},
                "children": [
                    {"data": {"name": "黑面包"}, },
                    {"data": {"name": "白面包"}, }
                ]
            }
        ]
        # 测试用户输入的结构是否正确
        [CategoryTreeModel(**tree) for tree in data]
        Category.load_bulk(data)

    def test_get_all(self):
        print(Category.dump_bulk())

    def test_get_tree(self):
        milk = Category.objects.filter(name='牛奶').first()
        if milk:
            print(Category.dump_bulk(milk))

    def test_delete(self):
        milk = Category.objects.filter(name='面包').first()
        milk.delete()
        print(Category.dump_bulk())

总结

对于在django中使用树、图等复杂结构之类需求,可以去尝试寻找现成的库;对于复杂数据的校验可以尝试使用pydantic。选择合适的工具会为我们节省很多时间。

相关推荐
Elastic 中国社区官方博客1 小时前
在 Elasticsearch 中使用 Mistral Chat completions 进行上下文工程
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
编程爱好者熊浪3 小时前
两次连接池泄露的BUG
java·数据库
TDengine (老段)5 小时前
TDengine 字符串函数 CHAR 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
qq7422349845 小时前
Python操作数据库之pyodbc
开发语言·数据库·python
姚远Oracle ACE5 小时前
Oracle 如何计算 AWR 报告中的 Sessions 数量
数据库·oracle
Dxy12393102166 小时前
MySQL的SUBSTRING函数详解与应用
数据库·mysql
码力引擎6 小时前
【零基础学MySQL】第十二章:DCL详解
数据库·mysql·1024程序员节
杨云龙UP6 小时前
【MySQL迁移】MySQL数据库迁移实战(利用mysqldump从Windows 5.7迁至Linux 8.0)
linux·运维·数据库·mysql·mssql
l1t6 小时前
利用DeepSeek辅助修改luadbi-duckdb读取DuckDB decimal数据类型
c语言·数据库·单元测试·lua·duckdb
安当加密6 小时前
Nacos配置安全治理:把数据库密码从YAML里请出去
数据库·安全