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

Django多对多关系构建及日期营养数据首页展示问题求助

问题解答

一、Django中构建多对多关系的正确方式

你当前已经通过through参数自定义了中间表DatesFoods来实现多对多关系,这里可以做几个优化让代码更符合Django规范:

  1. 优化中间表字段命名
    中间表的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)
    
  2. 添加反向查询别名
    在Dates模型的ManyToManyField中添加related_name,方便从Food反向查询关联的日期:

    class Dates(models.Model):
        food = models.ManyToManyField(Food, through='DatesFoods', related_name='related_dates')
        date = models.DateField()
    
  3. 正确创建与查询关联

    • 创建关联:通过中间表直接创建,或使用多对多字段的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 04:57:33