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

带过敏原校验的食物追踪应用Django数据模型设计咨询

合理数据模型设计及优化说明

你原有模型的核心问题是没有考虑过敏原的用户个性化属性,同时扩展能力不足,以下是优化后的完整方案:

核心优化点

  • 原有全局过敏原设计不符合实际使用逻辑,过敏原是用户个性化配置,需要和用户账号绑定
  • 支持用户自定义餐次,同时兼容系统默认餐次(早餐/午餐/晚餐等)
  • 预留扩展字段位,后续如果需要统计食材用量、热量等信息无需重构表结构
  • 内置过敏原自动校验逻辑,符合你要求的录入后自动校验的需求

优化后模型代码

from django.db import models
from django.conf import settings
from django.core.exceptions import ValidationError

class Meal(models.Model):
    """餐次分类"""
    name = models.CharField(max_length=50, verbose_name="餐次名称")
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name="所属用户", null=True, blank=True)
    # user字段为空时为系统默认餐次,所有用户可见;非空为用户自定义餐次,仅对应用户可见

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['name', 'user'], name='unique_meal_per_user')
        ]
        verbose_name = "餐次"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name


class Ingredient(models.Model):
    """基础食材库,全局统一维护"""
    name = models.CharField(max_length=50, unique=True, verbose_name="食材名称")

    class Meta:
        verbose_name = "食材"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name


class UserAllergen(models.Model):
    """用户个性化过敏原配置"""
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name="所属用户")
    ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE, verbose_name="过敏食材")

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['user', 'ingredient'], name='unique_allergen_per_user')
        ]
        verbose_name = "用户过敏原"
        verbose_name_plural = verbose_name

    def __str__(self):
        return f"{self.user.username}过敏:{self.ingredient.name}"


class Food(models.Model):
    """用户饮食记录"""
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name="所属用户")
    date = models.DateTimeField(verbose_name="食用时间")
    name = models.CharField(max_length=50, verbose_name="食物名称")
    notes = models.TextField(max_length=200, null=True, blank=True, verbose_name="备注")
    meal = models.ForeignKey(Meal, on_delete=models.CASCADE, verbose_name="所属餐次")
    ingredients = models.ManyToManyField(Ingredient, through='FoodIngredient', verbose_name="所用食材")
    has_allergen = models.BooleanField(default=False, verbose_name="是否包含过敏原")

    class Meta:
        verbose_name = "饮食记录"
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

    def clean(self):
        # 保存前自动执行过敏原校验
        super().clean()
        if self.pk:
            # 取当前用户的所有过敏原食材ID
            user_allergen_ids = UserAllergen.objects.filter(user=self.user).values_list('ingredient_id', flat=True)
            # 校验当前食物的食材是否命中过敏原
            self.has_allergen = self.ingredients.filter(id__in=user_allergen_ids).exists()
            # 如果需要直接阻断包含过敏原的食物保存,可以打开下面的注释
            # if self.has_allergen:
            #     raise ValidationError("当前食物包含您配置的过敏食材,请确认")


class FoodIngredient(models.Model):
    """食物与食材的关联中间表,可扩展用量、单位等字段"""
    food = models.ForeignKey(Food, on_delete=models.CASCADE, verbose_name="所属食物")
    ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE, verbose_name="食材")
    # 后续可扩展字段:
    # amount = models.FloatField(null=True, verbose_name="用量")
    # unit = models.CharField(max_length=10, null=True, verbose_name="单位")

    class Meta:
        verbose_name = "食物食材关联"
        verbose_name_plural = verbose_name

    def __str__(self):
        return f"{self.food.name} - {self.ingredient.name}"

内容的提问来源于stack exchange,提问作者bigredthelogger

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.14 08:06:05