带过敏原校验的食物追踪应用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
相关产品推荐
相关产品推荐

