菜谱网站食材模块数据库模型设计及现有实现方案咨询
现有设计存在的问题
- 结构冗余:全局食材和用户自定义食材字段完全一致,拆分两个表会导致查询、关联逻辑需要开发两套,后续维护成本很高
- 中间表设计缺陷:
IngredientToRecipe同时关联两个食材表的外键,每次使用都需要判断哪个字段非空,容易出现逻辑bug,也无法通过约束避免重复关联;且额外增加的user外键没有必要,所属用户可以直接通过关联的菜谱或食材获取 - Pantry关联逻辑错误:当前
pantryIngredients关联的是菜谱和食材的用量中间表,而非食材本身,不符合用户存储食材库存的需求 - 存在语法和规范问题:字段参数
Null应为小写null,pantryIngredients类名不符合Python大驼峰命名规范,inStock字段多写了一个等于号
优化实现方案
优化思路是合并两个食材表,通过类型字段区分全局/自定义属性,大幅简化关联逻辑:
优化后 recipe/models.py 代码
from django.db import models from django.conf import settings from django.db.models import Q User = settings.AUTH_USER_MODEL class Ingredient(models.Model): # 食材类型枚举 TYPE_GLOBAL = "global" TYPE_USER = "user" TYPE_CHOICES = [ (TYPE_GLOBAL, "全局通用"), (TYPE_USER, "用户自定义"), ] name = models.CharField(max_length=220, verbose_name="食材名称") type = models.CharField(max_length=10, choices=TYPE_CHOICES, default=TYPE_USER, verbose_name="食材类型") user = models.ForeignKey( User, on_delete=models.CASCADE, null=True, blank=True, verbose_name="所属用户" ) class Meta: constraints = [ # 全局食材名称唯一 models.UniqueConstraint( fields=["name", "type"], condition=Q(type="global"), name="unique_global_ingredient_name" ), # 同一用户下自定义食材名称唯一 models.UniqueConstraint( fields=["name", "user"], condition=Q(type="user"), name="unique_user_ingredient_name" ), ] def __str__(self): return self.name class Recipe(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="所属用户") name = models.CharField(max_length=220, verbose_name="菜谱名称") ingredients = models.ManyToManyField( Ingredient, through="RecipeIngredient", blank=True, verbose_name="所需食材" ) def __str__(self): return self.name class RecipeIngredient(models.Model): recipe = models.ForeignKey( Recipe, on_delete=models.CASCADE, related_name="recipe_ingredients", verbose_name="所属菜谱" ) ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE, verbose_name="食材") quantity = models.CharField(max_length=50, blank=True, null=True, verbose_name="用量数值") unit = models.CharField(max_length=50, blank=True, null=True, verbose_name="用量单位") note = models.TextField(blank=True, null=True, verbose_name="处理说明") class Meta: # 同一菜谱内同一食材避免重复关联 unique_together = ("recipe", "ingredient") def __str__(self): return f"{self.recipe.name} - {self.ingredient.name}"
优化后 pantry/models.py 代码
from django.db import models from django.conf import settings from recipes.models import Ingredient User = settings.AUTH_USER_MODEL class PantryIngredient(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="所属用户") ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE, verbose_name="食材") stock = models.IntegerField(default=0, verbose_name="库存数量") class Meta: # 同一用户的同一食材避免重复存储库存 unique_together = ("user", "ingredient") def __str__(self): return f"{self.user.username}的{self.ingredient.name}库存:{self.stock}"
方案优势
- 逻辑更简洁:查询用户可用食材只需一条语句
Ingredient.objects.filter(Q(type="global") | Q(user=当前用户id)),无需跨表查询两个食材表 - 扩展性更强:后续需要新增食材分类、热量、保质期等公共属性,只需在
Ingredient表新增字段即可 - 避免冗余数据:新增了合理的唯一约束,从数据库层面避免重复数据生成
- 符合业务逻辑: pantry直接关联食材本身,不管是全局还是用户自定义食材都可以直接存储库存
内容的提问来源于stack exchange,提问作者Nick
相关产品推荐
相关产品推荐

