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。选择合适的工具会为我们节省很多时间。

相关推荐
MonkeyKing_sunyuhua26 分钟前
ubuntu22.04 docker-compose安装postgresql数据库
数据库·docker·postgresql
天郁青26 分钟前
数据库交互的本地项目:后台管理系统
数据库·交互
马剑威(威哥爱编程)31 分钟前
MongoDB面试专题33道解析
数据库·mongodb·面试
小光学长1 小时前
基于vue框架的的流浪宠物救助系统25128(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
数据库·vue.js·宠物
零炻大礼包2 小时前
【SQL server】数据库远程连接配置
数据库
zmgst2 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
随心............2 小时前
python操作MySQL以及SQL综合案例
数据库·mysql
€☞扫地僧☜€2 小时前
docker 拉取MySQL8.0镜像以及安装
运维·数据库·docker·容器
CopyDragon2 小时前
设置域名跨越访问
数据库·sqlite
xjjeffery2 小时前
MySQL 基础
数据库·mysql