You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

菜谱网站食材模块数据库模型设计及现有实现方案咨询

现有设计存在的问题
  • 结构冗余:全局食材和用户自定义食材字段完全一致,拆分两个表会导致查询、关联逻辑需要开发两套,后续维护成本很高
  • 中间表设计缺陷: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 20:24:04