在Django中如何为在线菜单设计合理的分类、子分类与菜品模型?
你现有的实现已经能满足基础需求,不过从可维护性、查询效率、业务适配性角度,还可以做这几个优化:
1. 分类(Category)模型优化
- 调整父分类删除逻辑:你现在用的
on_delete=models.CASCADE,删除父分类会直接删掉所有子分类,风险很高。建议换成PROTECT(必须先删除所有子分类才能删父分类,防误操作)或者SET_NULL(父分类删除后子分类自动升级为一级分类),根据你的业务需求选就行。 - 增加层级校验:如果你的业务明确只用到两级分类,可以加
clean方法避免出现三级及以上分类,也能避免循环引用(比如A的父是B、B的父是A的异常情况)。 - 新增常用属性/方法:比如判断是否为一级分类的
is_root属性,获取完整分类路径的方法,后台展示的时候会方便很多。 - 补充Meta配置:加排序规则、后台显示的名称,使用体验更好。
2. 菜品(MenuItem)模型优化
- 规范命名:
other_Price改成蛇形命名other_price,符合Django和Python的通用开发规范,后续维护不容易出问题。 - 价格字段调整:
price的decimal_places现在设的是0,只能存整数价格,如果后续要支持带小数的定价(比如9.9元这种)改起来很麻烦,建议直接设成2,哪怕现在全是整数定价也完全不影响使用。 - 多对多关联字段优化:
related_name现在是单数item,改成复数items更符合语义,后续写查询的时候更顺,比如寿司分类.items.all()就能拿到分类下所有菜品。 - 去掉冗余参数:
ManyToManyField不需要设置null=True,因为多对多通过中间表关联,本身就支持空关联,只留blank=True即可。
3. 可选优化(适合查询量比较大的场景)
如果经常需要查「某个一级分类下的所有菜品(包括子分类下的菜品)」,可以在MenuItem里冗余一个root_category外键,绑定对应的一级分类,保存菜品的时候自动赋值,不用每次查询的时候都先查子分类再查菜品,能大幅提升查询效率。
优化后参考代码
from django.core.exceptions import ValidationError from django.db import models class Category(models.Model): name = models.CharField(max_length=200, verbose_name="分类名称") description = models.TextField(max_length=500, verbose_name="分类描述", blank=True, null=True) parent = models.ForeignKey( 'self', related_name='children', null=True, blank=True, on_delete=models.PROTECT, verbose_name="上级分类" ) class Meta: verbose_name = "菜单分类" verbose_name_plural = "菜单分类" ordering = ["name"] def clean(self): # 限制最多两级分类,不需要可以删掉这段校验 if self.parent and self.parent.parent: raise ValidationError("最多只能创建两级分类") # 避免循环引用 if self.parent == self: raise ValidationError("分类不能自己作为自己的上级分类") @property def is_root(self): """判断是否为一级分类""" return self.parent is None def __str__(self): if self.parent: return f"{self.parent.name} > {self.name}" return self.name class MenuItem(models.Model): name = models.CharField(max_length=200, verbose_name="菜品名称") image = models.ImageField(upload_to='menu_images/', null=True, blank=True, verbose_name="菜品图片") description = models.TextField(blank=True, null=True, verbose_name="菜品描述") price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="售价") other_price = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True, verbose_name="其他规格售价") categories = models.ManyToManyField( 'Category', related_name='items', blank=True, verbose_name="所属分类" ) # 可选:冗余一级分类,提升查询效率 # root_category = models.ForeignKey(Category, related_name="root_items", on_delete=models.PROTECT, verbose_name="所属一级分类") def __str__(self): return self.name
内容的提问来源于stack exchange,提问作者Naizen
相关产品推荐
相关产品推荐

