Django多对多关系构建及日期营养数据首页展示问题求助
问题解答
一、Django中构建多对多关系的正确方式
你当前已经通过through参数自定义了中间表DatesFoods来实现多对多关系,这里可以做几个优化让代码更符合Django规范:
优化中间表字段命名
中间表的date_id和food_id可以简化为date和food,Django会自动在数据库中生成date_id和food_id的列名,同时代码可读性更强:class DatesFoods(models.Model): date = models.ForeignKey(Dates, on_delete=models.CASCADE) food = models.ForeignKey(Food, on_delete=models.CASCADE)添加反向查询别名
在Dates模型的ManyToManyField中添加related_name,方便从Food反向查询关联的日期:class Dates(models.Model): food = models.ManyToManyField(Food, through='DatesFoods', related_name='related_dates') date = models.DateField()正确创建与查询关联
- 创建关联:通过中间表直接创建,或使用多对多字段的
add方法(如果中间表无额外字段)# 方式1:中间表创建 date_obj = Dates.objects.get(date='2024-05-20') food_obj = Food.objects.get(name='苹果') DatesFoods.objects.create(date=date_obj, food=food_obj) # 方式2:多对多字段add方法 date_obj.food.add(food_obj) - 查询关联:
# 获取某日期的所有食物 date_obj.food.all() # 获取某食物关联的所有日期 food_obj.related_dates.all()
- 创建关联:通过中间表直接创建,或使用多对多字段的
二、首页展示各日期营养详情的实现方案
要在首页展示每个日期的总营养数据,推荐将计算逻辑封装到模型中,同时优化视图的数据库查询性能:
步骤1:给Dates模型添加营养计算方法
class Dates(models.Model): food = models.ManyToManyField(Food, through='DatesFoods', related_name='related_dates') date = models.DateField() def get_total_nutrition(self): # 预加载关联数据,减少数据库查询次数 date_foods = self.datesfoods_set.all().select_related('food') total_protein = sum(df.food.protein for df in date_foods) total_fats = sum(df.food.fats for df in date_foods) total_carbs = sum(df.food.carbohydrates for df in date_foods) total_calories = sum(df.food.calories for df in date_foods) return { 'protein': total_protein, 'fats': total_fats, 'carbohydrates': total_carbs, 'calories': total_calories }
步骤2:修正create_date视图
修复原视图中GET请求初始化表单的问题,同时预加载关联数据提升性能:
def create_date(request): if request.method == 'POST': form = Dates_Form(request.POST) if form.is_valid(): form.save() else: # GET请求时初始化空表单 form = Dates_Form() # 预加载关联的中间表和食物数据,避免N+1查询 dates = Dates.objects.all().prefetch_related('datesfoods_set__food') return render(request, 'foodlist/home.html', context={'form': form, 'dates': dates})
步骤3:在首页模板中调用方法展示数据
在home.html中遍历日期并调用模型方法:
{% for date_item in dates %} <div class="date-item"> <h3>{{ date_item.date }}</h3> <p>蛋白质:{{ date_item.get_total_nutrition.protein }}g</p> <p>脂肪:{{ date_item.get_total_nutrition.fats }}g</p> <p>碳水化合物:{{ date_item.get_total_nutrition.carbohydrates }}g</p> <p>总热量:{{ date_item.get_total_nutrition.calories }} kcal</p> </div> {% endfor %}
额外优化:自动计算食物热量
原代码中需要手动调用total_calories_food方法,建议重写Food的save方法,自动计算热量:
class Food(models.Model): name = models.CharField(max_length=512, unique=True) protein = models.IntegerField(null=False) carbohydrates = models.IntegerField(null=False) fats = models.IntegerField(null=False) calories = models.IntegerField(null=True) def save(self, *args, **kwargs): self.calories = self.protein * 4 + self.carbohydrates * 4 + self.fats * 9 super().save(*args, **kwargs) def __str__(self): return f'{self.name}'
这样新增或修改食物时,热量会自动计算并保存,无需在视图中循环调用方法。
内容的提问来源于stack exchange,提问作者Clever
相关产品推荐
相关产品推荐

