引言
在开发过程中我们可能需要这样的树形结构:
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)
路径树会为你额外创建三个字段来辅助定义结构分别是path
、depth
、numchild
,它的插入和更新等维护比较繁重,但查询速度相当快。本文中我们不具体讨论实现原理,只是做简单使用演示,并为你可能遇到的问题提供思路,想详细了解其所有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
。选择合适的工具会为我们节省很多时间。